5

Wczoraj zapytałem o question o to, dlaczego traciłem dokładność w arytmetyce zmiennoprzecinkowej. Otrzymałem odpowiedź na pytanie, jak to było z powodu wyników pośrednich przechowywanych w rejestrach x87. To było pomocne, ale niektóre szczegóły wciąż mi uciekają. Oto odmiana programu, który przedstawiłem w poprzednim pytaniu, używam VC++ 2010 Express w trybie debugowania.Dokładność zmiennoprzecinkowa ponownie

int main() 
{ 
    double x = 1.8939201459282359e-308; /* subnormal number */ 
    double tiny = 4.9406564584124654e-324; /* smallest IEEE double */ 
    double scale = 1.6; 
    double temp = scale*tiny; 
    printf("%23.16e\n", x + temp); 
    printf("%23.16e\n", x + scale*tiny); 
} 

Urządzenie wysyła

1.8939201459282369e-308 
1.8939201459282364e-308 

pierwsza wartość jest poprawna zgodnie ze standardem IEEE. Nadanie zmiennej scale wartości 2.0 daje poprawną wartość dla obu obliczeń. Rozumiem, że temp w pierwszym obliczeniu jest wartością podnormalną i dlatego traci precyzję. Rozumiem również, że wartość scale*tiny jest przechowywana w rejestrze x87, który ma większy zakres wykładniczy, a więc ta wartość ma większą dokładność niż temp. Czego nie rozumiem, gdy dodajemy wartość do x, otrzymujemy poprawną odpowiedź z niższej dokładności. Z pewnością, jeśli niższa dokładność może dać poprawną odpowiedź, to wyższa dokładność powinna również dać poprawną odpowiedź? Czy ma to coś wspólnego z "podwójnym zaokrągleniem"?

Z góry dziękuję, to dla mnie zupełnie nowy temat, więc trochę się zmagam.

+0

Następujące może być prawdą, ale nie jest to dla mnie oczywiste: * Oczywiście, jeśli niższa dokładność może dać poprawną odpowiedź, to wyższa dokładność powinna również dać poprawną odpowiedź? * – NPE

+0

Gdybym był tobą , W takich obliczeniach użyłbym 'long double' ... –

+0

Skąd wiemy, że niższa liczba precyzji nie ma losowej wartości w ostatniej cyfrze? Zawsze istnieje 10% szans na trafienie spodziewanego. –

Odpowiedz

7

Chodzi o to, że ze względu na większy zakres wykładników, dwie liczby nie są podnormalne w reprezentacji x87.

W reprezentacji IEEE 754,

x = 0.d9e66553db96f × 2^(-1022) 
tiny = 0.0000000000001 × 2^(-1022) 

ale w reprezentacji x87,

x = 1.b3cccaa7b72de × 2^(-1023) 
tiny = 1.0000000000000 × 2^(-1074) 

Teraz, kiedy 1.6*tiny jest obliczana w reprezentacji IEEE 754, jest ona zaokrąglana do 0.0000000000002 × 2^(-1022) ponieważ jest najbliżej reprezentowalna liczba do wyniku matematycznego. Dodać, że w celu x skutkuje

0.d9e66553db96f × 2^(-1022) 
+ 0.0000000000002 × 2^(-1022) 
----------------------------- 
    0.d9e66553db971 × 2^(-1022) 

Jednak w reprezentacji X87, 1.6*tiny się

1.999999999999a × 2^(-1074) 

i kiedy to dodano

1.b3cccaa7b72de × 2^(-1023) 
+ 0.0000000000003333333333334 × 2^(-1023) 
----------------------------------------- 
    1.b3cccaa7b72e1333333333334 × 2^(-1023) 

wynik zaokrągloną do 53 znaczących bitów jest

1.b3cccaa7b72e1 × 2^(-1023) 

z ostatnim bitem w znaczniku i 1. Jeśli jest on następnie konwertowany na reprezentację IEEE754 (gdzie może mieć najwyżej 52 bity w znaczeniu i dlatego, że jest to liczba podnormalna), ponieważ jest dokładnie w połowie między dwoma sąsiednimi reprezentowalnymi liczbami 0.d9e66553db970 × 2^(-1022) i 0.d9e66553db971 × 2^(-1022) jest on domyślnie zaokrąglony do tego z ostatnim bitem w znaczeniu i zerem.

Należy zauważyć, że jeśli jednostka FPU nie była skonfigurowana do użycia tylko 53 bitów dla znacznika, ale pełna 64 typu rozszerzonej precyzji x87, wynik dodania byłby bliższy wynikowi IEEE754 0.d9e66553db971 × 2^(-1022), a zatem zaokrąglony do tego .

Skutecznie, ponieważ reprezentacja x87 ma większy zakres wykładniczy, masz więcej bitów dla znaczeń liczb podnormalnych IEEE754 niż w reprezentacji IEEE754 nawet z ograniczoną liczbą bitów w significand. Wynik obliczeń ma tu jeszcze jeden znaczący bit w x87 niż w IEEE754.

+0

Dzięki Daniel, przykład pracy był ** naprawdę ** to, czego potrzebowałem. Więc kiedy 1.b3cccaa7b72e1 × 2^(- 1023) zostanie przekonwertowany z powrotem na IEEE-754, zostanie zaokrąglone w dół do 0.d9e66553db970 × 2^(- 1022) zamiast do 0.d9e66553db971 × 2^(- 1022)? Jaki jest ogólnie tryb zaokrąglania dla tej operacji? – john

+0

Dobrze. (Chociaż nie wiem, czy jest on zaokrąglony do IEEE754 dla 'printf' w ogóle,' printf' może również użyć reprezentacji x87.) Domyślnym trybem zaokrąglania w IEEE754 jest round-ties-to-even, czyli ostatni bit znaczenia i zero. –

+1

Witaj Daniel, drobna uwaga: sposób opisywania dodatku w x87, w pobliżu "z powodu ograniczenia znaczących bitów, staje się 0.0000000000003 × 2^(- 1023)" brzmi jak dodatek Cray (http: //cs.nyu .edu/courses/fall03/G22.2420-001/lec4.pdf). To, co zamiast tego robi x87, jest konceptualnie równoważne obliczeniu dokładnej sumy (1.b3cccaa7b72e1333333333334 × 2^(- 1023)), a następnie zaokrągleniu. –