Округленные числа возвращаются как '0.999999999992345' иногда - PullRequest
4 голосов
/ 18 февраля 2011

У меня есть отчет, который должен возвращать что-то вроде

SELECT brand, ROUND(SUM(count * price) / SUM(count), 2) 
    WHERE ... GROUP BY brand, ...; 

Проблема в том, что я иногда получаем 9990.32999999999992345 в моем коде perl вместо 9990.33, который возвращает прямой запрос SQL,

Число начинает выглядеть так сразу после fetchrow_hashref, если это когда-либо происходит.Одно и то же число может встречаться в «хорошей» или «плохой» форме в разных запросах, но всегда одинаково в любом конкретном запросе.

Как я могу отследить это?

Ответы [ 6 ]

9 голосов
/ 18 февраля 2011

Узнайте все о проблемах точности с плавающей точкой здесь: http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems

7 голосов
/ 18 февраля 2011

Как сказал Мелламокб, вы должны округлить числа с плавающей запятой. Что еще более важно, count и price, вероятно, означают, что вы рассчитываете цену чего-либо. Как эта страница объясняет для типов данных FLOAT и DOUBLE, расчеты являются приблизительными, в то время как для DECIMAL они являются точными. В вашем конкретном примере вероятность невелика, что создаст проблемы, но не в том случае, если вы выполняете много расчетов с помощью price. Обычное правило - всегда использовать точные типы данных для расчета цен.

4 голосов
/ 18 февраля 2011

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

2 голосов
/ 19 февраля 2011

33/100 - периодическое число в двоичном формате, также как 1/3 - периодическое число в десятичном виде.

$ perl -e'printf "%.20f\n", 0.33'
0.33000000000000001554

Следовательно, для его хранения в виде числа с плавающей запятой потребуется бесконечное хранилище.Чтобы избежать этой проблемы, вам нужно хранить число в виде строки, либо в начале (в запросе до того, как он будет с плавающей точкой), либо в конце (путем округления).

2 голосов
/ 18 февраля 2011

Я могу вспомнить пару причин этого, но сначала:

Имеет ли какое-либо значение поставить CONCAT( '', ... ) вокруг вашего раунда? Какую версию Perl вы используете? Что сообщает perl -V: nvtype?

1 голос
/ 18 февраля 2011

Это проблема, присущая числам с плавающей запятой. Это конструктивная особенность, а не недостаток.

Убедитесь, что значение, возвращаемое из базы данных, является не значением с плавающей запятой, а строкой или десятичным числом. (Если типы данных `price` и` count` оба DECIMAL, то полученное выражение должно быть DECIMAL.

Если любой из них является плавающей точкой, то вы можете конвертировать в DECIMAL ...

SELECT brand, CONVERT( SUM(count * price) / SUM(count), DECIMAL(18,2) ) 
    WHERE ... GROUP BY brand, ...; 

Или преобразовать в строку

SELECT brand, CONVERT(CONVERT( SUM(count * price) / SUM(count), DECIMAL(18,2)),CHAR)
    WHERE ... GROUP BY brand, ...; 

Вы можете разрешить преобразование в DECIMAL сделать округление за вас. Если вы вернете DECIMAL или VARHCAR в Perl, это должно избежать проблем с плавающей запятой.

В целом, для обработки представления (округления) с плавающей запятой в Perl вы можете отформатировать, используя функцию sprintf, например,

my $rounded_val = sprintf(%.2f, $float_val);
...