PHP-zmienna precyzja liczb [duplikat]

To pytanie ma już odpowiedź tutaj:

$a = '35';
$b = '-34.99';
echo ($a + $b);

Wyniki w 0.0099999999999998

O co chodzi? Zastanawiałem się, dlaczego mój program ciągle donosił dziwne wyniki.

Dlaczego PHP nie zwraca oczekiwanego 0.01?

Author: Joe DF, 2010-09-16

9 answers

Ponieważ arytmetyka zmiennoprzecinkowa != arytmetyka liczb rzeczywistych. Ilustracją różnicy wynikającej z nieprecyzyjności jest, dla niektórych pływaków a i b, (a+b)-b != a. Dotyczy to każdego języka używającego pływaków.

Ponieważ zmiennoprzecinkowe są liczbami binarnymi o skończonej precyzji, istnieje skończona ilość liczb reprezentowalnych , co prowadzi do problemów z dokładnością i niespodzianek takich jak ta. Oto kolejna ciekawa lektura: co każdy informatyk powinien Znajomość Arytmetyki Zmiennoprzecinkowej .


Wracając do twojego problemu, zasadniczo nie ma sposobu, aby dokładnie przedstawić 34.99 lub 0.01 W binarnym (tak jak w dziesiętnym, 1/3 = 0.3333...), więc zamiast tego stosuje się przybliżenia. Aby obejść problem, możesz:

  1. Użycie round($result, 2) na wynik zaokrąglić go do 2 miejsc po przecinku.

  2. Użyj liczb całkowitych. Jeśli jest to waluta, powiedzmy dolary amerykańskie, a następnie przechowywać $35.00 jako 3500 i $34.99 jako 3499, a następnie podzielić wynik na 100.

Szkoda, że PHP nie ma dziesiętnego typu danych jak Inne języki do.

 113
Author: NullUserException,
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
2017-12-11 23:39:47

Liczby zmiennoprzecinkowe, podobnie jak wszystkie liczby, muszą być przechowywane w pamięci jako ciąg 0 i 1. Jak zmiennoprzecinkowy różni się od liczby całkowitej, to jak interpretujemy 0 i 1, gdy chcemy na nie spojrzeć.

Jeden bit to " znak "(0 = dodatni, 1 = ujemny), 8 bitów to wykładnik (w zakresie od -128 do +127), 23 bity to liczba znana jako" mantissa " (ułamek). Więc binarna reprezentacja (S1)(P8) (M23) ma wartość (-1^S)M*2^P

"Mantissa" przybiera specjalną formę. W normalnym zapisie naukowym pokazujemy "swoje miejsce" wraz z ułamkiem. Na przykład:

4.39 x 10^2 = 439

W systemie binarnym " one 'S place" jest pojedynczym bitem. Ponieważ ignorujemy wszystkie lewe-większość 0 w notacji naukowej (ignorujemy wszelkie nieistotne liczby), pierwszy bit jest gwarantowany jako 1

1.101 x 2^3 = 1101 = 13

Ponieważ mamy gwarancję, że pierwszy bit będzie 1, usuwamy ten bit podczas przechowywania numeru, aby zaoszczędzić miejsce. Tak więc powyższa liczba jest zapisana jako tylko 101 (dla mantissy). Przyjmuje się, że wiodąca 1

Jako przykład, weźmy łańcuch binarny

00000010010110000000000000000000

Rozbicie go na składniki:

Sign    Power           Mantissa
 0     00000100   10110000000000000000000
 +        +4             1.1011
 +        +4       1 + .5 + .125 + .0625
 +        +4             1.6875

Stosując nasz prosty wzór:

(-1^S)M*2^P
(-1^0)(1.6875)*2^(+4)
(1)(1.6875)*(16)
27

Innymi słowy, 0000001001011000000000000000000 to 27 w zmiennoprzecinkowych (zgodnie ze standardami IEEE-754).

Dla wielu liczb nie ma jednak dokładnej reprezentacji binarnej. Much jak 1/3 = 0,333.... powtarzając w nieskończoność, 1/100 to 0.00000010100011110101110000..... z powtórzeniem "10100011110101110000". 32-bitowy komputer nie może jednak przechowywać całej liczby w postaci zmiennoprzecinkowej. Więc najlepiej zgaduje.

0.0000001010001111010111000010100011110101110000

Sign    Power           Mantissa
 +        -7     1.01000111101011100001010
 0    -00000111   01000111101011100001010
 0     11111001   01000111101011100001010
01111100101000111101011100001010

(zauważ, że ujemna 7 jest wytwarzana przy użyciu dopełniacza 2)

Od razu powinno być jasne, że 01111100101000111100001010 nie wygląda jak 0.01

