Perché "SELECT POWER (10.0, 38.0);" genera un errore di overflow aritmetico?


15

Sto aggiornando il mio IDENTITYscript di controllo di overflow per tenere conto delle colonne DECIMALeNUMERIC IDENTITY .

Come parte del controllo computo la dimensione dell'intervallo del tipo di dati per ogni IDENTITYcolonna; Lo uso per calcolare quale percentuale di tale intervallo è stata esaurita. Perché DECIMALe NUMERIC la dimensione di quella gamma è2 * 10^p - 2 dov'è pla precisione.

Ho creato un gruppo di tabelle di test con DECIMALe NUMERIC IDENTITYcolonne e ho provato a calcolare i loro intervalli come segue:

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

Questo ha generato il seguente errore:

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

L'ho ristretto alle IDENTITYcolonne di tipo DECIMAL(38, 0)(cioè con la massima precisione), quindi ho provato il POWER()calcolo direttamente su quel valore.

Tutte le seguenti query

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

ha comportato anche lo stesso errore.

  • Perché SQL Server tenta di convertire l'output di POWER(), che è di tipo FLOAT, in NUMERIC(soprattutto quando FLOATha una precedenza più alta )?
  • Come posso calcolare dinamicamente l'intervallo di una DECIMALo NUMERICcolonna per tutte le possibili precisazioni (incluso p = 38, ovviamente)?

Risposte:


18

Dalla POWERdocumentazione :

Sintassi

POWER ( float_expression , y )

argomenti

float_expression
È un espressione del tipo galleggiante o di un tipo che può essere convertito in modo implicito float .

y
È il potere a cui aumentare float_expression . y può essere un'espressione della categoria del tipo di dati numerico esatto o approssimativo, ad eccezione del tipo di dati bit .

Tipi di restituzione

Restituisce lo stesso tipo inviato in float_expression . Ad esempio, se un decimale (2,0) viene inviato come float_expression, il risultato restituito è decimale (2,0).


Il primo input viene implicitamente lanciato, floatse necessario.

Il calcolo interno viene eseguito utilizzando l' floataritmetica dalla funzione C Runtime Library (CRT) standard pow.

L' floatoutput da powviene quindi riportato al tipo di operando di sinistra (implicito numeric(3,1)quando si utilizza il valore letterale 10.0).

L'uso di un esplicito floatfunziona bene nel tuo caso:

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

Un risultato esatto per 10 38 non può essere archiviato in un SQL Server decimal/numericperché richiederebbe 39 cifre di precisione (1 seguito da 38 zeri). La massima precisione è 38.


23

Invece di intromettermi ulteriormente con la risposta di Martin, aggiungerò il resto delle mie scoperte riguardanti POWER()qui.

Resisti ai tuoi mutandoni.

Preambolo

Innanzitutto, vi presento l'esposizione A, la documentazione MSDN perPOWER() :

Sintassi

POWER ( float_expression , y )

argomenti

float_expression Espressione di tipo float o di un tipo che può essere implicitamente convertita in float.

Tipi di restituzione

Come float_expression.

Dalla lettura dell'ultima riga si può concludere che POWER()è il tipo restituito FLOAT, ma rileggere. float_expressionè "di tipo float o di un tipo che può essere implicitamente convertito in float". Quindi, nonostante il suo nome, float_expressionpotrebbe effettivamente essere un FLOAT, un DECIMALo un INT. Poiché l'output di POWER()è uguale a quello di float_expression, anche questo può essere uno di quei tipi.

Quindi abbiamo una funzione scalare con tipi di ritorno che dipendono dall'input. Potrebbe essere?

osservazioni

Vi presento la mostra B, un test che dimostra che POWER()trasmette il suo output a diversi tipi di dati a seconda del suo input .

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;

I risultati rilevanti sono:

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

Ciò che sembra accadere è che si POWER()proietta float_expressionnel tipo più piccolo che gli si adatta, escluso BIGINT.

Pertanto, SELECT POWER(10.0, 38);fallisce con un errore di overflow perché 10.0viene lanciato su un valore NUMERIC(38, 1)non abbastanza grande da contenere il risultato di 10 38 . Questo perché 10 38 si espande per prendere 39 cifre prima del decimale, mentre NUMERIC(38, 1)può memorizzare 37 cifre prima del decimale più uno dopo di esso. Pertanto, il valore massimo che NUMERIC(38, 1)può contenere è 10 37 - 0,1.

Grazie a questa comprensione, posso inventare un altro errore di overflow come segue.

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

Un miliardo (a differenza di un trilione dal primo esempio, a cui viene lanciato NUMERIC(38, 0)) è appena abbastanza piccolo da stare in un INT. Un miliardo elevato alla terza potenza, tuttavia, è troppo grande per INT, quindi l'errore di overflow.

Diverse altre funzioni mostrano un comportamento simile, in cui il loro tipo di output dipende dal loro input:

Conclusione

In questo caso particolare, la soluzione è usare SELECT POWER(1e1, precision)... . Questo funzionerà per tutte le possibili precisazioni da quando 1e1viene lanciato FLOAT, che può contenere numeri ridicolmente grandi .

Poiché queste funzioni sono così comuni, è importante capire che i risultati potrebbero essere arrotondati o causare errori di overflow a causa del loro comportamento. Se ti aspetti o fai affidamento su un tipo di dati specifico per il tuo output, trasmetti esplicitamente l'input pertinente come necessario.

Quindi ragazzi, ora che lo sapete, potete andare avanti e prosperare.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.