SQL-сервер:Расчет с числовыми литералами
-
21-08-2019 - |
Вопрос
Я провел небольшое тестирование с вычислениями с плавающей запятой, чтобы минимизировать потерю точности.Я наткнулся на феномен, который хочу показать здесь и, надеюсь, получить объяснение.
Когда я пишу
print 1.0 / (1.0 / 60.0)
результат
60.0024000960
Когда я пишу ту же формулу и делаю явное приведение к float
print cast(1.0 as float) / (cast(1.0 as float) / cast(60.0 as float))
результат
60
До сих пор я думал, что числовые литералы с десятичными знаками автоматически обрабатываются как float
значения с соответствующей точностью.Кастинг в real
показывает тот же результат, что и приведение к float
.
- Есть ли какая-нибудь документация о том, как SQL Server оценивает числовые литералы?
- К какому типу данных относятся эти литералы?
- Мне действительно нужно приводить их к
float
получить лучшую точность (что для меня звучит как ирония :)? - Есть ли более простой способ, чем загромождать мои формулы приведениями?
Решение
SQL Server использует наименьший возможный тип данных.
Когда вы запустите этот скрипт
SELECT SQL_VARIANT_PROPERTY(1.0, 'BaseType')
SELECT SQL_VARIANT_PROPERTY(1.0, 'Precision')
SELECT SQL_VARIANT_PROPERTY(1.0, 'Scale')
SELECT SQL_VARIANT_PROPERTY(1.0, 'TotalBytes')
вы увидите, что SQL Server неявно использовал тип данных NUMERIC(2, 1).
Деление на 60,0 преобразует результат в ЧИСЛЕННЫЙ (8, 6).
Окончательный расчет преобразует результат в ЧИСЛОВОЙ (17, 10).
Редактировать
Взято из электронной документации по SQL Server. Преобразование типов данных
В операторах Transact-SQL константа с десятичной точкой автоматически преобразуется в числовое значение данных, используя минимальную точность и необходимую масштаб.Например, константа 12.345 преобразуется в числовое значение с точностью 5 и шкалой 3.
Другие советы
Да, вам часто приходится использовать их для плавания, чтобы повысить точность.Мой взгляд на это:
Я думаю, следует понять, что происходит за кулисами, для дальнейшего использования в подобных случаях.
Буквальные числовые значения с десятичной точкой, исключая экспоненциальное представление, представляют тип данных Decimal, который хранится как наименьший возможный тип Decimal.Та же цитата, что и у Ливена Кеерсмакерса:https://msdn.microsoft.com/en-us/library/ms191530%28SQL.90%29.aspx#_decimal
В операторах Transact-SQL константа с десятичной точкой автоматически преобразуется в числовое значение данных, используя минимальную точность и необходимую масштаб.Например, константа 12.345 преобразуется в числовое значение с точностью 5 и шкалой 3.
Конечные нули справа от десятичной точки указывают масштаб.Ведущие нули слева от десятичной точки игнорируются.
Некоторые примеры:
1.0 -> Decimal(2,1)
60.0 -> Decimal(3,1)
1.00 -> Decimal(3,2)
01.0 -> Decimal (2,1)
Еще один момент, который следует учитывать, это Приоритет типа данных.Когда оператор объединяет два выражения разных типов данных, правила приоритета типов данных указывают, что тип данных с более низким приоритетом преобразуется в тип данных с более высоким приоритетом.И еще один момент, который следует учитывать: если мы выполняем арифметические операции над типами Decimal, результирующий тип Decimal, т.е.точность и масштаб зависят как от операндов, так и от самой операции.Это описано в документе Точность, масштаб и длина.
Итак, часть вашего выражения в скобках
( 1.0 / 60.0 ) is evaluated to 0.016666 and the resulting type is Decimal (8,6)
используя приведенные выше правила точности и масштаба десятичных выражений.Кроме того, используется банкирское округление или округление до четности.Важно отметить, что используются разные округления для типов Decimal и float.Если мы продолжим выражение
1.0 / 0.016666 is evaluated to 60.002400096 and the resulting type is Decimal (17,10)
Таким образом, часть несоответствия связана с тем, что для десятичных типов используется другое округление, чем для чисел с плавающей запятой.
В соответствии с приведенными выше правилами было бы достаточно использовать только одно приведение внутри скобок.Каждый второй литерал будет повышен до плавающего уровня в соответствии с правилами приоритета типов данных.
1.0 / (1.0 / cast(60.0 as float))
И еще ВАЖНО.Даже это выражение с плавающей запятой не вычисляет точный результат.Просто интерфейс (SSMS или что-то еще) округляет значение до (я думаю) точности 6 цифр, а затем усекает конечные нули.Так что т.е.1,000001 становится 1.
Просто, не так ли?
Чтобы написать постоянное выражение с плавающей запятой, попробуйте использовать научную запись:
select (1.0E0 / (1.0E0 / 60.0E0))
Результат — 60.