PHP - Точность чисел с плавающей запятой [дубликат]


На этот вопрос уже есть ответ здесь:

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

Приводит к 0,009999999999998

Что с этим происходит? Я задавался вопросом, почему моя программа продолжала сообщать о странных результатах.

Почему PHP не возвращает ожидаемое значение 0.01?

Author: Joe DF, 2010-09-16

9 answers

Потому что арифметика с плавающей запятой!= арифметика действительных чисел. Иллюстрацией разницы из-за неточности является то, что для некоторых поплавков a и b, (a+b)-b != a. Это относится к любому языку, использующему поплавки.

Поскольку с плавающей запятой являются двоичными числами с конечной точностью, существует конечное количество представимых чисел , что приводит к проблемам точности и подобным сюрпризам. Вот еще одно интересное чтение: Что каждый компьютерный ученый Должен Знать Об Арифметике С Плавающей Запятой.


Возвращаясь к вашей проблеме, в принципе, нет способа точно представить 34,99 или 0,01 в двоичном формате (как и в десятичном, 1/3 = 0,3333...), поэтому вместо этого используются приближения. Чтобы обойти проблему, вы можете:

  1. Использовать round($result, 2) по результату округлить его до 2 знаков после запятой.

  2. Используйте целые числа. Если это валюта, скажем, доллары США, то храните 35,00 доллара как 3500, а 34,99 доллара как 3499, тогда разделите результат на 100.

Жаль, что PHP не имеет десятичного типа данных, как другие языки делают.

 113
Author: NullUserException, 2017-12-11 23:39:47

Числа с плавающей запятой, как и все числа, должны храниться в памяти в виде строки из 0 и 1. Все это биты для компьютера. Чем плавающая точка отличается от целого числа, так это тем, как мы интерпретируем 0 и 1, когда хотим на них взглянуть.

Один бит - это "знак" (0 = положительный, 1 = отрицательный), 8 бит - показатель степени (в диапазоне от -128 до +127), 23 бита - число, известное как "мантисса" (дробь). Таким образом, двоичное представление (S1)(P8)(M23) имеет значение (-1^С)М*2^Р

"Мантисса" принимает особую форму. В обычной научной нотации мы показываем "свое место" вместе с дробью. Например:

4,39 x 10^2 = 439

В двоичном коде "свое место" - это один бит. Поскольку мы игнорируем все самые левые 0 в научной нотации (мы игнорируем любые незначительные цифры), первый бит гарантированно будет 1

1.101 х 2^3 = 1101 = 13

Поскольку нам гарантировано, что первый бит будет равен 1, мы удаляем этот бит используется при сохранении номера для экономии места. Таким образом, указанное выше число хранится как всего 101 (для мантиссы). Предполагается, что ведущая 1

В качестве примера возьмем двоичную строку

00000010010110000000000000000000

Разбивка его на компоненты:

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

Применяя нашу простую формулу:

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

Другими словами, 00000010010110000000000000000000 равно 27 с плавающей запятой (в соответствии со стандартами IEEE-754).

Однако для многих чисел нет точного двоичного представления. Много например, как 1/3 = 0,333.... повторяясь вечно, 1/100 равно 0,00000010100011110101110000.....с повторяющимся "10100011110101110000". Однако 32-разрядный компьютер не может хранить все число с плавающей запятой. Так что он делает свое лучшее предположение.

0.0000001010001111010111000010100011110101110000

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

(обратите внимание, что отрицательный 7 получается с использованием дополнения 2)

Должно быть сразу ясно, что 01111100101000111101011100001010 выглядит совсем не так, как 0,01

Что еще более важно, однако, это содержит усеченную версию повторяющегося десятичный. Исходная десятичная дробь содержала повторяющееся "10100011110101110000". Мы упростили это до 01000111101011100001010

Переводя это число с плавающей запятой обратно в десятичное число по нашей формуле, мы получаем 0,0099999979 (обратите внимание, что это для 32-разрядного компьютера. 64-разрядный компьютер обладал бы гораздо большей точностью)

 45
Author: stevendesu, 2017-02-15 02:06:42

Здесь есть множество ответов о том, почему числа с плавающей запятой работают так, как они работают...

