Perché TSQL restituisce il valore errato per POWER (2., 64.)?


14

select POWER(2.,64.)restituisce 18446744073709552000invece di 18446744073709551616. Sembra avere solo 16 cifre di precisione (arrotondando il 17).

Pur rendendo esplicita la precisione select power(cast(2 as numeric(38,0)),cast(64 as numeric(38,0))), restituisce comunque il risultato arrotondato.

Questa sembra un'operazione piuttosto semplice per sfaldarsi arbitrariamente a 16 cifre di precisione come questa. Il massimo che può calcolare correttamente è solo POWER(2.,56.), non riuscendo POWER(2.,57.). Cosa sta succedendo qui?

La cosa veramente terribile è che in 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.;realtà restituisce il giusto valore. Questo per quanto riguarda la terseness.


Risposte:


17

Dalla documentazione online :

POWER ( float_expression , y )  

argomenti

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

L'implicazione è che qualunque cosa passi come primo parametro verrà implicitamente lanciata su a float(53) prima dell'esecuzione della funzione. Tuttavia, questo non è (sempre?) Il caso .

Se così fosse, spiegherebbe la perdita di precisione:

La conversione di valori float che utilizzano la notazione scientifica in decimale o numerico è limitata ai soli valori di precisione di 17 cifre. Qualsiasi valore con precisione superiore a 17 colpi a zero.

D'altra parte, il letterale 2.è tipo numeric...:

DECLARE @foo sql_variant;
SELECT @foo = 2.;
SELECT SQL_VARIANT_PROPERTY(@foo, 'BaseType');
GO
| (Nessun nome di colonna) |
| : --------------- |
| numerico |

dbfiddle qui

... e l'operatore moltiplica restituisce il tipo di dati dell'argomento con la precedenza più alta .

Sembra che nel 2016 (SP1) venga mantenuta tutta la precisione:

SELECT @@version;
GO
| (Nessun nome di colonna) |
| : ------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- ------- |
| Microsoft SQL Server 2016 (SP1) (KB3182545) - 13.0.4001.0 (X64) <br> 28 ottobre 2016 18:17:30 <br> Copyright (c) Microsoft Corporation <br> Express Edition (64-bit) su Windows Server 2012 R2 Standard 6.3 <X64> (Build 9600:) (Hypervisor) <br> |
SELECT POWER(2.,64.);
GO
| (Nessun nome di colonna) |
| : ------------------- |
| 18446744073709551616 |

dbfiddle qui

... ma nel 2014 (SP2) non sono:

SELECT @@version;
GO
| (Nessun nome di colonna) |
| : ------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- ------------------------------------ |
| Microsoft SQL Server 2014 (SP2) (KB3171021) - 12.0.5000.0 (X64) <br> 17 giugno 2016 19:14:09 <br> Copyright (c) Microsoft Corporation <br> Express Edition (64-bit) su Windows NT 6.3 <X64> (Build 9600:) (Hypervisor) <br> |
SELECT POWER(2.,64.);
GO
| (Nessun nome di colonna) |
| : ------------------- |
| 18446744073709552000 |

dbfiddle qui


1
Quindi, fondamentalmente, la funzione POWER è inutile per tutto ciò che richiede più di 17 cifre di precisione. Ecco perché produce il risultato giusto POWER(2.,56.) = 72057594037927936ma non superiore. Immagino che dovrò scrivere la mia funzione POWER che si moltiplica in un ciclo, lol.
Triynko,

14

Il risultato di 2 64 è esattamente rappresentabile in float(e del realresto).

Il problema sorge quando questo risultato preciso viene riconvertito in numeric(il tipo del primo POWERoperando).

Prima dell'introduzione del livello di compatibilità del database 130, SQL Server si arrotondava floatper numericconvertire in modo implicito un massimo di 17 cifre.

Sotto il livello di compatibilità 130, durante la conversione viene preservata la massima precisione possibile. Questo è documentato nell'articolo della Knowledge Base:

Miglioramenti di SQL Server 2016 nella gestione di alcuni tipi di dati e operazioni non comuni

Per trarne vantaggio nel database SQL di Azure, è necessario impostare COMPATIBILITY_LEVELsu 130:

ALTER DATABASE CURRENT SET COMPATIBILITY_LEVEL = 130;

Il test del carico di lavoro è necessario perché la nuova disposizione non è una panacea. Per esempio:

SELECT POWER(10., 38);

... dovrebbe generare un errore perché non è possibile memorizzare 10 38numeric (precisione massima di 38). Un errore di overflow risulta sotto 120 compatibilità, ma il risultato sotto 130 è:

99999999999999997748809823456034029568 -- (38 digits)

2

Con un po 'di matematica possiamo trovare una soluzione alternativa. Per dispari n:

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

Per pari n:

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

Un modo per scriverlo in T-SQL:

DECLARE @exponent INTEGER = 57;

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

Testato su SQL Server 2008, il risultato è 144115188075855872 anziché 144115188075855870.

Funziona fino a un esponente di 113. Sembra che un NUMERICO (38,0) possa memorizzare fino a 2 ^ 126 quindi non c'è copertura abbastanza completa, ma la formula potrebbe essere divisa in più pezzi se necessario .


0

Solo per divertimento, una soluzione CTE ricorsiva:

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 ;
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.