Pourquoi «SELECT POWER (10.0, 38.0);» génère une erreur de dépassement arithmétique?

15

Je mets à jour mon IDENTITYscript de vérification de débordement pour tenir compte des colonnes DECIMALetNUMERIC IDENTITY .

Dans le cadre de la vérification, je calcule la taille de la plage du type de données pour chaque IDENTITYcolonne; Je l'utilise pour calculer quel pourcentage de cette plage a été épuisé. PourDECIMAL et NUMERIC la taille de cette plage est2 * 10^p - 2pest la précision.

J'ai créé un tas de tables de test avec DECIMALetNUMERIC IDENTITY colonnes et a tenté de calculer leurs gammes comme suit:

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

Cela a généré l'erreur suivante:

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

Je l'ai réduit au IDENTITY colonnes de type DECIMAL(38, 0)(c'est-à-dire avec la précision maximale), j'ai donc essayé le POWER()calcul directement sur cette valeur.

Toutes les requêtes suivantes

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

a également entraîné la même erreur.

  • Pourquoi SQL Server essaie-t-il de convertir la sortie de POWER(), qui est de type FLOAT, en NUMERIC(en particulier lorsqueFLOAT a une priorité plus élevée )?
  • Comment puis-je calculer dynamiquement la plage d'une DECIMALou d' une NUMERICcolonne pour toutes les précisions possibles (y compris p = 38, bien sûr)?
Nick Chammas
la source

Réponses:

18

De la POWERdocumentation :

Syntaxe

POWER ( float_expression , y )

Arguments

float_expression
Est une expression de type float ou d'un type qui peut être implicitement converti en float .

y
Est la puissance à laquelle augmenter l' expression float_expression . y peut être une expression de la catégorie de type de données numérique exacte ou approximative, à l'exception du type de données binaire .

Types de retour

Renvoie le même type que celui soumis dans float_expression . Par exemple, si une décimale (2,0) est soumise en tant qu'expression float, le résultat renvoyé est décimal (2,0).


La première entrée est implicitement convertie en floatsi nécessaire.

Le calcul interne est effectué en utilisant l' floatarithmétique par la fonction standard C Runtime Library (CRT) pow.

La floatsortie de powest ensuite restituée au type de l'opérande de gauche (implicite numeric(3,1)lorsque vous utilisez la valeur littérale 10.0).

L'utilisation d'une explicite floatfonctionne bien dans votre cas:

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

Un résultat exact pour 10 38 ne peut pas être stocké dans un serveur SQL decimal/numericcar il nécessiterait 39 chiffres de précision (1 suivi de 38 zéros). La précision maximale est de 38.

Martin Smith
la source
23

Au lieu de me mêler davantage de la réponse de Martin, je vais ajouter le reste de mes conclusions concernant POWER() ici.

Accrochez-vous à votre culotte.

Préambule

Tout d'abord, je vous présente la pièce A, la documentation MSDN pourPOWER() :

Syntaxe

POWER ( float_expression , y )

Arguments

float_expression Est une expression de type float ou d'un type qui peut être implicitement converti en float.

Types de retour

Identique à float_expression.

Vous pouvez conclure de la lecture de cette dernière ligne que POWER()le type de retour est FLOAT, mais relisez. float_expressionest "de type float ou d'un type qui peut être implicitement converti en float". Ainsi, malgré son nom, float_expressionpeut en fait être un FLOAT, un DECIMALou un INT. Étant donné que la sortie de POWER()est la même que celle de float_expression, elle peut également appartenir à l'un de ces types.

Nous avons donc une fonction scalaire avec des types de retour qui dépendent de l'entrée. Est-ce que ça pourrait être?

Observations

Je vous présente la pièce B, un test qui démontre que POWER()sa sortie est convertie en différents types de données en fonction de son entrée .

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;

Les résultats pertinents sont:

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

Ce qui semble se produire est que les POWER()moulages float_expressiondans le type le plus petit qui correspond, non compris BIGINT.

Par conséquent, SELECT POWER(10.0, 38);échoue avec une erreur de dépassement de capacité, car le 10.0cast NUMERIC(38, 1)n'est pas assez grand pour contenir le résultat de 10 38 . En effet, 10 38 se développe pour prendre 39 chiffres avant la décimale, tandis que NUMERIC(38, 1)peut stocker 37 chiffres avant la décimale plus un après. Par conséquent, la valeur maximale NUMERIC(38, 1)pouvant être maintenue est 10 37 - 0,1.

Fort de cette compréhension, je peux concocter un autre échec de débordement comme suit.

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

Un milliard (par opposition au milliard de dollars du premier exemple, qui est converti en NUMERIC(38, 0)) est juste assez petit pour tenir dans un INT. Un milliard élevé à la troisième puissance, cependant, est trop gros pourINT , d'où l'erreur de débordement.

Plusieurs autres fonctions présentent un comportement similaire, où leur type de sortie dépend de leur entrée:

Conclusion

Dans ce cas particulier, la solution est d'utiliser SELECT POWER(1e1, precision).... Cela fonctionnera pour toutes les précisions possibles, car 1e1obtient cast FLOAT, qui peut contenir des nombres ridiculement grands .

Étant donné que ces fonctions sont si courantes, il est important de comprendre que vos résultats peuvent être arrondis ou provoquer des erreurs de débordement en raison de leur comportement. Si vous attendez ou utilisez un type de données spécifique pour votre sortie, convertissez explicitement l'entrée appropriée si nécessaire.

Alors, les enfants, maintenant que vous savez cela, vous pouvez aller de l'avant et prospérer.

Nick Chammas
la source