Но мало говорится о произвольной точности (Пикл упоминал об этом). Если вы хотите (или нуждаетесь) в точной точности, единственный способ сделать это (по крайней мере, для рациональных чисел) - использовать расширение BC Math (которое на самом деле является просто большой, произвольной точностью реализации...

Чтобы добавить два числа:

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

Приведет к 12345678901235.1234567890...

Это называется математикой произвольной точности. В основном все числа представляют собой строки, которые анализируются для каждой операции, и операции выполняются по цифрам (подумайте о делении в столбик, но выполняется библиотекой). Это означает, что он довольно медленный (по сравнению с обычными математическими конструкциями). Но он очень мощный. Вы можете умножать, складывать, вычитать, делить, находить по модулю и возведать в степень любое число, имеющее точное строковое представление.

Так что вы не можете сделать 1/3 со 100 % точностью, так как он имеет повторяющуюся десятичную дробь (и, следовательно, не является рациональным).

Но, если вы хотите знать, что такое 1500.0015 в квадрате:

Использование 32-битных поплавков (двойная точность) дает оценочный результат:

2250004.5000023

Но bcmath дает точный ответ:

2250004.50000225

Все зависит от необходимой вам точности.

Кроме того, здесь следует отметить еще кое-что. PHP может представлять только 32-разрядные или 64-разрядные целые числа (в зависимости от вашей установки). Итак, если целое число превышает размер собственного типа int (2,1 миллиарда для 32-битного, 9,2 x10^18 или 9,2 миллиарда миллиардов для подписанных int), PHP преобразует int в float. Хотя это не сразу является проблемой (поскольку все значения, меньшие, чем точность поплавка системы, по определению могут быть представлены непосредственно в виде поплавков), если вы попытаетесь умножить два вместе, это приведет к значительной потере точности.

Например, учитывая $n = '40000000002':

В качестве числа $n будет float(40000000002), что нормально, так как это точно изображенный. Но если мы выровняем его, то получим: float(1.60000000016E+21)

В виде строки (используя математику BC), $n будет точно '40000000002'. И если мы выровняем его, то получим: string(22) "1600000000160000000004"...

Поэтому, если вам нужна точность с большими числами или рациональными десятичными точками, вы можете заглянуть в bcmath...

 14
Author: ircmaxell, 2010-10-01 18:33:54

Мой php возвращает 0.01... alt text

Возможно, это нужно сделать с версией php (я использую 5.2)

 2
Author: Fribu - Smart Solutions, 2010-09-16 12:46:03

Используйте функцию PHP round(): http://php.net/manual/en/function .round.php

Этот ответ решает проблему, но не объясняет, почему. Я думал, что это очевидно [я также программирую на C++, так что для меня это очевидно;]], но если нет, предположим, что PHP обладает собственной точностью вычислений, и в этой конкретной ситуации он вернул наиболее соответствующую информацию относительно этого вычисления.

 2
Author: Tomasz Kowalczyk, 2010-09-16 12:58:29

Bcadd() может быть полезен здесь.

<?PHP

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

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

?>

(неэффективный вывод для ясности)

Первая строка дает мне 0,009999999999998. Второй дает мне 0,01

 2
Author: Pickle, 2010-09-29 19:49:15

Потому что 0,01 не может быть представлено точно как сумма рядов двоичных дробей. И именно так поплавки хранятся в памяти.

Я думаю, это не то, что вы хотите услышать, но это ответ на вопрос. О том, как это исправить, см. Другие ответы.

 1
Author: Andrey, 2010-09-16 12:48:24

[ Решено]

enter image description here

Каждое число будет сохранено в компьютере двоичным значением, таким как 0, 1. Числа с одинарной точностью занимают 32 бита.

Число с плавающей запятой может быть представлено: 1 бит для знака, 8 бит для показателя степени и 23 бита, называемых мантиссой (дробью).

Посмотрите пример ниже:

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

enter image description here

  • Знак: 0 означает положительное число, 1 означает отрицательное число, в данном случае это 0.

  • Показатель степени: 01111100 = 127 - 3 = 124.

    Примечание: смещение = 127, так что показатель смещения = -3 + "смещение". В одинарной точности смещение равно 127, поэтому в этом примере показатель смещения равен 124;

  • В части дроби мы имеем: 1,01 среднее: 0*2^-1 + 1*2^-2

    Число 1 (первая позиция 1.01) сохранять не нужно, потому что при таком представлении плавающего числа первое число всегда равно 1. Например, преобразовать: 0,11 => 1.1*2^(-1), 0.01 => 1*2^(-2).

Другой пример показывает, что всегда удаляйте первый ноль: 0.1 будет представлено 1*2^(-1). Так что первым всегда будет 1. Текущее число 1*2^(-1) будет равно:

  • 0: положительное число
  • 127-1 = 126 = 01111110
  • дробь: 00000000000000000000000 (число 23)

Наконец: Необработанный двоичный файл: 0 01111110 00000000000000000000000

Проверьте это здесь: http://www.binaryconvert.com/result_float.html?decimal=048046053

Теперь, если вы уже понимаете, как сохраняются числа с плавающей запятой. Что произойдет, если число не может быть сохранено в 32 битах (простая точность).

Например: в десятичной системе счисления. 1/3 = 0,33333333333333333333333333 и, поскольку он бесконечен, я полагаю, что у нас есть 5 бит для сохранения данных. Повторяю еще раз, это нереально. просто предположим. Таким образом, данные, сохраненные в компьютере, будут следующими:

0.33333.

Теперь, когда номер загрузил компьютер снова вычисляет:

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

Об этом:

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

Результат равен 0,01 (десятичный). Теперь давайте покажем это число в двоичном формате.

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

Проверьте здесь: http://www.binaryconvert.com/result_double.html?decimal=048046048049

Потому что (01011100001010001111) повторяется так же, как 1/3. Таким образом, компьютер не может сохранить это число в своей памяти. Он должен принести жертву. Это приводит не к точности в компьютере.

Продвинутый (Вы должны обладать знаниями о математика) Так почему же мы можем легко показать 0,01 в десятичной системе счисления, но не в двоичной.

Предположим, что двоичная дробь 0,01 (десятичная) конечна.

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, 2016-08-01 08:09:27

Не было бы проще использовать number_format(0.009999999999998, 2) или $res = $a+$b; -> number_format($res, 2);?

 0
Author: Jurijs Nesterovs, 2013-09-04 15:10:19