Множення і ділення чисел типу 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.


Я сподіваюся, що це було корисно для вас.

Mat (Оригінал: Multiplication and Division with Numerics)
Переклад: Моісеєнко С.І.
Оригінал перекладу


Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*