Pytanie Czy matematyka zmiennoprzecinkowa jest zepsuta?


Rozważ następujący kod:

0.1 + 0.2 == 0.3  ->  false
0.1 + 0.2         ->  0.30000000000000004

Dlaczego występują te nieścisłości?


2268
2018-02-25 21:39


pochodzenie


Zmienne zmiennoprzecinkowe zwykle mają to zachowanie. Jest to spowodowane tym, jak są przechowywane w sprzęcie. Aby uzyskać więcej informacji, sprawdź Artykuł Wikipedii o liczbach zmiennoprzecinkowych. - Ben S
JavaScript traktuje dziesiętne jako Liczb zmiennoprzecinkowych, co oznacza, że ​​operacje takie jak dodanie mogą podlegać błędowi zaokrąglenia. Możesz rzucić okiem na ten artykuł: Co każdy informatyk powinien wiedzieć o arytmetyki zmiennoprzecinkowej - matt b
Tylko dla informacji, WSZYSTKIE typy liczbowe w javascript to IEEE-754 Doubles. - Gary Willoughby
@Gary True, chociaż masz gwarancję doskonałej dokładności całkowitej dla liczb całkowitych do 15 cyfr, patrz hunlock.com/blogs/The_Complete_Javascript_Number_Reference - Ender
Ponieważ JavaScript używa standardu IEEE 754 dla matematyki, korzysta z 64-bitowy liczby pływające. Powoduje to błędy precyzji podczas wykonywania obliczeń zmiennoprzecinkowych (dziesiętnych), w skrócie, z powodu pracy komputerów Podstawa 2 natomiast dziesiętny jest Podstawa 10. - Pardeep Jain


Odpowiedzi:


Dwójkowy zmiennoprzecinkowy matematyka jest taka. W większości języków programowania jest oparty na Standard IEEE 754. JavaScript wykorzystuje 64-bitową reprezentację zmiennoprzecinkową, która jest taka sama jak Java double. Sedno problemu polega na tym, że liczby są przedstawiane w tym formacie jako liczba całkowita razy większa niż potęga dwóch; liczby wymierne (np 0.1, który jest 1/10), którego mianownik nie jest potęgą dwojga, nie może być dokładnie odwzorowany.

Dla 0.1 w standardzie binary64 format, reprezentacja może być zapisana dokładnie jako

  • 0.1000000000000000055511151231257827021181583404541015625 w postaci dziesiętnej, lub
  • 0x1.999999999999ap-4 w Notacja heksfloatowa C99.

W przeciwieństwie do liczby wymiernej 0.1, który jest 1/10, można zapisać dokładnie tak, jak

  • 0.1 w postaci dziesiętnej, lub
  • 0x1.99999999999999...p-4 w analogie z notacją heksfloatową C99, gdzie ... reprezentuje niekończącą się sekwencję liczb 9.

Stałe 0.2 i 0.3 w twoim programie będą również przybliżeniami do ich prawdziwych wartości. Zdarza się, że najbliższy double do 0.2 jest większy niż liczba wymierna 0.2 ale to najbliżej double do 0.3 jest mniejsza niż liczba wymierna 0.3. Suma 0.1 i 0.2 wije się większy niż liczba wymierna 0.3 a zatem nie zgadzam się ze stałą w twoim kodzie.

Dość kompleksowe podejście do zagadnień arytmetycznych zmiennoprzecinkowych jest Co każdy informatyk powinien wiedzieć o arytmetyki zmiennoprzecinkowej. Aby uzyskać łatwiejsze do zrozumienia wyjaśnienie, zobacz floating-point-gui.de.


1723
2018-04-18 11:52



"Pewna stała błędu" znana również jako wartość Epsilon. - Gary Willoughby
Myślę, że "pewna stała błędu" jest bardziej poprawna niż "Epsilon", ponieważ nie ma "Epsilonu", który mógłby być użyty we wszystkich przypadkach. Różne epsilony muszą być używane w różnych sytuacjach. A maszyna epsilon prawie nigdy nie jest dobra. - Rotsor
To nie jest całkiem prawda, że ​​cała matematyka zmiennoprzecinkowa opiera się na standardzie IEEE [754]. Wciąż są niektóre używane systemy, które mają na przykład stary szesnastkowy system IBM FP, a wciąż istnieją karty graficzne, które nie obsługują arytmetyki IEEE-754. Jest to jednak zgodne z rozsądnym przybliżeniem. - Stephen Canon
Cray porzucił zgodność ze standardem IEEE-754 w zakresie prędkości. Java rozluźnił także swoją przynależność jako optymalizację. - Art Taylor
Myślę, że powinieneś dodać coś do tej odpowiedzi na temat tego, jak powinny zawsze być obliczenia na pieniądze, zawsze robić to z arytmetyką stałoprzecinkową na liczby całkowite, ponieważ pieniądze są skwantyzowane. (Może być sens dokonywania wewnętrznych obliczeń księgowych w małych ułamkach centa, czy jakakolwiek jest twoja najmniejsza jednostka walutowa - często pomaga to np. W zmniejszeniu błędu zaokrąglania przy przeliczaniu "29,99 USD miesięcznie" na stawkę dzienną - ale powinno nadal arytmetyka stałoprzecinkowa.) - zwol


