Биография
Библиотека
Диссертация
Отчет о поиске
Ссылки
Задание
Персональный сайт магистра Павленко Дениса Николаевича

Умножение и деление чисел типа NUMERIC


первоисточник

Может быть вам покажутся удивительными результаты следующих арифметических операций:

declare @num1 numeric(38,10)
declare @num2 numeric(38,10)
set @num1 = .0000006
set @num2 = 1.0
select cast( @num1 * @num2 as numeric(38,10))

Дает: .0000010000
Вместо: .0000006000

Почему?

Ну, BOL (смотри Precision, Scale и Length) определяет следующие правила для арифметических операций c числами типа NUMERIC:

Операция Точность результата Масштаб результата *
e1 + e2 max(s1, s2) + max(p1-s1, p2-s2) + 1 max(s1, s2)
e1 - e2 max(s1, s2) + max(p1-s1, p2-s2) + 1 max(s1, s2)
e1 * e2 p1 + p2 + 1 s1 + s2
e1 / e2 p1 - s1 + s2 + max(6, s1 + p2 + 1) max(6, s1 + p2 + 1)
e1 { UNION | EXCEPT | INTERSECT } e2 max(s1, s2) + max(p1-s1, p2-s2) max(s1, s2)

В нашем случае точность и масштаб умножения вычисляется так:

Точность = P1 + P2 + 1 = 38 + 38 + 1 = 77
Масштаб = S1 + S2 = 10 + 10 = 20

Соответственно, результатом должно быть число типа numeric(77, 20), что не допускается. Вот где нам понадобится сноска:

* Точность и масштаб результата имеет абсолютный максимум 38. Если точность результата превышает 38, соответствующий масштаб уменьшается, чтобы предотвратить усечение целой части результата.

BOL в настоящий момент не вдается в подробности того, как выполняется усечение.

Поскольку точность превышает 38, мы постараемся избежать усечения целой части значения уменьшением масштаба (вместо этого усекая дробную часть значения). Каким количеством масштаба пожертвовать? Здесь нет правильного ответа. Если оставить слишком много, то будет потерян результат умножения больших чисел. Если оставить слишком мало, умножение малых чисел станет проблемой.

В SQL Server 2005 RTM (и предыдущих версиях) мы решили оставить минимальный масштаб - 6 как для умножения, так и деления. Поэтому наше numeric(77,20) усекалось до numeric(38,6), а затем приводилось к numeric(38,10). Однако это было сделано слишком поздно, и некоторые данные были потеряны. Это объясняет результат, который вы можете увидеть выше.

Поэтому важно стараться задавать по минимуму точность и масштаб операндов в умножении и делении. В этом случае:

declare @num1 numeric(18,10)
declare @num2 numeric(18,10)
set @num1 = .0000006
set @num2 = 1.0
select cast( @num1 * @num2 as numeric(38,10))

тип результата должен быть numeric(37,20). Поскольку точность и масштаб типа не превышает наших текущих пределов, неявное усечение не проводится. Затем мы приводим результат к numeric(38,10), что не вызывает потери данных в нашем случае.

Если вы не можете точно типизировать значения, участвующие в умножении и делении, например, если они являются параметрами процедуры, которая вызывается с большим разбросом значений, возможно, стоит обратить внимание на приближенные числовые типы (float, real) или определить свой собственный "высокоемкий" пользовательский точный числовой тип данных, используя CLR.

Я надеюсь, что это было полезно для вас. Дайте нам знать, если вы хотите прочитать о чем-то конкретно.

29/03/2006

faq(c)