Потеря числовой точности SQL server 2005
-
02-07-2019 - |
Вопрос
При отладке некоторого SQL-кода, связанного с финансами, была обнаружена странная проблема с числовой (24,8) математической точностью.
Выполнив следующий запрос в вашем MSSQL, вы получите результат выражения A + B * C равным 0.123457
ВЫБЕРИТЕ A, B, C, A + B * C От ( ВЫБЕРИТЕ ПРИВЕДЕНИЕ (0.12345678 В КАЧЕСТВЕ ЧИСЛОВОГО ЗНАЧЕНИЯ (24,8)) В КАЧЕСТВЕ A, ПРИВЕДЕНИЕ (0 В КАЧЕСТВЕ ЧИСЛОВОГО ЗНАЧЕНИЯ (24,8)) КАК B, ПРИВЕДИТЕ (500 КАК ЧИСЛОВОЕ ЗНАЧЕНИЕ (24,8)) КАК C ) T
Таким образом, мы потеряли 2 значимых символа.Пытаясь исправить это разными способами, я получил, что преобразование промежуточного результата умножения (который равен нулю!) в числовой (24,8) будет работать нормально.
И, наконец, у меня есть решение.Но все же у меня возникает вопрос - почему MSSQL ведет себя таким образом и какие преобразования типов на самом деле произошли в моем примере?
Решение
Точно так же, как сложение типа с плавающей запятой является неточным, умножение десятичных типов может быть неточным (или вызвать неточность), если вы превысите точность.Видишь Преобразование типов данных и десятичные и числовые.
С тех пор, как вы размножились NUMERIC(24,8)
и NUMERIC(24,8)
, и SQL Server будет проверять только тип, а не содержимое, он, вероятно, попытается сохранить потенциальные 16 десятичных цифр (24-8), когда не сможет сохранить все 48 цифр точности (максимум 38).Объединив два из них, вы получите 32 десятичных знака, что оставляет вам только 6 десятичных знаков (38 - 32).
Таким образом, исходный запрос
SELECT A, B, C, A + B * C
FROM ( SELECT CAST(0.12345678 AS NUMERIC(24,8)) AS A,
CAST(0 AS NUMERIC(24,8)) AS B,
CAST(500 AS NUMERIC(24,8)) AS C ) T
сводится к
SELECT A, B, C, A + D
FROM ( SELECT CAST(0.12345678 AS NUMERIC(24,8)) AS A,
CAST(0 AS NUMERIC(24,8)) AS B,
CAST(500 AS NUMERIC(24,8)) AS C,
CAST(0 AS NUMERIC(38,6)) AS D ) T
Опять же, между NUMERIC(24,8)
и NUMERIC(38,6)
, SQL Server попытается сохранить потенциальные 32 цифры без десятичных знаков, так что A + D
сводится к
SELECT CAST(0.12345678 AS NUMERIC(38,6))
что дает вам 0.123457
после округления.
Другие советы
Следуя логике , указанной eed3si9n и то, что вы сказали в своем вопросе, похоже, что лучший подход при выполнении математических операций - извлечь их в функцию и дополнительно указать точность после каждой операции,
В этом случае функция могла бы выглядеть примерно так:
create function dbo.myMath(@a as numeric(24,8), @b as numeric(24,8), @c as numeric(24,8))
returns numeric(24,8)
as
begin
declare @d as numeric(24,8)
set @d = @b* @c
return @a + @d
end
Несмотря на то, что написано на Точность, масштаб и длина (Transact-SQL).Я полагаю, что он также применяет минимальную "шкалу" (количество знаков после запятой), равную 6, к результирующему ЧИСЛОВОМУ типу для умножения так же, как и для деления и т.д.