Perspektywa projektanta sprzętu komputerowego

Uważam, że powinienem dodać do tego perspektywę projektanta sprzętu, ponieważ projektuję i buduję sprzęt zmiennoprzecinkowy. Znajomość pochodzenia błędu może pomóc w zrozumieniu tego, co dzieje się w oprogramowaniu, i ostatecznie mam nadzieję, że pomoże to wyjaśnić przyczyny, dla których błędy zmiennoprzecinkowe zdarzają się i zdają się kumulować w czasie.

1. Przegląd

Z perspektywy inżynierskiej większość operacji zmiennoprzecinkowych będzie miała pewien element błędu, ponieważ sprzęt, który wykonuje obliczenia zmiennoprzecinkowe, musi mieć tylko błąd mniejszy niż połowa jednostki w ostatnim miejscu. W związku z tym wiele urządzeń zatrzyma się z dokładnością, która jest konieczna tylko do uzyskania błędu mniejszego niż połowa jednostki w ostatnim miejscu pojedyncza operacja co jest szczególnie problematyczne w dzieleniu zmiennoprzecinkowym. To, co stanowi pojedynczą operację, zależy od tego, ile operandów przyjmuje jednostka. Dla większości jest to dwa, ale niektóre jednostki przyjmują 3 lub więcej operandów. Z tego powodu nie ma gwarancji, że powtarzające się operacje spowodują pożądany błąd, ponieważ błędy sumują się w czasie.

2. Normy

Większość procesorów podąża za IEEE-754 standardowe, ale niektóre wykorzystują denormalizowane lub inne standardy . Na przykład w IEEE-754 istnieje tryb denormalizowany, który pozwala na reprezentowanie bardzo małych liczb zmiennoprzecinkowych kosztem precyzji. Poniższe omówi jednak znormalizowany tryb IEEE-754, który jest typowym trybem działania.

W standardzie IEEE-754 projektanci sprzętu mogą mieć dowolną wartość błędu / epsilon, o ile jest mniej niż połowa jednostki w ostatnim miejscu, a wynik musi wynosić mniej niż połowę jednej jednostki w ostatnim miejscu. miejsce na jedną operację. To tłumaczy, dlaczego w przypadku powtarzających się operacji błędy się sumują. Dla podwójnej precyzji IEEE-754 jest to 54. bit, ponieważ 53 bity są używane do reprezentowania części numerycznej (znormalizowanej), zwanej również mantysą, liczby zmiennoprzecinkowej (na przykład 5,3 na 5,3 e5). Następne rozdziały bardziej szczegółowo omawiają przyczyny błędów sprzętowych w różnych operacjach zmiennoprzecinkowych.

3. Przyczyna błędu zaokrąglania w podziale

Główną przyczyną błędu w dzieleniu zmiennoprzecinkowym są algorytmy podziału używane do obliczenia ilorazu. Większość systemów komputerowych oblicza podział za pomocą mnożenia przez odwrotność, głównie w Z=X/Y, Z = X * (1/Y). Podział jest obliczany iteracyjnie, tzn. Każdy cykl oblicza niektóre bity ilorazu, aż do osiągnięcia żądanej dokładności, która dla IEEE-754 jest dowolna, z błędem mniejszym niż jedna jednostka w ostatnim miejscu. Tabela odwrotności Y (1 / Y) jest znana jako ilorazowa tabela selekcji (QST) w powolnym podziale, a rozmiar w bitach tabeli doboru ilorazu jest zwykle szerokością podstawy lub liczbą bitów iloraz obliczany w każdej iteracji plus kilka bitów warty. Dla standardu IEEE-754, podwójna precyzja (64-bit), byłby to rozmiar podstawy podziału, plus kilka bitów ochronnych k, gdzie k>=2. Na przykład, typowa tabela doboru wartości dla dzielnika, który oblicza 2 bity ilorazu na raz (podstawa 4) byłaby 2+2= 4 bity (plus kilka opcjonalnych bitów).

3.1 Błąd zaokrąglania Division: Aproksymacja wzajemności

Jakie odwrotności znajdują się w tabeli doboru ilorazu zależą od metoda podziału: powolny podział, taki jak podział SRT, lub szybki podział, taki jak podział Goldschmidt; każdy wpis jest modyfikowany zgodnie z algorytmem podziału w celu uzyskania najmniejszego możliwego błędu. W każdym razie jednak wszystkie wzajemności są przybliżenia rzeczywistej wzajemności i wprowadzić pewien element błędu. Zarówno metody powolnego podziału, jak i szybkiego podziału obliczają iloraz iteracyjnie, tj. Pewna liczba bitów ilorazu jest obliczana na każdym kroku, następnie wynik jest odejmowany od dywidendy, a dzielnik powtarza kroki aż błąd jest mniejszy niż połowa jednostka w ostatnim miejscu. Metody powolnego podziału obliczają stałą liczbę cyfr ilorazu na każdym etapie i zwykle są tańsze w budowie, a szybkie metody dzielenia obliczają zmienną liczbę cyfr na krok i są zwykle droższe w budowie. Najważniejszą częścią metod podziału jest to, że większość z nich polega na wielokrotnym mnożeniu przez przybliżenie odwrotności, więc są podatne na błąd.

