9

mam tę prostą linię kodu:Dlaczego zmienna float zapisuje wartość przez cięcie cyfr po punkcie w dziwny sposób?

float val = 123456.123456; 

podczas drukowania to Val lub spojrzeć w zakresie, przechowuje wartość 123456,13

Ok, w porządku, to nie może przechowywać te wszystkie cyfry po prostu w 4 bajtach, ale dlaczego robi 13 po punkcie? Czy nie powinno być 12?

(przy użyciu VC++ 2010 Express na win32)

+0

dziękuję wszystkim za odpowiedzi. mam to teraz. Po prostu powinien za to kropić ponad liczbę cyfr, zamiast zaokrąglać go. – Kosmos

Odpowiedz

7

Przedstawiony jako liczba zmiennoprzecinkowa ma wykładnik równy 16 (tj. Wartość to czasy manekinów 2^16 lub 65536). Mantisse staje

123456.123456/65536 = 1.8837909462890625 

Aby zmieścić się w 32-bitowym pływak The mantisse jest obcinana do 23 bitów, to teraz staje 1.883791. Po pomnożeniu z powrotem przez 65536 staje się 123456.125.

Zauważ 5 na trzeciej pozycji po przecinku dziesiętnym: procedura wyjściowa C++, której użyłeś, zaokrągla ją, sprawiając, że końcowa liczba wygląda jak 123456.13.

EDIT Objaśnienie zaokrąglenia (uwaga Rick Regan)

Zaokrąglenie nastąpi pierwsze binarnie (24 bitów), dziesiętnie konwersji binarnej, a następnie dziesiętnym, printf. Zapisana wartość to 1,1110001001000000001 x 2^16 = 1,8837909698486328125 x 2^16 = 123456.125. Wydrukowany jest jako 123456.13, ale tylko dlatego, że Visual C++ używa zaokrągleń "round half away from zero".

Rick ma także outstanding article on the subject.

Jeśli chciałbyś grać z innymi liczbami i ich reprezentacjami float, tutaj jest very useful IEEE-754 calculator.

+0

Czy mógłbyś wyjaśnić nieco więcej .. Nie mogłem zrozumieć szczególnie tej linii "Kiedy jest reprezentowana jako liczba zmiennoprzecinkowa, twój numer ma wykładnik 16" . Dlaczego jest potrzebny? –

+0

@RasmiRanjanNayak 32-bitowy float jest reprezentowany jako 23-bitowy mantisse, siedmio-bitowy wykładnik binarny i bit znaku. Logicznie dzielisz pierwotną liczbę przez dwa i zwiększasz wykładnik, aż jedyna pozostała cyfra przed kropką dziesiętną to "1". Dla '123456.123456' wymagane jest 16 podziałów. – dasblinkenlight

+3

@dasblinkenlight W najlepszym razie opis ten wprowadza w błąd. Zaokrąglenie występuje najpierw w binarnym (do 24 bitów), w konwersji dziesiętnej na binarną, a następnie w systemie dziesiętnym, w printf. Zapisana wartość to 1,1110001001000000001 x 2^16 = 1,8837909698486328125 x 2^16 = 123456.125. Wypisuje się jako 123456.13, ale tylko dlatego, że Visual C++ używa "zaokrąglania połowy do zera" (zobacz mój artykuł http://www.exploringbinary.com/inconsistent-rounding-ofprinted-floating-point-numbers/.) –

2

Spróbuj wydrukować wartość std::numeric_limits<float>::digits10. Z grubsza chodzi o to, ile precyzji w base 10 ma float. Próbujesz go przekroczyć, więc doświadczasz utraty precyzji (co oznacza, że ​​cyfry poza znaczącymi nie są naprawdę znaczące).

Zobacz np. What is the meaning of numeric_limits<double>::digits10

2

To jest całkowicie zależne od kompilatora. Sprawdź to w GCC. Powinno to być xxx.12

+5

Jeśli format używany wewnętrznie to IEEE-754, nie powinien on w ogóle być zależny od kompilatora. –

+0

Sprawdź http://steve.hollasch.net/cgindex/coding/ieeefloat.html –

+0

Myślę, że podajemy "zależne od kompilatora" dwa różne znaczenia; co ja - i inni - rozumiemy, że mówisz, że "to normalne, że różni się od kompilatora do kompilatora". Zamiast tego mówisz, że jest to * błąd specyficzny dla kompilatora *? –

8

Wartość przechowywana w val jest równa 123456.125. Otrzymujesz .13 ponieważ są zaokrąglenia go:

float val = 123456.123456; 
printf("%.4f %.2f\n", val, val); 

wyjściowa: 123456.1250 123456.13

należy użyć dwukrotnie w tym przypadku, aby uniknąć obcięcia. Kompilator powinien również ostrzec: "ostrzeżenie C4305:" inicjowanie ": obcięcie z" podwójnego "do" zmiennego "".

10

W trybie binarnym 123456.123456 to 11110001001000000.00.0011111001 ... (nieskończony). To zaokrągla do 11110001001000000.001 lub 123456.125. To zaokrągla się do 123456.13 po wydrukowaniu.

Powiązane problemy