Подтвердить что ты не робот

Ошибка вычисления float PHP при вычитании

У меня очень странная проблема. Если я вычитаю 2 float vars, где один из них является результатом математической операции, я получаю неправильное значение.

Пример:

var_dump($remaining);
var_dump($this->hours_sub['personal']);
echo $remaining-$this->hours_sub['personal'];

Это результат:

float 5.4
float 1.4
5.3290705182008E-15

5.4-1.4 должно быть 4 Если я добавлю два значения, результат будет правильным.

Где моя ошибка? Это не может быть проблемой округления.

4b9b3361

Ответ 1

Если кто-то дойдет до этой страницы с аналогичными проблемами, где вычитание с плавающим числом вызывает ошибку или странные значения. Я хочу объяснить эту проблему немного подробнее.

Он напрямую не связан с PHP, и это не ошибка. Однако каждый программист должен знать об этой проблеме.

Эта проблема даже заняла много жизней два десятилетия назад.

25 февраля 1991 года эта проблема расчета плавающего числа в ракетной батарее MIM-104 Patriot помешала ей перехватить входящую ракету Scud в Дахране, Саудовская Аравия, в результате которой погибли 28 солдат из 14-го отряда отряда офицеров армии США.

Но почему это происходит?

Причина в том, что значения с плавающей запятой представляют собой ограниченную точность. Таким образом, значение может не имеют одинакового строкового представления после любой обработки. Это также включает запись значения с плавающей запятой в script и непосредственно печатайте его без каких-либо математических операций.

Простой пример:

$a = '36';
$b = '-35.99';
echo ($a + $b);

Вы ожидаете, что он напечатает 0.01, правильно? Но он напечатает очень странный ответ вроде 0.009999999999998

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

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

Десятичные числа не представлены в двоичном виде из-за недостаточного пространства. Итак, вы не можете выразить 1/3 точно так же, как 0.3333333..., правильно? Почему мы не можем представить 0.01, поскольку двоичное число с плавающей точкой по той же причине. 1/100 имеет значение 0.00000010100011110101110000..... с повторением 10100011110101110000.

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

Если вы отформатируете результат, используя

echo number_format(0.009999999999998, 2);

он напечатает 0.01.

Это потому, что в этом случае вы инструктируете, как его следует читать и как нужна точность.

Ответ 2

В дополнение к использованию number_format() существуют три способа получения правильного результата. Один из них включает в себя небольшую математику, как показано ниже:

<?php

$a = '36';
$b = '-35.99';

$a *= 100;
$b *= 100;

echo (($a + $b)/100),"\n";

См. демо

Или вы можете просто использовать printf():

<?php

$a = '36';
$b = '-35.99';

printf("\n%.2f",($a+$b));

Смотрите демо

Обратите внимание, что без спецификатора точности результат printf() будет содержать обратные нулевые десятичные знаки, как показано ниже: 0.010000

Вы также можете использовать функцию BC Math bcadd() следующим образом:

<?php
$a = '36';
$b = '-35.99';
echo "\n",bcadd($a,$b,2);

См. демо