Co ważniejsze, zawiera ona jednak okrojoną wersję powtarzającego się dziesiętne. Oryginał dziesiętny zawierał powtórzenie "10100011110101110000". Uprościliśmy to do 01000111101011100001010

Tłumacząc tę liczbę zmiennoprzecinkową z powrotem na dziesiętną za pomocą naszego wzoru otrzymujemy 0.0099999979 (zauważ, że jest to dla 32-bitowego komputera. 64-bitowy komputer miałby znacznie większą dokładność)

 45
Author: stevendesu,
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
2017-02-15 02:06:42

Jest tu wiele odpowiedzi na temat tego, dlaczego liczby zmiennoprzecinkowe działają tak, jak działają...

Ale niewiele mówi się o arbitralnej precyzji (wspomniał o tym Pickle). Jeśli chcesz (lub potrzebujesz) dokładnej precyzji, jedynym sposobem, aby to zrobić (przynajmniej dla liczb wymiernych) jest użycie rozszerzenia BC Math (które jest tak naprawdę tylko BigNum, arbitralna precyzja...

Aby dodać dwie liczby:

$number = '12345678901234.1234567890';
$number2 = '1';
echo bcadd($number, $number2);

Spowoduje 12345678901235.1234567890...

To jest nazywa się arbitralną matematyką precyzyjną. Zasadniczo wszystkie liczby są ciągami, które są przetwarzane dla każdej operacji i operacje są wykonywane na zasadzie cyfra po cyfrze (myśl długi podział, ale wykonywane przez Bibliotekę). Oznacza to, że jest dość powolny (w porównaniu do zwykłych konstrukcji matematycznych). Ale jest bardzo potężny. Możesz mnożyć, dodawać, odejmować, dzielić, znajdować modulo i wykładniczo dowolną liczbę, która ma dokładną reprezentację ciągu.

Więc nie można zrobić 1/3 ze 100% dokładnością, ponieważ ma powtarzający się dziesiętny (a więc nie jest racjonalny).

Ale jeśli chcesz wiedzieć, co to jest 1500.0015 kwadrat:

Użycie pływaków 32-bitowych (Podwójna precyzja) daje szacunkowy wynik:

2250004.5000023

Ale bcmath daje dokładną odpowiedź:

2250004.50000225
Wszystko zależy od precyzji, jakiej potrzebujesz.

Również, coś innego do odnotowania tutaj. PHP może reprezentować tylko 32-bitowe lub 64-bitowe liczby całkowite(w zależności od instalacji). Więc jeśli liczba całkowita przekracza rozmiar natywnego typ int (2.1 billion for 32bit, 9.2 x10^18, or 9.2 billion billion for signed ints), PHP przekształci int w zmiennoprzecinkowy. Chociaż nie jest to od razu problemem (ponieważ wszystkie int mniejsze od precyzji pływaka systemu są z definicji bezpośrednio reprezentowane jako pływaki), jeśli spróbujesz pomnożyć dwa razem, stracisz znaczną precyzję.

Na przykład, podane $n = '40000000002':

Jako liczba, $n będzie float(40000000002), co jest w porządku, ponieważ jest dokładnie reprezentowana. Ale jeśli kwadrat, otrzymujemy: float(1.60000000016E+21)

Jako ciąg znaków (używając matematyki BC), $n będzie dokładnie '40000000002'. A jeśli to rozwiążemy, otrzymamy: string(22) "1600000000160000000004"...

Więc jeśli potrzebujesz precyzji z dużymi liczbami lub racjonalnymi punktami dziesiętnymi, możesz chcieć zajrzeć do bcmath...

 14
Author: ircmaxell,
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-10-01 18:33:54

Mój php zwraca 0.01... alt text

Może ma todo z wersją php, (ja używam 5.2)

 2
Author: Fribu - Smart Solutions,
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-09-16 12:46:03

Użyj funkcji PHP round(): http://php.net/manual/en/function.round.php

Ta odpowiedź rozwiązuje problem, ale nie wyjaśnia dlaczego. Myślałem, że to oczywiste [ja też programuję w C++, więc dla mnie to oczywiste ;]], ale jeśli nie, to powiedzmy, że PHP ma swoją własną precyzję obliczeniową i w tej konkretnej sytuacji zwraca najbardziej zgodne informacje dotyczące tego obliczenia.

 2
Author: Tomasz Kowalczyk,
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-09-16 12:58:29

Bcadd () może się tutaj przydać.

<?PHP

$a = '35';
$b = '-34.99';

echo $a + $b;
echo '<br />';
echo bcadd($a,$b,2);

?>

(nieefektywne wyjście dla jasności)

