MySQL вокруг странной ошибки

Я столкнулся с действительно странной ошибкой? на mysql + php прямо сейчас. Является простым выбором, в следующем примере я использую несколько полей, чтобы попытаться объяснить мою проблему:

  • «поле» – 11,5
  • $ phpvar – 1,15

Запрос 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, правильно? ну да и нет.

результаты запроса:

  • a1 [round (field * ". $ phpvar.", 2)] => 13.22
  • a2 [раунд (поле * 1.15, 2)] => 13.22
  • a3 [round (11.5 * ". $ phpvar.", 2)] => 13.23
  • a4 [раунд (11,5 * 1,15, 2)] => 13,23
  • a5 [поле * ". $ phpvar."] => 13.225 (без раунда)

что мне не хватает? как это возможно, когда дело доходит до использования «поля», мой результат получает подделку?

Проблема в том, как хранятся значения 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 до округления.