¿Por qué "SELECT POWER (10.0, 38.0);" arroja un error de desbordamiento aritmético?

15

Estoy actualizando mi IDENTITYsecuencia check desbordamiento para tener en cuenta DECIMALy NUMERIC IDENTITYcolumnas .

Como parte de la verificación, calculo el tamaño del rango del tipo de datos para cada IDENTITYcolumna; Lo uso para calcular qué porcentaje de ese rango se ha agotado. Para DECIMALy NUMERIC el tamaño de ese rango es2 * 10^p - 2 donde pestá la precisión.

Creé un montón de tablas de prueba con DECIMALy NUMERIC IDENTITYcolumnas e intenté calcular sus rangos de la siguiente manera:

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

Esto arrojó el siguiente error:

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

Lo reduje a las IDENTITYcolumnas de tipo DECIMAL(38, 0)(es decir, con la máxima precisión), así que intenté el POWER()cálculo directamente en ese valor.

Todas las siguientes consultas

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

También resultó en el mismo error.

  • ¿Por qué SQL Server intenta convertir la salida de POWER(), que es de tipo FLOAT, a NUMERIC(especialmente cuando FLOATtiene una mayor precedencia )?
  • ¿Cómo puedo calcular dinámicamente el rango de una DECIMALo NUMERICcolumna para todas las precisiones posibles (incluidas p = 38, por supuesto)?
Nick Chammas
fuente

Respuestas:

18

De la POWERdocumentación :

Sintaxis

POWER ( float_expression , y )

Argumentos

float_expression
Es una expresión de tipo float o de un tipo que se puede convertir implícitamente en float .

y
Es el poder al que elevar float_expression . y puede ser una expresión de la categoría de tipo de datos numérico exacto o aproximado, a excepción del tipo de datos de bit .

Tipos de retorno

Devuelve el mismo tipo que el enviado en float_expression . Por ejemplo, si se envía un decimal (2,0) como expresión_ float, el resultado devuelto es decimal (2,0).


La primera entrada se convierte implícitamente floatsi es necesario.

El cálculo interno se realiza utilizando la floataritmética mediante la función estándar de biblioteca de tiempo de ejecución C (CRT) pow.

La floatsalida de powse devuelve al tipo del operando de la izquierda (implícito numeric(3,1)cuando se usa el valor literal 10.0).

Usar un explícito floatfunciona bien en su caso:

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

Un resultado exacto para 10 38 no se puede almacenar en un servidor SQL decimal/numericporque requeriría 39 dígitos de precisión (1 seguido de 38 ceros). La precisión máxima es 38.

Martin Smith
fuente
23

En lugar de entrometerse con la respuesta de Martin, agregaré el resto de mis hallazgos POWER()aquí.

Agárrate a tus bragas.

Preámbulo

Primero, les presento la exhibición A, la documentación de MSDN paraPOWER() :

Sintaxis

POWER ( float_expression , y )

Argumentos

float_expression Es una expresión de tipo flotante o de un tipo que se puede convertir implícitamente en flotante.

Tipos de retorno

Igual que float_expression.

Puede concluir al leer la última línea que POWER()es el tipo de retorno FLOAT, pero leer de nuevo. float_expressiones "de tipo flotante o de un tipo que se puede convertir implícitamente en flotante". Entonces, a pesar de su nombre, en float_expressionrealidad puede ser a FLOAT, a DECIMALo an INT. Dado que la salida de POWER()es la misma que la de float_expression, también puede ser uno de esos tipos.

Entonces tenemos una función escalar con tipos de retorno que dependen de la entrada. ¿Podría ser?

Observaciones

Le presento la exhibición B, una prueba que demuestra que POWER()emite su salida a diferentes tipos de datos dependiendo de su entrada .

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;

Los resultados relevantes son:

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

Lo que parece estar sucediendo es que los POWER()moldes float_expressionen el tipo más pequeño que los ataques que, sin incluir BIGINT.

Por lo tanto, SELECT POWER(10.0, 38);falla con un error de desbordamiento porque se 10.0convierte en algo NUMERIC(38, 1)que no es lo suficientemente grande como para contener el resultado de 10 38 . Esto se debe a que 10 38 se expande para tomar 39 dígitos antes del decimal, mientras que NUMERIC(38, 1)puede almacenar 37 dígitos antes del decimal más uno después. Por lo tanto, el valor máximo que NUMERIC(38, 1)puede contener es 10 37 - 0.1.

Armado con este entendimiento, puedo inventar otra falla de desbordamiento de la siguiente manera.

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

Mil millones (en comparación con el billón del primer ejemplo, que se utiliza NUMERIC(38, 0)) es lo suficientemente pequeño como para caber en un INT. Sin embargo, mil millones recaudados a la tercera potencia son demasiado grandes INT, de ahí el error de desbordamiento.

Varias otras funciones exhiben un comportamiento similar, donde su tipo de salida depende de su entrada:

Conclusión

En este caso particular, la solución es usar SELECT POWER(1e1, precision).... Esto funcionará para todas las precisiones posibles desde que 1e1se emite FLOAT, que puede contener números ridículamente grandes .

Dado que estas funciones son tan comunes, es importante comprender que sus resultados pueden ser redondeados o pueden causar errores de desbordamiento debido a su comportamiento. Si espera o confía en un tipo de datos específico para su salida, emita explícitamente la entrada relevante según sea necesario.

Entonces, niños, ahora que saben esto, pueden salir y prosperar.

Nick Chammas
fuente