Pierwsza linia daje mi 0.00999999999999998. Druga daje mi 0.01

 2
Author: Pickle,
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-09-29 19:49:15

Ponieważ 0,01 nie może być dokładnie reprezentowane jako suma szeregu ułamków binarnych. I tak pływaki są przechowywane w pamięci.

Myślę, że to nie jest to, co chcesz usłyszeć, ale to jest odpowiedź na pytanie. Jak naprawić zobacz inne odpowiedzi.

 1
Author: Andrey,
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-09-16 12:48:24

[rozwiązane]

Tutaj wpisz opis obrazka

Każda liczba zostanie zapisana w komputerze przez wartość binarną, taką jak 0, 1. W liczbie pojedynczej precyzja zajmuje 32 bity.

Liczba zmiennoprzecinkowa może być przedstawiona przez: 1 bit dla znaku, 8 bit dla wykładnika i 23 bit o nazwie mantissa (ułamek).

Spójrz na poniższy przykład:

0.15625 = 0.00101 = 1.01*2^(-3)

Tutaj wpisz opis obrazka

  • Znak: 0 Średnia liczba dodatnia, 1 średnia ujemna liczba, w tym przypadku jest to 0.

  • Wykładnik: 01111100 = 127-3 = 124.

    Uwaga: bias = 127 tak stronniczy wykładnik = -3 + "bias". W przypadku pojedynczej precyzji, odchylenie wynosi 127 ,więc w tym przykładzie wykładnik stronniczy wynosi 124; {]}

  • W części ułamkowej mamy: 1,01 średniej: 0*2^-1 + 1*2^-2

    Liczba 1 (pierwsza pozycja z 1.01) nie musi być zapisywana, ponieważ gdy występuje liczba zmienna w ten sposób, pierwsza liczba zawsze będzie 1. Na przykład convert: 0.11 => 1.1*2^(-1), 0.01 => 1*2^(-2).

Inny przykład pokaż zawsze Usuń pierwsze zero: 0.1 zostanie wyświetlone 1*2^(-1). Pierwsze alwasy to 1. Obecna liczba 1*2^(-1) będzie:

  • 0: liczba dodatnia
  • 127-1 = 126 = 01111110
  • ułamek: 00000000000000000000000 (23 liczba)

Wreszcie: surowy binarny jest: 0 01111110 00000000000000000000000

Sprawdź tutaj: http://www.binaryconvert.com/result_float.html?decimal=048046053

Teraz, jeśli już rozumiesz, jak liczba zmiennoprzecinkowa jest zapisywana. Co się stanie, jeśli liczba nie może zapisać się w 32 bitach (prosta precyzja).

Na przykład: dziesiętne. 1/3 = 0.333333333333333333333333 a ponieważ jest nieskończony, przypuszczam, że mamy 5 bitów do zapisania danych. Powtórz jeszcze raz, to nie jest prawdziwe. tylko przypuśćmy. Tak więc dane zapisane w komputerze będą:

0.33333.

Teraz, gdy liczba załadowana komputer Oblicz ponownie:

0.33333 = 3*10^-1 + 3*10^-2 + 3*10^-3 + 3*10^-4 +  3*10^-5.

O tym:

$a = '35';
$b = '-34.99';
echo ($a + $b);

Wynik wynosi 0,01 (dziesiętny). Teraz pokażmy tę liczbę w postaci binarnej.

0.01 (decimal) = 0 10001111 01011100001010001111 (01011100001010001111)*(binary)

Sprawdź tutaj: http://www.binaryconvert.com/result_double.html?decimal=048046048049

Ponieważ (01011100001010001111) powtarza się tak samo jak 1/3. Tak więc komputer nie może zapisać tego numeru w pamięci. Musi się poświęcić. To prowadzi nie dokładność w komputerze.

Zaawansowane (Musisz mieć wiedzę o matematyka ) Więc dlaczego możemy łatwo pokazać 0.01 W dziesiętnym, ale nie w binarnym.

Załóżmy, że ułamek w dwójce równy 0,01 (dziesiętny) jest skończony.

So 0.01 = 2^x + 2^y... 2^-z
0.01 * (2^(x+y+...z)) =  (2^x + 2^y... 2^z)*(2^(x+y+...z)). This expression is true when (2^(x+y+...z)) = 100*x1. There are not integer n = x+y+...+z exists. 

=> So 0.01 (decimal) must be infine in binary.
 1
Author: christian Nguyen,
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
2016-08-01 08:09:27

Nie byłoby łatwiej używać number_format(0.009999999999998, 2) LUB $res = $a+$b; -> number_format($res, 2);?

 0
Author: Jurijs Nesterovs,
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-09-04 15:10:19