2013-06-20 3 views
11

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

Пример:

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

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

+1

Он [работает отлично] (http://ideone.com/rgr6h6), может быть, вы должны попробовать ['bcsub()'] (http://php.net/bcsub) – HamZa

+0

Почему «Это не может быть проблемой округления»? –

+1

@PatriciaShanahan Поскольку проблема округления не привела к значению, близкому к 0 для разности двух приближений к 5.4 и 1.4 соответственно. «Вопрос округления» создаст «3.999 ... 9xyz» или «4.000 ... 0xyz'. –

ответ

40

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

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

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

25 февраля 1991 года эта проблема расчета плавающих чисел в ракетном батарее MIM-104 Patriot не позволила перехватить входящую ракету Scud в Дахране, Саудовская Аравия, в результате которой погибли 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/100 - 0.00000010100011110101110000..... с повторением 10100011110101110000.

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

При форматировании результата с помощью

echo number_format(0.009999999999998, 2); 

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

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

+0

Спасибо за хороший ответ. – RubbelDeCatc

0

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

<?php 

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

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

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

См demo

Или вы могли бы просто использовать Е():

<?php 

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

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

См demo

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

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

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

См demo

Смежные вопросы