Oto, co dzieje się w systemie binarnym. Jak wiemy, niektórych wartości zmiennoprzecinkowych nie można przedstawić dokładnie w postaci binarnej, nawet jeśli można je przedstawić dokładnie w postaci dziesiętnej. Te 3 liczby są tylko przykładami tego faktu.
Za pomocą tego programu wypisuję szesnastkową reprezentację każdej liczby i wyniki każdego dodania.
public class Main{
public static void main(String args[]) {
double x = 23.53; // Inexact representation
double y = 5.88; // Inexact representation
double z = 17.64; // Inexact representation
double s = 47.05; // What math tells us the sum should be; still inexact
printValueAndInHex(x);
printValueAndInHex(y);
printValueAndInHex(z);
printValueAndInHex(s);
System.out.println("--------");
double t1 = x + y;
printValueAndInHex(t1);
t1 = t1 + z;
printValueAndInHex(t1);
System.out.println("--------");
double t2 = x + z;
printValueAndInHex(t2);
t2 = t2 + y;
printValueAndInHex(t2);
}
private static void printValueAndInHex(double d)
{
System.out.println(Long.toHexString(Double.doubleToLongBits(d)) + ": " + d);
}
}
Ta printValueAndInHex
metoda to tylko pomocnik drukarki szesnastkowej.
Dane wyjściowe są następujące:
403787ae147ae148: 23.53
4017851eb851eb85: 5.88
4031a3d70a3d70a4: 17.64
4047866666666666: 47.05
--------
403d68f5c28f5c29: 29.41
4047866666666666: 47.05
--------
404495c28f5c28f6: 41.17
4047866666666667: 47.050000000000004
Pierwsze 4 numery są x
, y
, z
i s
„s reprezentacje szesnastkowym. W reprezentacji zmiennoprzecinkowej IEEE bity 2-12 reprezentują wykładnik binarny , to znaczy skalę liczby. (Pierwszy bit jest bitem znaku, a pozostałe bity mantysy .) Przedstawiony wykładnik to tak naprawdę liczba binarna minus 1023.
Wykładniki dla pierwszych 4 liczb są wyodrębniane:
sign|exponent
403 => 0|100 0000 0011| => 1027 - 1023 = 4
401 => 0|100 0000 0001| => 1025 - 1023 = 2
403 => 0|100 0000 0011| => 1027 - 1023 = 4
404 => 0|100 0000 0100| => 1028 - 1023 = 5
Pierwszy zestaw dodatków
Druga liczba ( y
) ma mniejszą wartość. Podczas dodawania tych dwóch liczb w celu uzyskania x + y
, ostatnie 2 bity drugiej liczby ( 01
) są przesuwane poza zakres i nie są uwzględniane w obliczeniach.
Drugi dodatek dodaje x + y
i z
dodaje dwie liczby w tej samej skali.
Drugi zestaw dodatków
Tutaj x + z
pojawia się pierwszy. Są tej samej skali, ale dają liczbę wyższą w skali:
404 => 0|100 0000 0100| => 1028 - 1023 = 5
Drugi dodatek dodaje x + z
i y
, a teraz 3 bity są usuwane, y
aby dodać liczby ( 101
). Tutaj musi być zaokrąglenie w górę, ponieważ wynikiem jest następna liczba zmiennoprzecinkowa w górę: 4047866666666666
dla pierwszego zestawu dodatków vs. 4047866666666667
dla drugiego zestawu dodatków. Ten błąd jest wystarczająco znaczący, aby pokazać go na wydruku sumy.
Podsumowując, zachowaj ostrożność podczas wykonywania operacji matematycznych na liczbach IEEE. Niektóre reprezentacje są niedokładne i stają się jeszcze bardziej niedokładne, gdy skale są różne. Dodaj i odejmij liczby o podobnej skali, jeśli możesz.
(2.0^53 + 1) - 1 == 2.0^53 - 1 != 2^53 == 2^53 + (1 - 1)
.). Dlatego tak: bądź ostrożny przy wyborze kolejności sum i innych operacji. Niektóre języki mają wbudowane funkcje do wykonywania sum o „wysokiej precyzji” (np. Pytonymath.fsum
), więc możesz rozważyć użycie tych funkcji zamiast naiwnego algorytmu sumowania.