Проблема с round() Неправильно округляет

tolik777

Новичок
Проблема с round() Неправильно округляет

Есть код:
PHP:
$num = 0.365;
$num = round((float)$num,2);
echo $num;
На сервере под Осью Fedora Core 4 выводит результат 0.36.
На локалке под WinXP SP2 выводит правильный результат 0.37.
Ни чего непойму.

-~{}~ 31.10.06 12:32:

Забыл сказать и там и там PHP4.
 

hermit_refined

Отшельник
Ничего удивительного. Вещественные числа - это вам не игрушки.

В исходниках PHP 5.1.6 и 4.4.4 есть такая замечательная вещь:
Код:
# ifndef PHP_WIN32
#  define PHP_ROUND_FUZZ 0.50000000001
# else
#  define PHP_ROUND_FUZZ 0.5
# endif
А в 4.3.0 - нету. Видимо, это как раз попытка добиться предсказуемости. Поставьте php посвежей.
 

SiMM

Новичок
> В исходниках PHP 5.1.6 и 4.4.4 есть такая замечательная вещь
А разве формат чисел с плавающей запятой (IEEE 754, если не ошибаюсь) зависит от OS?
 

tolik777

Новичок
В чейнжлоге 4.4.4 и 4.4.3 я не нашел чего-нибудь, что касается функции round().

-~{}~ 31.10.06 13:29:

http://bugs.php.net/bug.php?id=36008
 

Skubent

Новичок
Формат может и не зависит, округления - зависят. Проверено опытным путем неоднократно.
 

tolik777

Новичок
Попробовал так:
PHP:
$num = 0.365; 
$num = round((float)$num,2); 
echo $num;
Тоже самое выводит.
Какой выход посоветуете?
 

SiMM

Новичок
> округления - зависят
Интересно, почему, если эта операция выполняется процессором, а не реализуется программно, если я не ошибаюсь.

> Какой выход посоветуете?
PHP:
echo floor($num*100+0.5)/100;
?
 

tolik777

Новичок
Пробовал. Не помогает.
floor тоже дает на сервере 0.36, а на локале 0.37
 

hermit_refined

Отшельник
Вообще весело. У меня на 4.4.4 тоже дает 0.36 :).
Кроме того, round(365/1000, 2); даёт 0.36, а эквивалент - судя по исходникам - на С:
Код:
double f = pow( 10.0, (double) 2);
floor( ((double) 365/1000) * f + 0.5) / f;
даёт верные 0.37. Чего-то, видимо, я не учитываю, но факт остаётся фактом - их же тесты проваливаются:

=====================================================================
FAILED TEST SUMMARY
---------------------------------------------------------------------
...
Bug #24142 (round() problems) [ext/standard/tests/math/bug24142.phpt]
...

SiMM
Это связано не с форматом, а с реализацией pow (т.е. логарифма + эскпоненты). К слову сказать - они там прежде проверку через config.m4 делают, тот кусок, что я привёл - подстраховка.

Добиться предсказуемости у них не вышло, да в общем понятно, что не таким путём её надо добиваться. Что-то они курили, когда это писали.

tolik777
Нормальной программе должно быть пофигу, ведь это же всё-таки округление. Если же вам принципиально - добавляйте что-нибудь очень маленькое.
 

tolik777

Новичок
Дело в том что у меня магазин и там после оплаты делается проверка на соответсвие суммы. Раньше задавался диапазон отклонений у меня +- 3% от суммы.
Но теперь у меня система усложнилась, и из-за округлений эта сума не укладывается даже в этот диапазон 3%. А это уже критично.
 

hermit_refined

Отшельник
tolik777
Значит, вы что-то не то делаете, вам надо поменять схему вычислений. С вещественными числами нужно быть очень осторожным, и в то же время - понимать, что если вы в результате вычислений получили что-то похожее на 0.365, то в действительности - там может быть нечто другое, 0.364999999999997 или 0.365000000000001, так что округление здесь не принципиально.

Потеря идёт из-за округлений как таковых, вне зависимости от того, "правильны" ли они в вышеупомянутом смысле. Поэтому вам необходимо минимизировать их количество, и сделать их "согласованными".
 

tolik777

Новичок
Да это я все понимаю. Пришлось везде 0.0000005 поприбалять. Все вроде нормально заработало, но код стал некрасивый.
Просто когда просчитываю все на калькуляторе, все нормально получается. На локальном компьютере под Windows также все отлично работает, а вот на сервер закинул и начались баги.
Вещественные то вещественные, но все же round работает не так как надо. Надеюсь в следующих версиях РНР этот глюк исправят.
Все спасибо за подсказки.
 

С.

Продвинутый новичок
Просто округление надо производить на промежуточных шагах:

Неправильно:
$cost[1]=$price[1]*$qty[1];
$cost[2]=$price[2]*$qty[2];
$cost[3]=$price[3]*$qty[3];
$total= round($cost[1]+$cost[2]+$cost[3],2);

Правильно:
$cost[1]=round($price[1]*$qty[1],2);
$cost[2]=round($price[2]*$qty[2],2);
$cost[3]=round($price[3]*$qty[3],2);
$total= $cost[1]+$cost[2]+$cost[3];

А как это округление производится - совершенно без разницы.
 

Gorynych

Посетитель PHP-Клуба
на всякий случай, о следующих версиях PHP:

WinXP, PHP 5.1.2 - 0,37
FreeBSD, PHP 5.1.3 - 0,37
 

SiMM

Новичок
> Это связано не с форматом, а с реализацией pow (т.е. логарифма + эскпоненты)
Я думал, они аппаратные, соответственно от OS не зависят. Но если в стандартных библиотеках чего-то мудят - то, конечно, результат непредсказуем.
 

tolik777

Новичок
Правильно:
$cost[1]=round($price[1]*$qty[1],2);
$cost[2]=round($price[2]*$qty[2],2);
$cost[3]=round($price[3]*$qty[3],2);
$total= $cost[1]+$cost[2]+$cost[3];
А помойму разницы нету. Я пробовал вычисление делать в функции округления. Тот же самый результат дает.
Видимо связка PHP + RedHat и ее клоны неправильно округляют.

-~{}~ 31.10.06 16:25:

Кстати в приведенном примере:
PHP:
$num = 0.365; 
$num = round((float)$num,2); 
echo $num;
какой может быть промежуточный шаг?
 

С.

Продвинутый новичок
Автор оригинала: tolik777
А помойму разницы нету.
Правильно, если цены до сотых и количество целое. А откуда у тебя тогда тысячные берутся? Я могу предположить либо ты колбасу по граммам продаешь, либо скидку предоставляешь. Вот тогда и получится разница.

Автор оригинала: tolik777
PHP:
$num = 0.365; 
$num = round((float)$num,2); 
echo $num;
какой может быть промежуточный шаг?
Откуда 0.365 взялось? Результат предыдущего вычисления? Тогда он должен к тебе придти уже округленным. А в какую сторону, тебя не должно волновать.

Если ты как раз и производишь промежуточное округление, то сделай его и не парься. Не будет у тебя никаких проблем в какую бы сторону он не округлился.
 

tolik777

Новичок
Скажу откуда. К примеру товар стоит 7.7$ и есть скидка 35%.
7.7*35% = 2.695. При округлении должно получиться 2.7, а получается 2.69.
 

С.

Продвинутый новичок
Ну и что? Например в банках именно так и округляют.

Ты писал, что у тебя из-за этого какое-то отклонение в 3% вылезает. Вот в это я поверить не могу.
 
Сверху