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



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



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


результаты в 0.009999999999998



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



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

590   9  

9 ответов:

потому что арифметика с плавающей точкой != арифметика действительных чисел. Иллюстрацией разницы из-за неточности является, для некоторых поплавков 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 не имеет десятичного типа данных, как другоеязыки do.

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

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

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

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

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

будет в результате 12345678901235.1234567890...

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

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

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

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

2250004.5000023

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

2250004.50000225

все зависит от точности вам нужно.

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

например,$n = '40000000002':

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

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

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

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

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

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

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

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

<?PHP

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

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

?>

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

первая строка дает мне 0.009999999999998. Второй дает мне 0.01

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

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

[решен]

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.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. Поэтому компьютер не может сохранить это число в своей памяти. Он должен жертвовать. Это приводит не точность в компьютере.

Advanced ( Вы должны иметь знания о математике ) Так почему же мы можем легко показать 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.

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

Comments

    Ничего не найдено.