Это врожденная проблема с вычислениями с числами с плавающей запятой, которая не является специфичной для Firebird. Проблема в том, что вычисление 15 * 1.1 * 0.49
с использованием чисел двойной точности составляет , а не , а именно 8,085. Фактически, если вы сделаете 8.085 - RES
, вы получите значение, которое (приблизительно) 1.776356839400251e-015
(хотя, скорее всего, ваш клиент просто представит его как 0.00000000
).
Вы получите аналогичное значениерезультаты на разных языках. Например, в Java
DecimalFormat df = new DecimalFormat("#.00");
df.format(15 * 1.1 * 0.49);
также будет выдавать 8.08
по той же причине.
Кроме того, если вы измените порядок операций, вы получите другой результат. Например, использование 15 * 0.49 * 1.1
приведет к 8.085
и округлению до 8.09
, поэтому фактические результаты будут соответствовать вашим ожиданиям.
Учитывая, что round
само по себе также возвращает двойную точность, на самом деле это нехороший способ справиться с этим в вашем коде SQL, потому что округленное значение с большим числом десятичных дробей может все же привести к значению, немного меньшему, чем вы ожидаете, из-за того, как работают числа с плавающей запятой, поэтому двойной раунд все еще может не сработать для некоторыхчисла, даже если презентация в вашем клиенте выглядит «правильно».
Если вы просто хотите это для целей презентации, лучше сделать это в своем интерфейсе, но в качестве альтернативы вы можете попробовать трюки, такие как добавление небольшого значенияи приведение к decimal
, например что-то вроде:
cast(RES + 1e-10 as decimal(18,2))
Однако это все еще имеет проблемы с округлением, потому что невозможно различить значения, которые действительно равны 8.08499999999 (и должны быть округлены до 8.08),и значения, где результат расчета просто 8.08499999999 в плавающей запятой, в то время как в точных цифрах это будет 8,085 (и поэтому необходимо округлить до 8,09).
В аналогичном ключе вы можете попытаться использовать двойное приведение к decimal
(например, cast(cast(res as decimal(18,3)) as decimal(18,2))
) или приведение decimal
, а затем округление (например, round(cast(res as decimal(18,3)), 2)
. Это было бы немного более последовательным, чем двойное округление, потому что первое приведение преобразуется в точные числовые значения, но опять-таки это имеет аналогичный недостаток, как упомянуто выше.
Хотя вы не хотите слышать этот ответ, если хотитеточная числовая семантика, вы не должны использовать типы с плавающей точкой.