4. Błędy zaokrągleń w innych operacjach: Obcinanie

Inną przyczyną błędów zaokrąglania we wszystkich operacjach są różne tryby obcięcia ostatecznej odpowiedzi, na którą pozwala IEEE-754. Jest ścięty, okrągły do ​​zera, round-to-nearest (domyślnie), zaokrąglone i zaokrąglone. Wszystkie metody wprowadzają element błędu mniej niż jednej jednostki w ostatnim miejscu dla pojedynczej operacji. W miarę upływu czasu i powtarzających się operacji, obcinanie powoduje również kumulację w wyniku błędu. Ten błąd skracania jest szczególnie problematyczny w potęgowaniu, co wiąże się z pewną formą wielokrotnego mnożenia.

5. Powtórne operacje

Ponieważ sprzęt wykonujący obliczenia zmiennoprzecinkowe musi tylko uzyskać wynik z błędem mniejszym niż połowa jednostki w ostatnim miejscu dla pojedynczej operacji, błąd wzrośnie w wyniku powtarzania operacji, jeśli nie zostanie wyświetlony. To jest powód, dla którego w obliczeniach, które wymagają błędu ograniczonego, matematycy używają metod, takich jak użycie od ronda do najbliższego nawet cyfra w ostatnim miejscu IEEE-754, ponieważ z biegiem czasu błędy częściej znoszą się nawzajem, oraz Arytmetyka międzywymiarowa w połączeniu z odmianami Tryby zaokrąglania IEEE 754 przewidzieć błędy zaokrąglania i poprawić je. Z powodu niskiego błędu względnego w porównaniu do innych trybów zaokrąglania, zaokrąglanie do najbliższej cyfry parzystej (w ostatnim miejscu) jest domyślnym trybem zaokrąglania IEEE-754.

Zwróć uwagę, że domyślny tryb zaokrąglania jest zaokrąglany do najbliższego nawet cyfra w ostatnim miejscu, gwarantuje błąd mniej niż połowy jednostki w ostatnim miejscu dla jednej operacji. Samo obcięcie, zaokrąglenie i zaokrąglenie może spowodować błąd przekraczający połowę jednej jednostki na ostatnim miejscu, ale mniej niż jedną jednostkę na ostatnim miejscu, więc te tryby nie są zalecane, chyba że są używane w arytmetyce międzywymiarowej.

6. Podsumowanie

W skrócie, podstawową przyczyną błędów w operacjach zmiennoprzecinkowych jest kombinacja obcinania sprzętu i obcinania odwrotności w przypadku dzielenia. Ponieważ standard IEEE-754 wymaga tylko błędu mniejszego niż połowa jednostki w ostatnim miejscu dla pojedynczej operacji, błędy zmiennoprzecinkowe w powtarzanych operacjach będą sumować, chyba że zostaną poprawione.


490
2018-02-25 21:43



(3) jest źle. Błąd zaokrąglenia w podziale jest nie mniejszy niż jeden jednostka na ostatnim miejscu, ale najwyżej pół jednostka na ostatnim miejscu. - gnasher729
@ gnasher729 Dobry połów. Większość podstawowych operacji ma również błąd mniejszy niż 1/2 jednostki na ostatnim miejscu przy użyciu domyślnego trybu zaokrąglania IEEE. Edytował wyjaśnienie, a także zauważył, że błąd może być większy niż 1/2 jednego ulpa, ale mniejszy niż 1 ulp, jeśli użytkownik nadpisuje domyślny tryb zaokrąglania (jest to szczególnie prawdziwe w systemach wbudowanych). - KernelPanik
(1) Wskaźnik zmiennoprzecinkowy liczby nie mam błędu. Każda wartość zmiennoprzecinkowa jest dokładnie tym, czym jest. Większość (ale nie wszystkie) zmiennoprzecinkowe operacje podać niedokładne wyniki. Na przykład nie ma binarnej wartości zmiennoprzecinkowej, która jest dokładnie równa 1,0 / 10,0. Niektóre operacje (np. 1,0 + 1,0) zrobić podać dokładne wyniki z drugiej strony. - james large
"Główną przyczyną błędu w dzieleniu zmiennoprzecinkowym są algorytmy podziału używane do obliczenia ilorazu" jest a bardzo mylące rzeczy do powiedzenia. W przypadku dywizji zgodnej z IEEE-754 tylko przyczyną błędu w dzieleniu zmiennoprzecinkowym jest niemożność dokładnego odwzorowania wyniku w formacie wyniku; ten sam wynik jest obliczany niezależnie od używanego algorytmu. - Stephen Canon
@Matt Przepraszamy za spóźnioną odpowiedź. Jest to spowodowane problemami związanymi z zasobami / czasem i kompromisami. Istnieje sposób, aby zrobić długi podział / bardziej "normalny" podział, nazywa się to SRT Division z Radix Two. Jednak to wielokrotnie przesuwa i odejmuje dzielnik z dywidendy i zajmuje wiele cykli zegara, ponieważ oblicza tylko jeden bit ilorazu na cykl zegara. Używamy tabel odwrotnych, abyśmy mogli obliczyć więcej bitów ilorazu na cykl i uzyskać efektywną kompromisową wydajność / szybkość. - KernelPanik


