We Wprowadzeniu do programowania nauczyliśmy się, że jeśli podzielisz dwie liczby całkowite, zawsze otrzymasz liczbę całkowitą. Aby rozwiązać problem, ustaw co najmniej jedną z tych liczb całkowitych jako zmiennoprzecinkową.
Dlaczego kompilator nie rozumie, że chcę, aby wynik był liczbą dziesiętną?
Komentarze
Odpowiedź
Dlaczego kompilator nie rozumie, że chcę, aby wynik był liczbą dziesiętną?
Kompilator C ++ po prostu postępuje zgodnie z dobrze zdefiniowanymi i deterministycznymi regułami przedstawionymi w standardzie C ++. Standard C ++ ma te reguły, ponieważ komitet normalizacyjny postanowił zrobić to w ten sposób.
Mogli napisać normę mówiącą, że matematyka liczb całkowitych daje w wyniku liczby zmiennoprzecinkowe lub robi to tylko w przypadek reszty. Jednak to zwiększa złożoność: albo muszę wiedzieć z wyprzedzeniem, jaki jest wynik, albo może przekonwertować z powrotem na liczbę całkowitą, jeśli zawsze daje to liczbę zmiennoprzecinkową. Może chcę liczbę całkowitą .
Jedną z podstawowych filozofii C ++ jest „nie płacisz za to, czego nie używasz ”. Jeśli faktycznie chcesz złożoność mieszania liczb całkowitych i zmiennoprzecinkowych (a dodatkowe instrukcje procesora i dostęp do pamięci pociągają za sobą 1 ), wykonaj rzutowanie typu, jak wspomniałeś w swoim pytaniu . W przeciwnym razie trzymaj się standardowej matematyki całkowitoliczbowej.
Wreszcie, mieszanie zmiennych całkowitych i zmiennoprzecinkowych może spowodować utratę precyzji, a czasami nieprawidłowe wyniki, co omówię poniżej. Jeśli chcesz tego, zapłać za to: w przeciwnym razie standard nakazuje kompilatorom trzymać się ścisłego zestawu reguł mieszania typów danych. To dobrze zdefiniowane zachowanie: jako programista C ++ mogę to sprawdzić w standardzie i zobaczyć, jak to działa.
Zasadniczo istnieją trzy sposoby robienia tego, co próbujesz zrobić, każdy ma zalety i wady.
-
Matematyka liczb całkowitych: skutkuje to obcięciem wyników podczas dzielenia, jak się dowiedziałeś. Jeśli potrzebujesz części dziesiętnej, musisz potraktować ją oddzielnie, dzieląc, uzyskując resztę i traktując część dziesiętną jako resztę podzieloną przez dzielnik. Jest to trochę bardziej złożona operacja i wymaga więcej zmiennych do żonglowania.
-
Matematyka zmiennoprzecinkowa: generalnie da poprawne (wystarczające) wyniki dla małych wartości, ale może łatwo wprowadzać błędy z precyzją i zaokrąglaniem, zwłaszcza przy wzroście wykładnika. Jeśli podzielisz dużą liczbę przez małą, możesz nawet spowodować niedomiar lub po prostu otrzymać zły wynik, ponieważ skale liczb nie współgrają ze sobą.
-
Zrób własną matematykę. Istnieją klasy obsługujące rozszerzoną precyzję liczb dziesiętnych i wymiernych . Zwykle będą one wolniejsze niż matematyka na typach wbudowanych, ale generalnie nadal są dość szybkie i zapewniają matematykę o dowolnej precyzji. Zaokrąglanie i inne problemy nie są automatyczne, jak w przypadku zmiennoprzecinkowych IEEE, ale zyskujesz większą kontrolę i na pewno większą dokładność.
Kluczem tutaj jest wybór na podstawie domeny problemu . Wszystkie trzy metody przedstawiania liczb mają swoje zalety i wady. Używasz licznika pętli? Wybierz typ integralny. Reprezentujesz lokalizacje w przestrzeni 3D? Prawdopodobnie grupa pływaków. Chcesz śledzić pieniądze? Użyj stałego typu dziesiętnego.
1 Najpopularniejsze architektury procesorów (np. x86-64 ) będą miały oddzielne zestawy instrukcji, które działają na różnych typach rejestrów, takich jak liczby całkowite i zmiennoprzecinkowe, a także dodatkowe instrukcje do konwersji między całkowitymi, zmiennoprzecinkowymi i różnymi ich reprezentacjami (ze znakiem i bez znaku, zmiennoprzecinkową i podwójną). Niektóre z tych operacji mogą również wiązać się z dostępem do pamięci: konwertowanie wartości i zapisywanie jej w pamięci (jej zmiennej). Matematyka na poziomie procesora nie jest tak prosta, jak „integer in, float out.„Chociaż dodanie dwóch liczb całkowitych może być bardzo prostą operacją, prawdopodobnie pojedynczą instrukcją, mieszanie typów danych może zwiększyć złożoność.
Komentarze
- Mówisz, że standard C ++ stanowi, że takie zachowanie powinno być takie. Dlaczego? Czy nie ' t byłoby łatwiej powiedzieć, ” Dzielenie liczb całkowitych, które nie są równo podzielne, daje w wyniku liczby zmiennoprzecinkowe, każdy inny podział jest uczciwą grą. ”
- @ moonman239 zobacz moje zmiany.
- @ moonman239 Nie dla twórców kompilatorów. Wiele powszechnie używanych architektur procesorów CPU dostarcza wynik w postaci liczby całkowitej, gdy jest proszony o wykonanie dzielenia za pomocą dwóch liczb całkowitych. Musieliby zaimplementować sprawdzanie wyników innych niż całkowite, a następnie przełączyć się na wolniejszy zmiennoprzecinkowy Alternatywnie, mogli domyślnie przejść na dzielenie zmiennoprzecinkowe i stracić zainteresowanie tych, którzy chcieli szybkiej matematyki, tych, którzy chcieli dokładnej matematyki i tych, którzy byli przyzwyczajeni do C. Zmiana teraz nie jest ' t opcją, ponieważ naruszyłoby to kompatybilność z istniejącym kodem.
- Nie polecałbyś tego jako alternatywy, ale uczynienie statycznego typu wyrażenia zależą od wartości czasu działania wygranych operandów ' t współpracują ze statycznym systemem typów C ++ '.
- @ moonman239: Operacja, która generuje inny typ w zależności od wartości operandów, to szaleństwo.
Odpowiedź
Wynika to z ewolucji sprzętu. We wczesnych czasach komputerów nie wszystkie maszyny miały jednostkę zmiennoprzecinkową, sprzęt po prostu nie był w stanie zrozumieć pojęcia liczby zmiennoprzecinkowej. Oczywiście liczby zmiennoprzecinkowe można zaimplementować jako abstrakcję oprogramowania, ale ma to znaczące wady. Cała arytmetyka na tych maszynach musiała domyślnie być czystą arytmetyką całkowitoliczbową.
I nadal istnieje wyraźne rozróżnienie między całkowitymi i zmiennoprzecinkowymi jednostkami arytmetycznymi wewnątrz procesora. Ich operandy są przechowywane w oddzielnych plikach rejestrowych na początku, a jednostka całkowita jest połączona tak, aby pobierać dwa argumenty liczb całkowitych i generować wynik w postaci liczby całkowitej, który kończy się w rejestrze całkowitym. Niektóre procesory wymagają nawet zapisania wartości całkowitej w pamięci, a następnie ponownego załadowania z powrotem do rejestru zmiennoprzecinkowego, zanim będzie można je przekodować na liczbę zmiennoprzecinkową, zanim będzie można wykonać na niej dzielenie zmiennoprzecinkowe.
W związku z tym decyzja podjęta przez programistów C na samym początku języka (C ++ po prostu odziedziczył to zachowanie) była jedyną właściwą decyzją i pozostaje wartościową do dziś: jeśli potrzebujesz matematyki zmiennoprzecinkowej, może go używać. Jeśli jej nie potrzebujesz, nie musisz.
Komentarze
- To smutne, że większość ograniczeń, które istniały na tworzenie standardu C ++ jest dziś dość przestarzałe! Na przykład: ” nie płacisz za to, czego nie używasz. ” w dzisiejszych czasach sprzęt jest uważany za rzecz oczywistą, a użytkownicy chcą tylko wykonanie!
- @ mahen23 Nie wszyscy użytkownicy tak myślą. Pracuję w dziedzinie, w której programy są uruchamiane równolegle na tysiącach rdzeni procesorów. W tej dziedzinie efektywność to pieniądz, zarówno pod względem inwestycji, jak i samego zużycia energii. Język taki jak Java nie ma najmniejszych szans w tej dziedzinie, podczas gdy C ++ tak.
- @ mahen23 Nie, to nie jest ' t – lub lepiej, tylko wtedy, gdy spojrzysz na obecne architektury procesorów dla komputerów stacjonarnych i nowszych. Nadal istnieje wiele systemów wbudowanych, które nie ' lub tylko częściowo obsługują operacje zmiennoprzecinkowe, a C i C ++ nadal je obsługują, aby zapewnić możliwie najbardziej wydajną implementację używając assemblera. Przy okazji, języki wyższego poziomu, takie jak Python, rozróżniają operacje na liczbach całkowitych i FP – wypróbuj
10 / 3
.
Odpowiedź
10/2 z liczbami całkowitymi daje dokładnie 5 – poprawną odpowiedź.
W przypadku matematyki zmiennoprzecinkowej, 10/2 może dać poprawną odpowiedź *.
Innymi słowy, nie jest możliwe, aby liczby zmiennoprzecinkowe były „doskonałe” na obecnym sprzęcie – tylko matematyka liczb całkowitych może być poprawna, niestety nie może robić miejsc dziesiętnych, ale jest łatwa praca arounds.
Na przykład zamiast 4/3 wykonaj (4 * 1000) / (3 * 1000) == 1333. Po prostu narysuj a. w oprogramowaniu podczas wyświetlania odpowiedzi użytkownikowi (1.333). To daje dokładną odpowiedź, a nie taką, która jest błędna o pewną liczbę miejsc po przecinku.
Błędy matematyczne zmiennoprzecinkowe mogą sumować się, powodując poważne błędy – wszystko, co ważne (np. Finanse), będzie używać matematyki całkowitej .
* przykład 10/2 będzie faktycznie poprawny z matematyką zmiennoprzecinkową, ale nie można na tym polegać, wiele innych liczb daje nieprawidłowe wyniki …aby uzyskać więcej informacji, przeczytaj: http://http.cs.berkeley.edu/~wkahan/ieee754status/ieee754.ps Chodzi o to, że nie można polegać na dokładności, gdy punkty zmiennoprzecinkowe są zaangażowany
Komentarze
- Implementacje zmiennoprzecinkowe zgodne ze standardem IEEE 754 dadzą dokładny wynik za 10/2. W rzeczywistości podają dokładny wyniki dla dowolnej operacji obejmującej tylko operandy całkowite, które mają wynik w postaci liczby całkowitej, pod warunkiem, że operandy i wynik mogą być dokładnie reprezentowane, a które „wystarczająco małe” liczby całkowite mogą.
- @ 5gon12eder there ' nie trzeba niczego wybierać, ja ' staram się po prostu opisać złożony problem w prostych słowach. Cały punkt wspierania wartości niecałkowitych polega na umieszczeniu miejsc dziesiętnych ( co można zrobić za pomocą liczb całkowitych, po prostu mnożąc wszystko przez liczbę miejsc dziesiętnych, które chcesz, tak jak pokazałem).
Odpowiedź
Chociaż technicznie nie jest to całkowicie poprawne, C ++ jest nadal uważany za nadzbiór języka C, został zainspirowany nim i jako taki przywłaszczył sobie niektóre z jego właściwości, a jedną z nich jest dzielenie liczb całkowitych.
C został w większości zaprojektowany tak, aby był wydajny i szybki, a liczby całkowite są generalnie znacznie szybciej niż zmiennoprzecinkowe, ponieważ typ liczby całkowitej jest powiązany ze sprzętem, podczas gdy zmiennoprzecinkowe muszą być obliczane.
Kiedy operand /
otrzymuje dwie liczby całkowite, jedną lewej i prawej strony, może w ogóle nie wykonywać dzielenia, wynik można obliczyć za pomocą prostego dodawania i pętli, pytając, ile razy operand po prawej stronie pasuje do operandu po lewej stronie.
//
to operator dzielenia liczby całkowitej, a#
to komentarz jednowierszowy. W Pascalu operatorem dzielenia liczb całkowitych jest słowo kluczowediv
. Oba działają całkiem dobrze w swoim języku ges. C robi prawdopodobnie najgorszą możliwą rzecz: jeden operator, który może zrobić dwie zupełnie różne rzeczy w oparciu o dowolny kontekst.