Pourquoi TSQL renvoie-t-il la mauvaise valeur pour POWER (2., 64.)?


14

select POWER(2.,64.)renvoie 18446744073709552000au lieu de 18446744073709551616. Il semble n'avoir que 16 chiffres de précision (arrondi au 17).

Même en rendant la précision explicite, select power(cast(2 as numeric(38,0)),cast(64 as numeric(38,0)))elle renvoie toujours le résultat arrondi.

Cela semble être une opération assez basique pour qu'il s'écaille arbitrairement à 16 chiffres de précision comme celui-ci. Le plus élevé qu'il peut calculer correctement est seulement POWER(2.,56.), à défaut POWER(2.,57.). Qu'est-ce qui se passe ici?

Ce qui est vraiment horrible, c'est que cela select 2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.*2.;renvoie en fait la bonne valeur. Voilà pour la justesse.


Réponses:


17

De la documentation en ligne :

POWER ( float_expression , y )  

Arguments

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

L'implication est que tout ce que vous passez en tant que premier paramètre va être implicitement converti en a float(53) avant l'exécution de la fonction. Mais ce n'est pas (toujours?) Le cas .

Si c'était le cas, cela expliquerait la perte de précision:

La conversion des valeurs flottantes qui utilisent la notation scientifique en décimal ou numérique est limitée aux valeurs de précision à 17 chiffres uniquement. Toute valeur avec une précision supérieure à 17 tours à zéro.

Par contre, le littéral 2.est de type numeric…:

DECLARE @foo sql_variant;
SELECT @foo = 2.;
SELECT SQL_VARIANT_PROPERTY(@foo, 'BaseType');
GO
| (Pas de nom de colonne) |
| : --------------- |
| numérique |

dbfiddle ici

… Et l'opérateur multiplier renvoie le type de données de l'argument avec la priorité la plus élevée .

Il apparaît que sur 2016 (SP1), toute la précision est conservée:

SELECT @@version;
GO
| (Pas de nom de colonne) |
| : ------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- ------- |
| Microsoft SQL Server 2016 (SP1) (KB3182545) - 13.0.4001.0 (X64) <br> 28 octobre 2016 18:17:30 <br> Copyright (c) Microsoft Corporation <br> Express Edition (64 bits) sur Windows Server 2012 R2 Standard 6.3 <X64> (Build 9600:) (Hyperviseur) <br> |
SELECT POWER(2.,64.);
GO
| (Pas de nom de colonne) |
| : ------------------- |
| 18446744073709551616 |

dbfiddle ici

… Mais en 2014 (SP2), ils ne sont pas:

SELECT @@version;
GO
| (Pas de nom de colonne) |
| : ------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- ------------------------------------ |
| Microsoft SQL Server 2014 (SP2) (KB3171021) - 12.0.5000.0 (X64) <br> 17 juin 2016 19:14:09 <br> Copyright (c) Microsoft Corporation <br> Express Edition (64 bits) sur Windows NT 6.3 <X64> (Build 9600:) (Hyperviseur) <br> |
SELECT POWER(2.,64.);
GO
| (Pas de nom de colonne) |
| : ------------------- |
| 18446744073709552000 |

dbfiddle ici


1
Donc, fondamentalement, la fonction POWER est inutile pour tout ce qui nécessite plus de 17 chiffres de précision. C'est pourquoi il produit le bon résultat POWER(2.,56.) = 72057594037927936mais pas plus. Je suppose que je vais devoir écrire ma propre fonction POWER qui se multiplie juste en boucle, lol.
Triynko

14

Le résultat de 2 64 est exactement représentable en float(et reald'ailleurs).

Le problème se pose lorsque ce résultat précis est reconverti en numeric(le type du premier POWERopérande).

Avant l'introduction du niveau de compatibilité de base de données 130, SQL Server arrondissait floataux numericconversions implicites à un maximum de 17 chiffres.

Sous le niveau de compatibilité 130, autant de précision que possible est préservée pendant la conversion. Ceci est documenté dans l'article de la base de connaissances:

Améliorations de SQL Server 2016 dans la gestion de certains types de données et opérations inhabituelles

Pour en tirer parti dans Azure SQL Database, vous devez définir la COMPATIBILITY_LEVELsur 130:

ALTER DATABASE CURRENT SET COMPATIBILITY_LEVEL = 130;

Des tests de charge de travail sont nécessaires car le nouvel arrangement n'est pas une panacée. Par exemple:

SELECT POWER(10., 38);

... devrait générer une erreur car 10 38 ne peut pas être stocké numeric(précision maximale de 38). Une erreur de débordement entraîne une compatibilité inférieure à 120, mais le résultat inférieur à 130 est:

99999999999999997748809823456034029568 -- (38 digits)

2

Avec un peu de mathématiques, nous pouvons trouver une solution de contournement. Pour les impairs n:

2 ^ n 
= 2 ^ (2k + 1)
= 2 * (2 ^ 2k)
= 2 * (2 ^ k) * (2 ^ k)

Pour même n:

2 ^ n 
= 2 ^ (2k)
= 1 * (2 ^ 2k)
= 1 * (2 ^ k) * (2 ^ k)

Une façon d'écrire cela en T-SQL:

DECLARE @exponent INTEGER = 57;

SELECT (1 + @exponent % 2) * POWER(2., FLOOR(0.5 * @exponent)) * POWER(2., FLOOR(0.5 * @exponent));

Testé sur SQL Server 2008, le résultat est 144115188075855872 au lieu de 144115188075855870.

Cela fonctionne jusqu'à un exposant de 113. Il semble qu'un NUMERIC (38,0) puisse stocker jusqu'à 2 ^ 126, il n'y a donc pas une couverture complète, mais la formule pourrait être divisée en plusieurs morceaux si nécessaire .


0

Juste pour le plaisir, une solution CTE récursive:

with 
  prm (p, e) as           -- parameters, to evaluate: p**e
    (select 2, 64),       -- (2 ** 64)  
  pow (power, exp) as 
    (select cast(p as numeric(30,0)), 
            e
     from prm 
     union all 
     select cast(power * power * (case when exp % 2 = 0 then 1 else p end) 
                 as numeric(30,0)), 
            exp / 2 
     from prm, pow 
     where exp > 1 
    ) 
select power 
from pow 
where exp = 1 ;
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.