PHP - плавающая точность чисел

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

Результаты в 0,009999999999998

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

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

Ответ 1

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

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


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

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

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

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

Ответ 2

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

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

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

4.39 x 10 ^ 2 = 439

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

1,101 x 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,3333.... повторяется навсегда, 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-разрядный компьютер будет иметь гораздо большую точность)

Ответ 3

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

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

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

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

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

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

Таким образом, вы не можете сделать 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. Хотя это не сразу проблема (так как все ints, меньшие, чем точность системного float, по определению непосредственно представляются как float), если вы попытаетесь умножить два вместе, это потеряет значительную точность.

Например, данный $n = '40000000002':

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

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

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

Ответ 4

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

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

Ответ 5

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

возможно, у него есть todo с php-версией, (я использую 5.2)

Ответ 6

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

<?PHP

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

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

?>

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

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

Ответ 7

Потому что 0.01 нельзя представить точно как сумму рядов двоичных дробей. Вот как хранятся в памяти поплавки.

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

Ответ 8

[Решено]

введите описание изображения здесь

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

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

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

0,155625 = 0,00101 = 1,01 * 2 ^ (- 3)

введите описание изображения здесь

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

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

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

  • В части фракции мы имеем: 1.01 среднее: 0 * 2 ^ -1 + 1 * 2 ^ -2

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

В другом примере всегда удаляем первый ноль: 0,1 будет отображаться 1 * 2 ^ (- 1). Итак, первым alwasy будет 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,33333333333333333333333, и поскольку он бесконечен, я полагаю, у нас есть 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.

Ответ 9

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