Почему «SELECT POWER (10.0, 38.0);» выдает ошибку арифметического переполнения?

15

Я обновляю свой IDENTITYскрипт проверки переполнения для учета DECIMALи NUMERIC IDENTITYстолбцов .

В рамках проверки я вычисляю размер диапазона типа данных для каждого IDENTITYстолбца; Я использую это, чтобы вычислить, какой процент этого диапазона был исчерпан. Для DECIMALи NUMERIC размер этого диапазона,2 * 10^p - 2 где pточность.

Я создал кучу тестовых таблиц с DECIMALи NUMERIC IDENTITYстолбцами и попытался вычислить их диапазоны следующим образом :

SELECT POWER(10.0, precision)
FROM sys.columns
WHERE 
       is_identity = 1
   AND type_is_decimal_or_numeric
;

Это бросило следующую ошибку:

Msg 8115, Level 16, State 6, Line 1
Arithmetic overflow error converting float to data type numeric. 

Я сузил его до IDENTITYстолбцов типа DECIMAL(38, 0)(то есть с максимальной точностью), поэтому я попробовал POWER()вычисление непосредственно по этому значению.

Все следующие запросы

SELECT POWER(10.0, 38.0);
SELECT CONVERT(FLOAT, (POWER(10.0, 38.0)));
SELECT CAST(POWER(10.0, 38.0) AS FLOAT);

также привел к той же ошибке.

  • Почему SQL Server попытаться преобразовать вывод POWER(), который имеет тип FLOAT, к NUMERIC(особенно , когда FLOATимеет более высокий приоритет )?
  • Как я могу динамически рассчитать диапазон DECIMALили NUMERICстолбца для всех возможных значений точности (включая p = 38, конечно,)?
Ник Чаммас
источник

Ответы:

18

Из POWERдокументации :

Синтаксис

POWER ( float_expression , y )

аргументы

float_expression
Является ли выражение типа поплавка или такого типа , который может быть неявно преобразован в поплавок .

y
- сила, до которой можно вызвать выражение float_expression . y может быть выражением точной числовой или приблизительной категории числового типа данных, за исключением типа битовых данных.

Типы возврата

Возвращает тот же тип, который представлен в float_expression . Например, если десятичное число (2,0) представлено как выражение float_expression, возвращаемый результат будет десятичным (2,0).


Первый вход неявно приводится к floatпри необходимости.

Внутренние вычисления выполняются с использованием floatарифметики стандартной функцией C Runtime Library (CRT) pow.

floatВыход из powотливают обратно к типу левого операнда (подразумевается, что numeric(3,1)при использовании буквальное значение 10.0).

Использование явного floatпрекрасно работает в вашем случае:

SELECT POWER(1e1, 38);
SELECT POWER(CAST(10 as float), 38.0);

Точный результат для 10 38 не может быть сохранен в SQL Server, decimal/numericпотому что для этого потребуется точность в 39 цифр (за 1 следует 38 нулей). Максимальная точность 38.

Мартин Смит
источник
23

Вместо того, чтобы больше вмешиваться в ответ Мартина, я добавлю остальные мои выводы, касающиеся POWER()здесь.

Держись за свои трусики.

преамбула

Сначала я представляю вам экспонат А, документацию MSDN дляPOWER() :

Синтаксис

POWER ( float_expression , y )

аргументы

float_expression Выражение типа float или типа, который можно неявно преобразовать в float.

Типы возврата

Такой же как float_expression.

Из этой последней строки вы можете сделать вывод, что POWER()тип возвращаемого значения - FLOATно прочитайте еще раз. float_expressionимеет тип типа float или типа, который может быть неявно преобразован в тип float. Таким образом, несмотря на название, на float_expressionсамом деле это может быть a FLOAT, a DECIMALили an INT. Так как выходные данные POWER()такие же, как и у float_expression, они также могут быть одним из этих типов.

Итак, у нас есть скалярная функция с типами возвращаемых данных, которые зависят от входных данных. Может ли это быть?

наблюдения

Я представляю вам экспонат B, тест, демонстрирующий, что POWER()вывод его данных выполняется в зависимости от типа входных данных .

SELECT 
    POWER(10, 3)             AS int
  , POWER(1000000000000, 3)  AS numeric0     -- one trillion
  , POWER(10.0, 3)           AS numeric1
  , POWER(10.12305, 3)       AS numeric5
  , POWER(1e1, 3)            AS float
INTO power_test;

EXECUTE sp_help power_test;

DROP TABLE power_test;

Соответствующие результаты:

Column_name    Type      Length    Prec     Scale
-------------------------------------------------
int            int       4         10       0
numeric0       numeric   17        38       0
numeric1       numeric   17        38       1
numeric5       numeric   17        38       5
float          float     8         53       NULL

То , что кажется, происходит то , что POWER()забросы float_expressionв наименьшему типу , который соответствует его, не включая BIGINT.

Таким образом, происходит SELECT POWER(10.0, 38);сбой с ошибкой переполнения, поскольку 10.0происходит приведение к типу, NUMERIC(38, 1)который недостаточно велик, чтобы содержать результат 10 38 . Это связано с тем, что 10 38 расширяется до 39 цифр перед десятичной запятой, тогда как NUMERIC(38, 1)может хранить 37 цифр до десятичной запятой и одну после нее. Следовательно, максимальное значение NUMERIC(38, 1)может быть 10 37 - 0,1.

Вооружившись этим пониманием, я могу придумать еще одну ошибку переполнения следующим образом.

SELECT POWER(1000000000, 3);    -- one billion

Один миллиард (в отличие от одного триллиона из первого примера, который приведен NUMERIC(38, 0)) - достаточно мал, чтобы уместиться в INT. Однако миллиард, возведенный в третью степень, слишком велик INT, поэтому возникает ошибка переполнения.

Несколько других функций демонстрируют аналогичное поведение, где их тип вывода зависит от их ввода:

Вывод

В данном конкретном случае решение заключается в использовании SELECT POWER(1e1, precision).... Это сработает для всех возможных значений точности с момента 1e1приведения к нему FLOAT, который может содержать смехотворно большие числа .

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

Итак, дети, теперь, когда вы это знаете, вы можете идти дальше и процветать.

Ник Чаммас
источник