Сергей П.
37 повідомлень
#16 років тому
Столкнулся с проблемой дробный чисел. Предположим что есть система, 90% кода которой - это математическая модель.

так вот иногда получалось так, что, например, число "4.35" не равно числу "4.35", предположительно, что где-то там есть знаки после запятой, хотя по echo они не выводятся. В общем ситуация кода такая:


$a = 4.35;
$b = 256.65/59;
echo ($a==$b? "равны": "не равны");


В этом примере числа конечно будут равны, но в более сложной модели возникают проблемы.

Лечится только так:

$param = sprintf("%.2f", $param); // нужно всегда 2 знака после запятой


так вот вопрос: как обойтись без посторонних преобразований? или это единственный путь?
Николай С.
710 повідомлень
#16 років тому
Довольно часто простые десятичные дроби вроде 0.1 или 0.7 не могут быть преобразованы в свои внутренние двоичные аналоги без небольшой потери точности. Это может привести к неожиданным результатам: например, floor((0.1+0.7)*10) скорее всего возвратит 7 вместо ожидаемой 8 как результат внутреннего представления числа, являющегося в действительности чем-то вроде 7.9999999999....

Это связано с невозможностью точно выразить некоторые дроби в десятичной системе счисления конечным числом цифр. Например, 1/3 в десятичной форме принимает вид 0.3333333. . ..

Так что никогда не доверяйте точности последних цифр в результатах с числами с плавающей точкой и никогда не проверяйте их на равенство. Если вам действительно необходима высокая точность, вам следует использовать математические функции произвольной точности или gmp-функции.
Сергей П.
37 повідомлень
#16 років тому
SolNikolay, СПС, т.е. все идет к этому, чтобы преобразоывывать все числа к тому виду, чтобы операции над ними были точными
Болатов А.
1090 повідомлень
#16 років тому
Вещественные числа не следует сравнивать на равенство. Более правильным будет использовать целочисленную арифметику, либо сравнивать |v1-v2|<e, где e - достаточная точность сравнения.
Сергей П.
37 повідомлень
#16 років тому
alibek, тот же вариант, что и преозование
Вадим Т.
3240 повідомлень
#16 років тому
programmist, тот вариант, который предложил alibek - единственно правильный. Можно еще использовать какие-либо либы для работы с вещественными чистами, но они в любом случае имеют точно такую же внутреннюю реализацию. То есть, определяется некоторая константа, определяющая точность, и используется во всех операциях сравнения вещественных чисел.

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

Нам, помню, эти все нюансы по работе с вещественными числами читали на лекциях в институте еще в 1992 году, правда тогда были Си, Pascal, Basic, Assembler-ы всякие разные, но сути дела не меняет...
Сергей П.
37 повідомлень
#16 років тому
Что интересно, ни в Си, ни в паскале, ни в Васике не встречал таких моментов, хотя уже программирую на них 15 лет, а асм плохо знаю, так что за него не ручаюсь
Вадим Т.
3240 повідомлень
#16 років тому
programmist, наверное Вы просто не обращали внимание на это, что говорит о том, что Ваши программы потенциально могли работать с дефектами. Такая проблема была с самого начала появления ЭВМ, и до сих пор она же остается.
Сергей П.
37 повідомлень
#16 років тому
tvv, ну это Вы погорячились
Константин Т.
589 повідомлень
#16 років тому
Tvv не погорячился - это как раз тот случай, когда практика расходится с теорией в 999-и случаях из 1000, а в одном вроде-бы одинаковые числа не равны.
Сергей П.
37 повідомлень
#16 років тому
Pilat66, tvv, значит мне дествительно повезло, т.к. встретился с этим только 2 месяца назад, все исправил на sprintf, но раз специалисты говорят надо то бум иметь в виду. СПС за разъяснения
Вадим Т.
3240 повідомлень
#16 років тому
programmist, что будет в этом примере? В PHP 5.2.0 под Windows в первом случае результаты будут разные, во втором - одинаковые.

<?php
$a = 1.245;
$b = 1.24499999999999999;
printf("%.2f %.2f\n", $a, $b);

$a = 1.245;
$b = 1.244999999999999999;
printf("%.2f %.2f\n", $a, $b);
?>

Точность тут Вы не можете задавать, Вы зависите от той точности, которая дается той или иной версией системных либ, часто они разные на разных хостах. То есть, Ваши программы могут выдавать разные результаты в зависимости от версий PHP, версий и типов OS, версий системных либ тех хостов, где Ваши программы запущены, и т.д., и это неправильно.

Вот теперь и задумайтесь, стоит ли использовать sprintf для округления вещественных чисел перед сравнением. Еще дополнительно задумайтесь, как повлияет это Ваше преобразование с sprintf на производительность. После этого подумайте вообще о том, нужно ли это преобразование (округление) делать, и придете к выводу, что без него можно обойтись, если сравнивать вещественные числа правильно, с учетом точности.