Przykłady niedokładności zmiennoprzecinkowych

Jak wyjaśnisz nieścisłość zmiennoprzecinkową świeżym programistom i laikom, którzy nadal myślą, że komputery są nieskończenie mądre i dokładne?
Czy masz ulubiony przykład lub anegdotę, która wydaje się, że pomysł jest znacznie lepszy niż dokładne, ale suche Wyjaśnienie?
Jak to jest nauczane na zajęciach z informatyki?

Author: David Rutten, 2010-01-20

7 answers

Istnieją zasadniczo dwie główne pułapki, w których ludzie wpadają z liczbami zmiennoprzecinkowymi.

  1. Problem skali. Każda liczba FP ma wykładnik, który określa ogólną "skalę" liczby, więc można reprezentować albo naprawdę małe wartości lub naprawdę duże, choć liczba cyfr, które można poświęcić na to jest ograniczona. Dodanie dwóch liczb o różnej skali spowoduje czasami, że mniejsza zostanie "zjedzona", ponieważ nie ma sposobu, aby dopasować ją do większej skala.
    PS> $a = 1; $b = 0.0000000000000000000000001
    PS> Write-Host a=$a b=$b
    a=1 b=1E-25
    PS> $a + $b
    1
    
    Jako analogię do tego przypadku można wyobrazić sobie duży basen i łyżeczkę wody. Oba są bardzo różnych rozmiarów, ale indywidualnie można łatwo zrozumieć, ile z grubsza są. Nalewanie łyżeczki do basenu jednak pozostawi cię z grubsza z basenem pełnym wody.

    (Jeśli ludzie uczący się tego mają problemy z notacją wykładniczą, można również użyć wartości 1 i 100000000000000000000 lub tak.)

  2. Wtedy istnieje problem reprezentacji binarnej a dziesiętnej. Liczba taka jak 0.1 nie może być dokładnie reprezentowana przez ograniczoną liczbę cyfr binarnych. Niektóre języki to maskują:

    PS> "{0:N50}" -f 0.1
    0.10000000000000000000000000000000000000000000000000
    

    Ale można "wzmocnić" błąd reprezentacji przez wielokrotne dodawanie liczb razem:

    PS> $sum = 0; for ($i = 0; $i -lt 100; $i++) { $sum += 0.1 }; $sum
    9,99999999999998
    

    Nie mogę wymyślić miłej analogii, aby to właściwie wyjaśnić. To w zasadzie ten sam problem, dlaczego można reprezentować 1/3 tylko w przybliżeniu w układzie dziesiętnym, ponieważ do uzyskaj dokładną wartość, którą musisz powtórzyć 3 w nieskończoność na końcu ułamka dziesiętnego.

    Podobnie ułamki binarne są dobre do reprezentowania połówek, ćwiartek, ósemek itp. ale rzeczy takie jak dziesiąta dadzą nieskończenie powtarzający się strumień cyfr binarnych.

  3. Jest jeszcze jeden problem, chociaż większość ludzi nie natknie się na to, chyba że robią ogromne ilości numerycznych rzeczy. Ale wtedy Ci już wiedzą o problemie. Od wielu liczby zmiennoprzecinkowe są jedynie przybliżeniami dokładnej wartości, co oznacza, że dla danego przybliżenia f liczby rzeczywistej r może istnieć nieskończenie wiele więcej liczb rzeczywistych r1, r2, ... które odwzorowują dokładnie to samo przybliżenie. Te liczby leżą w określonym przedziale. Powiedzmy, że rmin jest minimalną możliwą wartością r , która daje f oraz rmax {[26] } maksymalna możliwa wartość r dla której to dotyczy, wtedy otrzymujemy interwał [r / min, rmax ] gdzie dowolna liczba w tym przedziale może być liczbą rzeczywistą r .

    Teraz, jeśli wykonasz obliczenia na tej liczbie-dodawanie, odejmowanie, mnożenie itp.- tracisz precyzję. Każda liczba jest tylko przybliżeniem, dlatego w rzeczywistości wykonujesz obliczenia z interwałami. Na wynikiem jest również interwał i błąd przybliżenia tylko zawsze staje się większy, tym samym rozszerzając interwał. Możesz odzyskać jeden numer z tej kalkulacji. Ale to tylko jedna Liczba z przedziałumożliwych wyników , biorąc pod uwagę precyzję oryginalnych operandów i stratę precyzji wynikającą z obliczeń.

    Tego typu rzeczy nazywa się arytmetyka interwałowa i przynajmniej dla mnie była to część naszego kursu matematyki na Uniwersytet.

 26
Author: Јοеу,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-20 17:00:18

Pokaż im, że system base-10 cierpi na dokładnie ten sam problem.

Spróbuj przedstawić 1/3 jako reprezentację dziesiętną w bazie 10. Nie będziesz w stanie tego zrobić dokładnie.

Więc jeśli napiszesz "0.3333", będziesz miał dość dokładną reprezentację dla wielu przypadków użycia.

Ale jeśli przeniesiesz to z powrotem do ułamka, otrzymasz "3333/10000", które jest , a nie tym samym, co"1/3".

