Сергей П.
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 на производительность. После этого подумайте вообще о том, нужно ли это преобразование (округление) делать, и придете к выводу, что без него можно обойтись, если сравнивать вещественные числа правильно, с учетом точности.