Po konwersji .1 lub 1/10 na bazę 2 (binarne) otrzymasz powtarzający się wzorzec po przecinku dziesiętnym, tak jak próbujesz reprezentować 1/3 w bazie 10. Wartość nie jest dokładna, a zatem nie możesz tego zrobić dokładna matematyka z nim za pomocą normalnych metod zmiennoprzecinkowych.


357
2017-11-20 02:39



Świetna i krótka odpowiedź. Powtarzający się wzór wygląda jak 0.00011001100110011001100110011001100110011001100110011 ... - Konstantin Chernov
Nie wyjaśnia to, dlaczego nie jest lepszy algorytm, który nie jest konwertowany na pliki binarne na pierwszym miejscu. - Dmitri Zaitsev
Ponieważ wydajność. Używanie binarnego jest kilka tysięcy razy szybsze, ponieważ jest rodzime dla maszyny. - Joel Coehoorn
Istnieją metody, które dają dokładne wartości dziesiętne. BCD (dziesiętne kodowane binarnie) lub różne inne formy liczby dziesiętnej. Są one jednak wolniejsze (duzo wolniej) i zajmują więcej miejsca niż przy użyciu binarnego zmiennoprzecinkowego. (jako przykład, spakowany BCD przechowuje 2 cyfry po przecinku w bajcie.To 100 możliwych wartości w bajcie, które może faktycznie przechowywać 256 możliwych wartości, lub 100/256, które marnują około 60% możliwych wartości bajtu.) - Duncan C
@Jacksonkr nadal myślisz w bazie-10. Komputery są base-2. - Joel Coehoorn


Większość odpowiedzi odnosi się do tego pytania w bardzo suchych, technicznych kategoriach. Chciałbym rozwiązać ten problem w kategoriach, które normalni ludzie mogą zrozumieć.

Wyobraź sobie, że próbujesz pokroić pizze. Masz automatyczny nóż do pizzy, który może ciąć plasterki pizzy dokładnie w połowie. Może zmniejszyć o połowę całą pizzę lub może zmniejszyć o połowę istniejący kawałek, ale w każdym przypadku połówka jest zawsze dokładna.

Ta przecinarka do pizzy ma bardzo delikatne ruchy, a jeśli zaczniesz od całej pizzy, a następnie połowę, i kontynuuj połowę najmniejszego plastra za każdym razem, możesz zrobić połowę 53 razy zanim plasterek jest zbyt mały, nawet jeśli chodzi o jego wysoce precyzyjne zdolności. W tym momencie nie można już zmniejszyć o połowę tego bardzo cienkiego plasterka, ale trzeba go uwzględnić lub wykluczyć, tak jak jest.

Teraz, w jaki sposób ułożyłbyś wszystkie plasterki w taki sposób, aby dodać jedną dziesiątą (0,1) lub jedną piątą (0,2) pizzy? Naprawdę pomyśl o tym i spróbuj go rozwiązać. Możesz nawet spróbować prawdziwej pizzy, jeśli masz pod ręką mityczną precyzyjną przecinarkę do pizzy. :-)


Większość doświadczonych programistów zna oczywiście prawdziwą odpowiedź, a mianowicie, że nie ma sposobu, aby złożyć razem dokładny dziesiąta lub piąta pizzy używająca tych plasterków, bez względu na to, jak drobno je pokroisz. Możesz zrobić całkiem dobre przybliżenie, a jeśli dodasz aproksymację 0,1 z przybliżeniem 0,2, otrzymasz całkiem dobre przybliżenie 0,3, ale wciąż jest to tylko przybliżenie.

W przypadku liczb o podwójnej precyzji (która jest dokładnością, która umożliwia zmniejszenie o połowę pizzy 53 razy), liczby od razu mniejsze i większe od 0,1 wynoszą 0,09999999999999999167332731531132594682276248931884765625 i 0,1000000000000000055511151231257827021181583404541015625. Ta ostatnia jest nieco bliższa 0.1 niż poprzednia, więc parser liczbowy, biorąc pod uwagę wartość wejściową 0.1, faworyzuje tę drugą.

(Różnica między tymi dwoma liczbami jest "najmniejszym fragmentem", który musimy zdecydować, aby je zawrzeć, co wprowadza odchylenie w górę lub wykluczenie, które wprowadza odchylenie w dół. Techniczne określenie tego najmniejszego fragmentu jest ulp.)

W przypadku 0.2 liczby są takie same, po prostu przeskalowane o współczynnik 2. Znów faworyzujemy wartość nieco wyższą niż 0,2.

Zauważ, że w obu przypadkach przybliżenia dla 0.1 i 0.2 mają niewielkie odchylenie w górę. Jeśli dodamy do nich wystarczającą liczbę tych odchyleń, będą one wypychać liczbę dalej i dalej od tego, czego chcemy, i w rzeczywistości, w przypadku 0,1 + 0,2, odchylenie jest wystarczająco wysokie, aby wynikowa liczba nie była już najbliższą liczbą do 0,3.

