Вопрос

Я провел небольшое тестирование с вычислениями с плавающей запятой, чтобы минимизировать потерю точности.Я наткнулся на феномен, который хочу показать здесь и, надеюсь, получить объяснение.

Когда я пишу

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.

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