TSQL neden GÜÇ için yanlış değer döndürüyor (2., 64.)?


14

select POWER(2.,64.)18446744073709552000yerine döner 18446744073709551616. Sadece 16 basamaklı bir hassasiyete sahip (17'yi yuvarlama).

Hassasiyeti açıkça select power(cast(2 as numeric(38,0)),cast(64 as numeric(38,0)))ortaya koysa bile , yuvarlatılmış sonucu döndürür.

Bu, 16 haneli hassas bir şekilde keyfi olarak ortaya çıkması için oldukça basit bir işlem gibi görünüyor. Doğru hesaplayabileceği en yüksek değer sadece POWER(2.,56.)başarısızlıktır POWER(2.,57.). Burada neler oluyor?

Gerçekten korkunç olan, 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.;aslında doğru değeri döndürmesidir. Kesinlik için çok fazla.


Yanıtlar:


17

Gönderen online belgeler :

POWER ( float_expression , y )  

Argümanlar

float_expression float türünün veya örtük olarak float'a dönüştürülebilecek türün bir ifadesidir

Bunun anlamı, ilk parametre olarak ilettiğiniz her ne olursa olsun , işlev yürütülmeden float(53) önce örtük olarak a'ya aktarılır. Ancak, bu (her zaman?) Durum değildir .

Durum böyle olsaydı, hassasiyet kaybını açıklardı:

Bilimsel gösterimi kullanan kayan değerlerin ondalık veya sayıya dönüştürülmesi yalnızca 17 basamaklı kesinlik değerleriyle sınırlıdır. Hassasiyeti 17'den yüksek olan herhangi bir değer sıfıra yuvarlar.

Öte yandan değişmez 2.tiptir numeric…:

DECLARE @foo sql_variant;
SELECT @foo = 2.;
SELECT SQL_VARIANT_PROPERTY(@foo, 'BaseType');
GO
| (Sütun adı yok) |
| : --------------- |
| sayısal |

dbfiddle burada

… Ve çarpma işleci , daha yüksek önceliğe sahip bağımsız değişkenin veri türünü döndürür .

Görünüşe göre 2016'da (SP1), tüm hassasiyet korunuyor:

SELECT @@version;
GO
| (Sütun adı yok) |
| : ------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- ------- |
| Microsoft SQL Server 2016 (SP1) (KB3182545) - 13.0.4001.0 (X64) <br> 28 Ekim 2016 18:17:30 <br> Telif Hakkı (c) Windows Server'da Microsoft Corporation <br> Express Edition (64 bit) 2012 R2 Standart 6.3 <X64> (Derleme 9600:) (Hipervizör) <br> |
SELECT POWER(2.,64.);
GO
| (Sütun adı yok) |
| : ------------------- |
| 18446744073709551616 |

dbfiddle burada

… Ancak 2014'te (SP2):

SELECT @@version;
GO
| (Sütun adı yok) |
| : ------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- ------------------------------------ |
| Microsoft SQL Server 2014 (SP2) (KB3171021) - 12.0.5000.0 (X64) <br> 17 Haziran 2016 19:14:09 <br> Telif Hakkı (c) Windows NT üzerinde Microsoft Corporation <br> Express Edition (64-bit) 6.3 <X64> (Derleme 9600:) (Hipervizör) <br> |
SELECT POWER(2.,64.);
GO
| (Sütun adı yok) |
| : ------------------- |
| 18446744073709552000 |

dbfiddle burada


1
Temel olarak, GÜÇ işlevi 17 basamaktan fazla hassasiyet gerektiren herhangi bir şey için işe yaramaz. Bu yüzden doğru sonucu verir, POWER(2.,56.) = 72057594037927936ancak daha yüksek değildir. Sanırım sadece bir döngüde çoğalan kendi GÜÇ işlevimi yazmak zorunda kalacağım, lol.
Triynko

14

2 64'ün sonucu tam olarak temsil edilebilir float(ve realbu konuda).

Bu kesin sonuç numeric(ilk POWERişlenenin türü) haline dönüştürüldüğünde sorun ortaya çıkar .

Veritabanı uyumluluk düzeyi 130 bulunmadan önce, SQL Server yuvarlak floatiçin numeric17 basamaklı bir maksimum örtülü dönüşüm.

Uyumluluk seviyesi 130 altında, dönüştürme sırasında mümkün olduğu kadar çok hassasiyet korunur. Bu, Bilgi Bankası makalesinde belgelenmiştir:

Bazı veri türlerinin ve olağandışı işlemlerin işlenmesinde SQL Server 2016 geliştirmeleri

Azure SQL Veritabanı'nda bundan yararlanmak için COMPATIBILITY_LEVEL, 130 değerini ayarlamanız gerekir :

ALTER DATABASE CURRENT SET COMPATIBILITY_LEVEL = 130;

Yeni düzenleme her derde deva olmadığı için iş yükü testi gereklidir. Örneğin:

SELECT POWER(10., 38);

... 10 38 depolanamadığı için bir hata atmalıdır numeric(maksimum 38 hassasiyet). Bir taşma hatası 120 uyumluluğu altında, ancak 130'un altında sonuç:

99999999999999997748809823456034029568 -- (38 digits)

2

Biraz matematikle bir çözüm bulabiliriz. Garip için n:

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

Hatta n:

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

Bunu T-SQL'de yazmanın bir yolu:

DECLARE @exponent INTEGER = 57;

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

SQL Server 2008'de test edildiğinde sonuç 144115188075855870 yerine 144115188075855872'dir.

Bu, 113 üssüne kadar çalışır. NUMERIC (38,0) 2 ^ 126'ya kadar saklayabilir, bu nedenle tam kapsama alanı yoktur, ancak formül gerekirse daha fazla parçaya bölünebilir .


0

Sadece eğlence için, özyinelemeli bir CTE çözümü:

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 ;
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.