W szczególności 0,1 + 0,2 to naprawdę 0,1000000000000000055511151231257827021181583404541015625 + 0.200000000000000011102230246251565404236316680908203125 = 0,3000000000000000444089209850062616169452667236328125, natomiast liczba najbliżej 0,3 to w rzeczywistości 0,299999999999999988897769753748434595763683319091796875.


P.S. Niektóre języki programowania umożliwiają również cięcie pizzy Podziel plasterki na dokładnie dziesiątki. Chociaż takie nożyce do pizzy są rzadkie, jeśli masz do nich dostęp, powinieneś go używać, gdy ważne jest, aby uzyskać dokładnie jedną dziesiątą lub jedną piątą kawałka.

(Oryginalnie opublikowane na Quora.)


226
2018-02-25 21:41



Zauważ, że istnieją pewne języki, które zawierają dokładną matematykę. Jednym z przykładów jest Scheme, na przykład przez GNU Guile. Widzieć draketo.de/english/exact-math-to-the-rescue - one zachowują matematykę jako ułamki i tylko dzielą się na końcu. - Arne Babenhauserheide
@FloatingRock W rzeczywistości bardzo niewiele popularnych języków programowania ma wbudowane racjonalne liczby. Arne jest Schemerem, tak jak ja, więc to są rzeczy, które nam psują się. - Chris Jester-Young
@ArneBabenhauserheide Myślę, że warto dodać, że będzie działać tylko z liczbami wymiernymi. Więc jeśli robisz matematykę z liczbami niewymiernymi, jak pi, musisz zapisać ją jako wielokrotność pi. Oczywiście dowolne obliczenia z udziałem pi nie mogą być reprezentowane jako dokładna liczba dziesiętna. - Aidiakapi
@connexo OK. Jak zaprogramowałbyś rotator pizzy, aby uzyskać 36 stopni? Co to jest 36 stopni? (Podpowiedź: jeśli potrafisz to dokładnie zdefiniować, masz również plaster-dokładnie-dziesiątą pizzę.) Innymi słowy, nie możesz faktycznie mieć 1/360 (stopień) lub 1 / 10 (36 stopni) z tylko binarnym zmiennoprzecinkowym. - Chris Jester-Young
@connexo Ponadto, "każdy idiota" nie może obracać pizzy dokładnie 36 stopni. Ludzie są zbyt podatni na błędy, aby zrobić coś tak precyzyjnego. - Chris Jester-Young


Błędy zaokrąglania punktów zmiennoprzecinkowych. 0.1 nie może być reprezentowany tak dokładnie w base-2 jak w base-10 ze względu na brak czynnika 5 primingu. Tak jak 1/3 przyjmuje nieskończoną liczbę cyfr do reprezentacji dziesiętnej, ale wynosi "0,1" w base-3, 0.1 przyjmuje nieskończoną liczbę cyfr w bazie-2, gdzie nie ma w bazie-10. A komputery nie mają nieskończonej ilości pamięci.


199
2018-04-09 12:25



komputery nie potrzebują nieskończonej ilości pamięci, aby uzyskać 0,1 + 0,2 = 0,3 prawe - Pacerier
@Pacerier Oczywiście, mogliby używać dwóch liczb całkowitych o nieograniczonej precyzji do reprezentowania ułamka, lub mogliby użyć notowania notowań. Jest to specyficzne pojęcie "binarne" lub "dziesiętne", które uniemożliwiają to - pomysł, że masz sekwencję cyfr binarnych / dziesiętnych i gdzieś tam, punkt radix. Aby uzyskać precyzyjne wyniki racjonalne, potrzebowalibyśmy lepszego formatu. - Devin Jeanpierre
@Pacerier: ani binarny, ani dziesiętny zmiennoprzecinkowy nie może dokładnie zapisać 1/3 lub 1/13. Dziesiętne typy zmiennoprzecinkowe mogą precyzyjnie reprezentować wartości w postaci M / 10 ^ E, ale są mniej precyzyjne niż binarne liczby zmiennoprzecinkowe o podobnych rozmiarach, jeśli chodzi o reprezentowanie większości innych frakcji. W wielu aplikacjach bardziej przydatna jest większa precyzja z dowolnymi ułamkami niż doskonała precyzja z kilkoma "specjalnymi". - supercat
@Pacerier Oni zrobić jeśli przechowują liczby jako binarne pływaki, na czym polegała odpowiedź. - Mark Amery
@chux: Różnica w precyzji między typami binarnymi i dziesiętnymi nie jest ogromna, ale różnica 10: 1 w przypadku najlepszego lub najgorszego przypadku w przypadku typów dziesiętnych jest znacznie większa niż różnica 2: 1 w przypadku typów binarnych. Ciekawi mnie, czy ktoś zbudował sprzęt lub oprogramowanie napisane, aby działał efektywnie na jednym z typów dziesiętnych, ponieważ żaden z nich nie wydaje się być zdolny do wydajnej implementacji sprzętu ani oprogramowania. - supercat


Oprócz innych poprawnych odpowiedzi, możesz rozważyć skalowanie swoich wartości, aby uniknąć problemów z arytmetyką zmiennoprzecinkową.

