Я столкнулся с действительно странной ошибкой? на mysql + php прямо сейчас. Является простым выбором, в следующем примере я использую несколько полей, чтобы попытаться объяснить мою проблему:
Запрос MySQL:
select round(field * " . $phpvar . " ,2) as a1, round(field * 1.15 ,2) as a2, round(11.5 * " . $phpvar . " ,2) as a3, round(11.5 * 1.15 ,2) as a4, field * " . $phpvar . " as a5 from ...
хорошо, я пытаюсь получить 13.23. "field" * $phpvar = 13.225
, поэтому, используя раунд (13.225,2), я должен получить 13.23, правильно? ну да и нет.
результаты запроса:
что мне не хватает? как это возможно, когда дело доходит до использования «поля», мой результат получает подделку?
Проблема в том, как хранятся значения DOUBLE и FLOAT.
Возможно (и вероятно), что значения, такие как 11,5 или 22,475 кул, сохраняются в приблизительных значениях, таких как 11.499999999999 ~ или 22.475000000000000001, поэтому некоторые вычисления или округления могут привести к неправильным результатам.
Всегда лучше сохранять значения float в тип DECIMAL coulmn, где значение хранится точно со всеми десятичными цифрами и не аппроксимируется.
Точные числовые литералы (такие как 1.15
во всех четырех ваших примерах и 11.5
в последних двух) кодируются с использованием типа DECIMAL
MySQL.
В ваших первых двух примерах результатом умножения field
(типа DOUBLE
) с такими литералами является еще один DOUBLE
, который хранится с использованием 8-байтового представления с плавающей точкой стандарта IEEE Standard (т.е. binary64 ). Однако в этом представлении 13.225
кодируется как 0x402A733333333333
, чьи биты означают:
Знак : 0b0 Предвзятый показатель : 0b10000000010 = 1026 (представление включает смещение +1023, поэтому exp = 3) Значения и : 0b [1.] 1010011100110011001100110011001100110011001100110011 = [1.] 6531249999999999555910790149937383830547332763671875 ^ скрытый бит, не сохраненный в двоичном представлении
Это соответствует:
(-1) ^ 0 * 1.6531249999999999555910790149937383830547332763671875 * 2 ^ 3 = 13.2249999999999996447286321199499070644378662109375000
Поэтому округление этого результата до двух знаков после запятой дает 13.22
не 13.23
.
Выполнение умножения с двумя типами DECIMAL
, например двумя литералами, используемыми в последних двух примерах, приводит к другому DECIMAL
. В этом представлении 13.225
кодирует в двоично-кодированном десятичном формате с точной точностью, и поэтому операция округления приводит к 13.23
как ожидалось.
Как упоминалось в руководстве по MySQL :
Числа с плавающей запятой иногда вызывают путаницу, поскольку они являются приблизительными и не сохраняются в виде точных значений. Значение с плавающей запятой, как указано в инструкции SQL, может быть не таким, как значение, представленное внутри. Попытки рассматривать значения с плавающей запятой как точные в сравнении могут привести к проблемам. Они также зависят от зависимостей платформы или реализации. Эти типы вопросов относятся к типам данных
FLOAT
иDOUBLE
. Для столбцовDECIMAL
MySQL выполняет операции с точностью до девяти десятичных цифр, которые должны решать наиболее распространенные проблемы неточности.
Если вы после точной точности, DECIMAL
может больше соответствовать вашим требованиям. Если вам нужен диапазон / производительность DOUBLE
но все же хотите получить желаемый результат округления, вы можете CONVERT
результат умножения в DECIMAL
до округления.