Десятичная операция SQL Server является неточной

Oct 27 2020

Когда я запускаю эту простую операцию на SQL-сервере:

Select 800.0 /30.0 

Я получаю значение 26,666666, где, даже если оно округляется до 6 цифр, оно должно быть 26,666667. Как я могу получить точный расчет? Я попытался найти это в Интернете и нашел решение, в котором я приводил каждый операнд к десятичной дроби высокой точности перед операцией, но это будет неудобно для меня, потому что у меня много длинных сложных вычислений. думаю, должно быть лучшее решение.

Ответы

2 Larnu Oct 26 2020 at 23:46

При использовании деления в SQL Server любые цифры после итоговой шкалы усекаются, а не округляются. Для вашего выражения у вас есть a decimal(4,1)и a decimal(3,1), что приводит к decimal(10,6):

Precision = p1 - s1 + s2 + max(6, s1 + p2 + 1)
Scale = max(6, s1 + p2 + 1)

В результате 26.66666666666666~усекается до 26.666666.

Вы можете обойти это, увеличив размер точности и масштаба, а затем CONVERTвернувшись к требуемой точности и масштабу. Например, повысить точность и масштаб , decimal(3,1)чтобы decimal(5,2)и преобразовать обратно к decimal(10,6):

SELECT CONVERT(decimal(10,6),800.0 / CONVERT(decimal(5,3),30.0));

Это возвращается 26.666667.

CR241 Oct 26 2020 at 23:50

Это может быть полезно:

Используйте ROUND (Transact-SQL)

SELECT ROUND(800.0 /30.0, 5) AS RoundValue;

Результат:

RoundValue

26.666670
seanb Oct 27 2020 at 08:41

Я считаю, что это потому, что SQL Server принимает ваши числа как decimalзначения (которые являются точными, например, 6.6666 и 6.6667 означают именно эти значения, а не 6 и две трети), а не floatзначения (которые могут работать с приблизительными числами).

Если вы явным образом приведете / преобразуете его в a floatв начале, ваши вычисления должны выполняться плавно.

Вот несколько примеров , чтобы продемонстрировать разницу между int, decimalи floatрасчетов

  • Делим 20 на 3
  • Разделить 20 на 3, а затем снова умножить на 3 (математически должно быть 20).
SELECT  (20/3)                             AS int_calc,
        (20/3) * 3                         AS int_calc_x3,
        (CAST(20 AS decimal(10,3)) /3)     AS dec_calc,
        (CAST(20 AS decimal(10,3)) /3) * 3 AS dec_calc_x3,
        (CAST(20 AS float) /3)             AS float_calc,
        (CAST(20 AS float) /3) * 3         AS float_calc_x3

со следующими результатами

int_calc   int_calc_x3   dec_calc   dec_calc_x3   float_calc         float_calc_x3
6         18             6.666666   19.999998     6.66666666666667   20

В вашем случае вы можете использовать

Select CAST(800.0 AS float) /30.0

что дает 26,6666666666667

Обратите внимание, если вы затем умножите обратно на 30, он получит правильный результат, например,

Select (CAST(800.0 AS float) /30.0) * 30

дает 800. Решения, работающие с десятичными знаками, не будут иметь этого.

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

SELECT A.Num / 30
FROM (Select ((CAST(800.0 AS float) /30.0) * 30) AS Num) AS A

все равно будет 26.6666666666667

Надеюсь, это поможет вам в ваших долгих сложных расчетах.