Na przykład:

var result = 1.0 + 2.0;     // result === 3.0 returns true

... zamiast:

var result = 0.1 + 0.2;     // result === 0.3 returns false

Ekspresja 0.1 + 0.2 === 0.3 zwraca false w JavaScript, ale na szczęście liczba całkowita arytmetyczna w zmiennoprzecinkach jest dokładna, więc można uniknąć dziesiętnych błędów reprezentacji przez skalowanie.

Jako praktyczny przykład, aby uniknąć problemów z punktami zmiennymi, gdzie dokładność jest najważniejsza, jest to zalecane1 do obsługi pieniędzy jako liczba całkowita reprezentująca liczbę centów: 2550 centów zamiast 25.50 dolarów.


1 Douglas Crockford: JavaScript: dobre części: Dodatek A - Okropne części (strona 105).


99
2018-02-23 17:15



Problem polega na tym, że sama konwersja jest niedokładna. 16,08 * 100 = 1607.9999999999998. Czy musimy uciekać się do dzielenia liczby i przekształcania osobno (jak w 16 * 100 + 08 = 1608)? - Jason
Rozwiązaniem tutaj jest wykonanie wszystkich obliczeń w liczbie całkowitej, a następnie podzielenie przez proporcję (w tym przypadku 100) i zaokrąglenie tylko podczas prezentacji danych. To zapewni, że twoje obliczenia będą zawsze precyzyjne. - David Granado
Wystarczy trochę nacisnąć: arytmetyczna liczba całkowita jest tylko dokładna w punkcie zmiennoprzecinkowym do punktu (kalambur przeznaczony). Jeśli liczba jest większa niż 0x1p53 (w celu użycia notacji zmiennoprzecinkowej w systemie szesnastkowym w języku Java 7 = 9007199254740992), wówczas wartość ulp wynosi 2, a więc 0x1p53 + 1 jest zaokrąglane w dół do 0x1p53 (a 0x1p53 +3 jest zaokrąglane do 0x1p53 + 4, z powodu rundy do parzystości). :-D Ale z pewnością, jeśli twoja liczba jest mniejsza niż 9 biliardów, powinieneś być w porządku. :-P - Chris Jester-Young
Jak się masz .1 + .2 pokazywać .3? - CodyBugstein
Jason, powinieneś po prostu zaokrąglić wynik (int) (16.08 * 100 + 0.5) - Mikhail Semenov


Moja odpowiedź jest dość długa, więc podzieliłem ją na trzy części. Ponieważ pytanie dotyczy matematyki zmiennoprzecinkowej, kładłem nacisk na to, co maszyna faktycznie robi. Zrobiłem również specyficzne dla podwójnej (64-bitowej) precyzji, ale argument odnosi się zarówno do arytmetyki zmiennoprzecinkowej.

Preambuła

Na IEEE 754 podwójna precyzja binarnego formatu zmiennoprzecinkowego (binary64) liczba reprezentuje numer formularza

value = (-1) ^ s * (1.m51m50... m2m1m0)2 * 2e-1023

w 64 bitach:

  • Pierwszy bit to trochę znaku: 1 jeśli liczba jest ujemna, 0 Inaczej1.
  • Kolejne 11 bitów to wykładnik potęgowy, który jest offsetowy do 1023. Innymi słowy, po odczytaniu bitów potęgowych z liczby podwójnej precyzji, należy odjąć 1023, aby uzyskać moc dwóch.
  • Pozostałe 52 bity to significand (lub mantysa). W mantysie "domniemany" 1. jest zawsze2 pominięty, ponieważ najważniejszym bitem dowolnej wartości binarnej jest 1.

1 - IEEE 754 pozwala na koncepcję a podpisane zero - +0 i -0 są traktowane inaczej: 1 / (+0) jest dodatnią nieskończonością; 1 / (-0) jest ujemną nieskończonością. Dla wartości zerowych mantysa i bity wykładnicze są zerowe. Uwaga: wartości zerowe (+0 i -0) nie są jawnie zaklasyfikowane jako denormalne2.

2 - Nie dotyczy to liczby denormalne, które mają wykładnik kompensacyjny równy zero (i domyślny 0.). Zakres denormalnych liczb podwójnych precyzji to dmin ≤ | x | ≤ dmaks, gdzie dmin (najmniejsza reprezentowana niezerowa liczba) to 2-1023 - 51 (≈ 4,94 * 10-324) i dmaks (największa liczba denormalna, dla której mantysa w całości składa się z 1s) wynosi 2-1023 + 1 - 2-1023 - 51 (≈ 2,225 * 10-308).


Zwrot podwójnej precyzji do binarnej

Istnieje wiele konwerterów online do konwersji liczby zmiennoprzecinkowej podwójnej precyzji na dwójkową (np binaryconvert.com), ale oto przykładowy kod C # do uzyskania reprezentacji IEEE 754 dla liczby podwójnej precyzji (oddzielaję trzy części dwukropkami (:):

