Ошибка вычисления 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 Если я добавлю два значения, результат будет правильным.

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

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

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

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

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

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

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

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

 $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/1000.00000010100011110101110000..... с повторением 10100011110101110000 .

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

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

 echo number_format(0.009999999999998, 2); 

он будет печатать 0.01 .

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

В дополнение к использованию 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); 

См. Демонстрацию