Вопрос

При отладке некоторого 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, к результирующему ЧИСЛОВОМУ типу для умножения так же, как и для деления и т.д.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top