public static string BinaryRepresentation(double value)
{
    long valueInLongType = BitConverter.DoubleToInt64Bits(value);
    string bits = Convert.ToString(valueInLongType, 2);
    string leadingZeros = new string('0', 64 - bits.Length);
    string binaryRepresentation = leadingZeros + bits;

    string sign = binaryRepresentation[0].ToString();
    string exponent = binaryRepresentation.Substring(1, 11);
    string mantissa = binaryRepresentation.Substring(12);

    return string.Format("{0}:{1}:{2}", sign, exponent, mantissa);
}

Dojście do rzeczy: oryginalne pytanie

(Przejdź na dół dla wersji TL; DR)

Cato Johnston (pytający) zapytał, dlaczego 0.1 + 0.2! = 0.3.

Napisane w trybie binarnym (z dwukropkami oddzielającymi trzy części), reprezentacje wartości IEEE 754 są następujące:

0.1 => 0:01111111011:1001100110011001100110011001100110011001100110011010
0.2 => 0:01111111100:1001100110011001100110011001100110011001100110011010

Zwróć uwagę, że mantysa składa się z powtarzających się cyfr 0011. To jest klawisz dlaczego nie ma błędów w obliczeniach - 0,1, 0,2 i 0,3 nie mogą być reprezentowane w trybie binarnym dokładnie w skończone liczba bitów binarnych większa niż 1/9, 1/3 lub 1/7 może być dokładnie odwzorowana w cyfry dziesiętne.

Konwertowanie wykładników na dziesiętne, usuwanie przesunięcia i ponowne dodawanie implikowanych 1 (w nawiasach kwadratowych), 0,1 i 0,2 to:

0.1 = 2^-4 * [1].1001100110011001100110011001100110011001100110011010
0.2 = 2^-3 * [1].1001100110011001100110011001100110011001100110011010

Aby dodać dwie liczby, wykładnik musi być taki sam, tj .:

0.1 = 2^-3 *  0.1100110011001100110011001100110011001100110011001101(0)
0.2 = 2^-3 *  1.1001100110011001100110011001100110011001100110011010
sum = 2^-3 * 10.0110011001100110011001100110011001100110011001100111

Ponieważ suma nie ma formy 2n * 1. {bbb} podnosimy wykładnik o jeden i przesuwamy po przecinku (dwójkowy) wskaż, aby uzyskać:

sum = 2^-2 * 1.0011001100110011001100110011001100110011001100110011(1)

W mantysie jest teraz 53 bity (53. jest w nawiasach kwadratowych w linii powyżej). Domyślny tryb zaokrąglania dla IEEE 754 jest "Zaokrąglaj do najbliższego"- tj. jeśli liczba x mieści się między dwiema wartościami za i bwybierana jest wartość, w której bit najmniej znaczący wynosi zero.

a = 2^-2 * 1.0011001100110011001100110011001100110011001100110011
x = 2^-2 * 1.0011001100110011001100110011001100110011001100110011(1)
b = 2^-2 * 1.0011001100110011001100110011001100110011001100110100

Zauważ, że za i b różnią się tylko ostatnim bitem; ...0011 + 1 = ...0100. W tym przypadku wartość z najmniej znaczącym bitem wynosi zero b, więc suma to:

sum = 2^-2 * 1.0011001100110011001100110011001100110011001100110100

TL; DR

Pisanie 0.1 + 0.2 w binarnej reprezentacji IEEE 754 (z dwukropkami oddzielającymi trzy części) i porównując ją z 0.3, to jest (umieściłem różne bity w nawiasach kwadratowych):

0.1 + 0.2 => 0:01111111101:0011001100110011001100110011001100110011001100110[100]
0.3       => 0:01111111101:0011001100110011001100110011001100110011001100110[011]

Przekształcone z powrotem w dziesiętne wartości te to:

0.1 + 0.2 => 0.300000000000000044408920985006...
0.3       => 0.299999999999999988897769753748...

Różnica wynosi dokładnie 2-54, czyli ~ 5.5511151231258 × 10-17 - nieistotne (dla wielu aplikacji) w porównaniu do pierwotnych wartości.

Porównanie ostatnich kilku bitów liczby zmiennoprzecinkowej jest z natury niebezpieczne, jak każdy, kto czyta słynny "Co każdy informatyk powinien wiedzieć o arytmetyki zmiennoprzecinkowej"(który obejmuje wszystkie główne części tej odpowiedzi) będzie wiedzieć.

Większość kalkulatorów używa dodatkowych straży cyfr aby obejść ten problem, czyli w jaki sposób 0.1 + 0.2 da 0.3: ostatnie kilka bitów jest zaokrąglonych.


81
2018-03-16 05:27



Moja odpowiedź została odrzucona krótko po jej opublikowaniu. Od tego czasu dokonałem wielu zmian (włączając w to wyraźne notowanie powtarzających się bitów podczas pisania 0.1 i 0.2 w binarnym, które pominąłem w oryginale). Jeśli nie zdasz sobie sprawy, że widzą to w dół, możesz przesłać mi swoją opinię, aby poprawić odpowiedź. Uważam, że moja odpowiedź dodaje coś nowego, ponieważ traktowanie sumy w IEEE 754 nie jest uwzględnione w taki sam sposób w innych odpowiedziach. Podczas gdy "Co każdy informatyk powinien wiedzieć ..." obejmuje kilka takich samych materiałów, moja odpowiedź dotyczy konkretnie w przypadku 0,1 + 0,2. - Wai Ha Lee


