2009-07-02 4 views
9

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

Когда я пишу

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 получить лучшую точность (что мне кажется иронией :)?
  • Есть ли более простой способ, чем загромождать мои формулы с помощью бросков?

ответ

9

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).
Окончательный расчет преобразует результат в NUMERIC (17, 10).


Редактировать

Взятые из SQL Server Books Online Data Type Conversion

В заявлениях Transact-SQL, постоянной с десятичной точкой автоматически преобразуется в числовое значение данных, использование минимальной точности и шкалы необходимо. Например, константа 12,345 преобразуются в числовое значение с точностью до 5 и шкалы из 3.

+0

А .. Я не знал этого метода до сих пор. Большое спасибо :) – VVS

+0

Значит, нет никакого пути вокруг явного приведения, чтобы получить ожидаемый результат? Есть ли другой способ сказать SQL Server, что данный литерал имеет тип (например,) float? – VVS

+0

@VVS, помимо явного приведения, как вы уже делаете, не то, что я знаю. SQL Server всегда интерпретирует значение как числовое.Проблема может быть немного облегчена, если вы сами добавляете точность к своим значениям, например 1.0000000000000000000000000000000000000000. –

4

Да, вы часто должны бросить их плавать получить более высокую точность.Мой взгляд на него:

For better precision cast decimals before calculations

+0

Обоснование в статье является ошибочным. Он основан на некоторых произвольных значениях. Было бы легко найти встречный пример в пользу десятичного типа. Эти индийские претензии, основанные на одном эмпирическом примере, являются ловушками сами по себе. – f470071

3

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

Литеральные численные значения с десятичной точкой, исключая научную нотацию, представляют собой десятичный тип данных, который хранится как можно меньше десятичного типа. То же цитата, как Ливены Keersmaekers от: 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) 

Еще один момент, чтобы рассмотреть это Data Type precedence. Когда оператор объединяет два выражения разных типов данных, правила для приоритета типа данных указывают, что тип данных с более низким приоритетом преобразуется в тип данных с более высоким приоритетом. И еще один момент для рассмотрения - если мы выполняем арифметические операции над десятичными типами, то полученный десятичный тип, т. Е. Точность и масштаб, зависят от обоих операндов и самой операции. Это описано в документе Precision, Scale, and Length.

Таким образом, часть вашего выражения в круглых скобках

(1.0/60.0) is evaluated to 0.016666 and the resulting type is Decimal (8,6) 

используя выше правили о точности и масштабе десятичных выражений. Кроме того, используется округление банкинга или округление до четного. Важно отметить, что используются различные округления для десятичного и плавающего типов. Если мы продолжим выражение

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)) 

И еще одна ВАЖНАЯ вещь. Даже это выражение float не вычисляет точный результат. Это просто так, что передняя часть (SSMS или что-то еще) округляет значение до (я думаю) точность 6 цифр, а затем усекает конечные нули. Так, например, 1.000001 становится 1.

Простой, не так ли?

Смежные вопросы