Inne ułamki, takie jak 1/2 mogą być łatwo reprezentowane przez skończona reprezentacja dziesiętna w bazie-10: "0.5"

Teraz base-2 i base-10 borykają się zasadniczo z tym samym problemem: oba mają pewne liczby, których nie mogą dokładnie przedstawić.

Podczas gdy baza-10 nie ma problemu z reprezentowaniem 1/10 jako "0.1" w bazie-2, potrzebujesz nieskończonej reprezentacji zaczynającej się od " 0.000110011..".

 8
Author: Joachim Sauer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-20 12:23:51

Jak to się ma do wyjaśnienia laikowi? Jednym ze sposobów reprezentacji liczb jest liczenie jednostek dyskretnych. To są komputery cyfrowe. Dla liczb całkowitych, tych bez części ułamkowej, nowoczesne komputery cyfrowe liczą potęgi 2: 1, 2, 4, 8. ,,, Wartość miejsca, cyfry binarne, bla, bla, bla. Dla ułamków komputery cyfrowe liczą potęgi odwrotne dwóch: 1/2, 1/4, 1/8, ... Problem polega na tym, że wiele liczb nie może być reprezentowanych przez sumę skończonej liczby tych odwrotnych moce. Użycie większej liczby wartości miejsca (więcej bitów) zwiększy precyzję reprezentacji tych' problemowych ' liczb, ale nigdy nie uzyskamy jej dokładnie, ponieważ ma tylko ograniczoną liczbę bitów. Niektóre liczby nie mogą być reprezentowane przez nieskończoną liczbę bitów.

Drzemka...

OK, chcesz zmierzyć objętość wody w pojemniku, a masz tylko 3 miarki: pełny kubek, pół kubka i ćwierć kubka. Po policzeniu ostatniego pełnego kubka, powiedzmy, że jest jedna trzecia / align = "left" / Jednak nie możesz tego zmierzyć, ponieważ nie wypełnia żadnej kombinacji dostępnych kubków. Nie wypełnia pół filiżanki, a przelew z ćwiartki filiżanki jest zbyt mały, aby cokolwiek wypełnić. Więc masz błąd-różnica między 1/3 i 1/4. Ten błąd jest potęgowany podczas łączenia go z błędami z innych pomiarów.

 6
Author: gary,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-20 12:13:10

W Pythonie:

>>> 1.0 / 10
0.10000000000000001

Wyjaśnij, w jaki sposób niektóre ułamki nie mogą być dokładnie reprezentowane w postaci binarnej. Podobnie jak niektóre ułamki (np. 1/3) nie mogą być dokładnie reprezentowane w bazie 10.

 2
Author: codeape,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-20 10:14:48

Inny przykład, w C

printf (" %.20f \n", 3.6);

Niesamowicie daje

3.60000000000000008882

 2
Author: cibercitizen1,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-05-13 07:46:40

Oto moje proste zrozumienie.

Problem: Wartość 0.45 nie może być dokładnie reprezentowana przez zmiennoprzecinkową i jest zaokrąglana do 0.450000018. Dlaczego?

Odpowiedź: Wartość int 45 jest reprezentowana przez wartość binarną 101101. Aby zrobić wartość 0.45 byłoby dokładne, gdyby można było wziąć 45 x 10^-2 (= 45 / 10^2.) Ale to niemożliwe, ponieważ musisz użyć Bazy 2 zamiast 10.

Więc najbliżej 10^2 = 100 byłoby 128 = 2^7. Całkowita liczba bity, których potrzebujesz to 9: 6 dla wartości 45 (101101) + 3 bity dla wartości 7 (111). Następnie wartość 45 x 2^-7 = 0,3515625. Teraz masz poważny problem nieścisłości. 0.3515625 nie jest blisko 0.45.

Jak poprawić tę nieścisłość? Możemy zmienić wartość 45 i 7 na coś innego.

Może 460 x 2^-10 = 0.44921875. Używasz teraz 9 bitów dla 460 i 4 bitów dla 10. Wtedy jest trochę bliżej, ale nadal nie tak blisko. Jednak jeśli początkowa pożądana wartość było 0.44921875 wtedy otrzymałbyś dokładne dopasowanie bez przybliżenia.

Więc wzór na wartość będzie X = A x 2^B. gdzie A i B są wartościami całkowitymi dodatni lub ujemny. Oczywiście im wyższe liczby mogą być wyższe, tym większa będzie dokładność, ponieważ wiesz, że liczba bitów reprezentujących wartości A i B jest ograniczona. Dla float masz całkowitą liczbę 32. Double ma 64, a Decimal 128.

 1
Author: Jan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-16 08:01:51

Ładny kawałek numerycznej dziwności można zaobserwować, jeśli ktoś przekształci 9999999.499999999999 na float i z powrotem na double. Wynik jest zgłaszany jako 10000000, mimo że wartość ta jest oczywiście bliższa 9999999, i mimo że 9999999.499999999 poprawnie zaokrągla się do 99999999.

 0
Author: supercat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-05 03:02:02