Liczby zmiennoprzecinkowe przechowywane w komputerze składają się z dwóch części, liczby całkowitej i wykładnika, do którego baza jest pobierana i mnożona przez część całkowitą.

Jeśli komputer pracował w bazie 10, 0.1 byłoby 1 x 10⁻¹, 0.2 byłoby 2 x 10⁻¹, i 0.3 byłoby 3 x 10⁻¹. Integer matematyczne jest łatwe i dokładne, więc dodawanie 0.1 + 0.2 z pewnością wyniknie 0.3.

Komputery zazwyczaj nie działają w bazie 10, działają w bazie 2. Na przykład nadal można uzyskać dokładne wyniki dla niektórych wartości 0.5 jest 1 x 2⁻¹ i 0.25 jest 1 x 2⁻², a dodanie ich spowoduje 3 x 2⁻², lub 0.75. Dokładnie.

Problem dotyczy liczb, które można dokładnie przedstawić w bazie 10, ale nie w bazie 2. Liczby należy zaokrąglić do najbliższego odpowiednika. Przyjmując bardzo powszechny 64-bitowy format zmiennoprzecinkowy IEEE, najbliższy numer do 0.1 jest 3602879701896397 x 2⁻⁵⁵i najbliższy numer 0.2 jest 7205759403792794 x 2⁻⁵⁵; dodanie ich razem skutkuje 10808639105689191 x 2⁻⁵⁵lub dokładną wartość dziesiętną 0.3000000000000000444089209850062616169452667236328125. Liczby zmiennoprzecinkowe są zwykle zaokrąglane w celu wyświetlenia.


48
2018-02-25 21:42



@Mark Dziękuję za to jasne wyjaśnienie, ale wtedy pojawia się pytanie, dlaczego 0,1 + 0,4 dokładnie sumuje się do 0,5 (co najmniej w Pythonie 3). Jaki jest najlepszy sposób sprawdzenia równości podczas używania floats w Pythonie 3? - pchegoor
@ user2417881 Operacje zmiennoprzecinkowe IEEE mają reguły zaokrąglania dla każdej operacji, a czasami zaokrąglenie może dać dokładną odpowiedź nawet wtedy, gdy te dwie liczby są wyłączone. Szczegóły są za długie na komentarz, a ja i tak nie jestem ekspertem. Jak widać w tej odpowiedzi 0.5 jest jednym z niewielu miejsc dziesiętnych, które można przedstawić w postaci binarnej, ale to tylko zbieg okoliczności. Aby sprawdzić równość, patrz stackoverflow.com/questions/5595425/.... - Mark Ransom
@ user2417881 Twoje pytanie zaintrygowało mnie, więc zmieniłem je w pełne pytanie i odpowiedź: stackoverflow.com/q/48374522/5987 - Mark Ransom


Błąd zaokrąglania punktu swobodnego. Od Co każdy informatyk powinien wiedzieć o arytmetyki zmiennoprzecinkowej:

Ściskanie nieskończenie wielu liczb rzeczywistych w skończoną liczbę bitów wymaga przybliżonej reprezentacji. Chociaż istnieje nieskończenie wiele liczb całkowitych, w większości programów wynik obliczeń całkowitych może być przechowywany w 32 bitach. W przeciwieństwie do tego, biorąc pod uwagę dowolną ustaloną liczbę bitów, większość obliczeń z liczbami rzeczywistymi wytworzy wielkości, które nie mogą być dokładnie odwzorowane za pomocą wielu bitów. Dlatego wynik obliczeń zmiennoprzecinkowych musi być często zaokrąglany, aby dopasować się do jego skończonej reprezentacji. Ten błąd zaokrąglenia jest charakterystyczną cechą obliczeń zmiennoprzecinkowych.


40
2017-12-26 06:51





Moje obejście:

function add(a, b, precision) {
    var x = Math.pow(10, precision || 2);
    return (Math.round(a * x) + Math.round(b * x)) / x;
}

precyzja odnosi się do liczby cyfr, które chcesz zachować po przecinku podczas dodawania.


29
2017-10-05 18:39





Wiele dobrych odpowiedzi zostało opublikowanych, ale chciałbym dodać jeszcze jeden.

Nie wszystkie liczby mogą być reprezentowane przez pływa/debel Na przykład liczba "0,2" będzie reprezentowana jako "0.200000003" w pojedynczej precyzji w standardzie float IEEE754.

Model dla rzeczywistych numerów sklepów pod maską reprezentuje liczby zmiennoprzecinkowe jako

enter image description here

Nawet jeśli możesz pisać 0.2 z łatwością, FLT_RADIX i DBL_RADIX wynosi 2; nie 10 dla komputera z FPU, który używa "standardu IEEE dla binarnej zmiennoprzecinkowej arytmetyki (ISO / IEEE Std 754-1985)".

Więc trochę trudno jest dokładnie reprezentować takie liczby. Nawet jeśli wyraźnie określisz tę zmienną bez obliczania pośredniego.


26
2018-02-02 23:49