Mengapa TSQL mengembalikan nilai yang salah untuk POWER (2., 64.)?


14

select POWER(2.,64.)mengembalikan 18446744073709552000bukan 18446744073709551616. Tampaknya hanya memiliki 16 digit presisi (pembulatan ke-17).

Bahkan dengan membuat presisi eksplisit, select power(cast(2 as numeric(38,0)),cast(64 as numeric(38,0)))ia tetap mengembalikan hasil yang bulat.

Sepertinya ini adalah operasi yang cukup mendasar untuk bisa keluar sembarangan dengan 16 digit presisi seperti ini. Yang tertinggi yang dapat dihitung dengan benar hanya POWER(2.,56.), gagal untuk POWER(2.,57.). Apa yang terjadi disini?

Yang benar-benar mengerikan adalah bahwa 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.;sebenarnya mengembalikan nilai yang benar. Begitu banyak untuk kesederhanaan.


Jawaban:


17

Dari dokumentasi online :

POWER ( float_expression , y )  

Argumen

float_expression Adalah ekspresi dari tipe float atau tipe yang secara implisit dapat dikonversi menjadi float

Implikasinya adalah bahwa apa pun yang Anda lewati sebagai parameter pertama akan secara implisit dilemparkan ke float(53) sebelum fungsi dijalankan. Namun, ini bukan (selalu?) Kasusnya .

Jika itu masalahnya, itu akan menjelaskan hilangnya presisi:

Konversi nilai float yang menggunakan notasi ilmiah menjadi desimal atau numerik dibatasi hanya untuk nilai presisi 17 digit. Nilai apa pun dengan presisi lebih tinggi dari 17 putaran ke nol.

Di sisi lain, literalnya 2.adalah tipe numeric...:

DECLARE @foo sql_variant;
SELECT @foo = 2.;
SELECT SQL_VARIANT_PROPERTY(@foo, 'BaseType');
GO
| (Tidak ada nama kolom) |
| : --------------- |
| numerik |

Aku di sini

... dan operator multiply mengembalikan tipe data dari argumen dengan prioritas yang lebih tinggi .

Tampaknya pada 2016 (SP1), semua presisi dipertahankan:

SELECT @@version;
GO
| (Tidak ada nama kolom) |
| : ------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- ------- |
| Microsoft SQL Server 2016 (SP1) (KB3182545) - 13.0.4001.0 (X64) <br> 28 Okt 2016 18:17:30 <br> Hak cipta (c) Microsoft Corporation <br> Edisi Express (64-bit) pada Windows Server 2012 R2 Standard 6.3 <X64> (Build 9600:) (Hypervisor) <br> |
SELECT POWER(2.,64.);
GO
| (Tidak ada nama kolom) |
| : ------------------- |
| 18446744073709551616 |

Aku di sini

... tetapi pada 2014 (SP2), mereka bukan:

SELECT @@version;
GO
| (Tidak ada nama kolom) |
| : ------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- ------------------------------------ |
| Microsoft SQL Server 2014 (SP2) (KB3171021) - 12.0.5000.0 (X64) <br> 17 Juni 2016 19:14:09 <br> Hak cipta (c) Microsoft Corporation <br> Edisi Express (64-bit) pada Windows NT 6.3 <X64> (Build 9600:) (Hypervisor) <br> |
SELECT POWER(2.,64.);
GO
| (Tidak ada nama kolom) |
| : ------------------- |
| 18446744073709552000 |

Aku di sini


1
Jadi pada dasarnya, fungsi POWER tidak berguna untuk apa pun yang membutuhkan lebih dari 17 digit presisi. Itu sebabnya ia menghasilkan hasil yang tepat untuk POWER(2.,56.) = 72057594037927936tetapi tidak lebih tinggi. Saya kira saya harus menulis fungsi POWER saya sendiri yang hanya berlipat ganda dalam satu lingkaran, lol.
Triynko

14

Hasil dari 2 64 tepat diwakili dalam float(dan realdalam hal ini).

Masalah muncul ketika hasil yang tepat ini dikonversi kembali ke numeric(jenis POWERoperan pertama ).

Sebelum tingkat kompatibilitas database 130 diperkenalkan, SQL Server dibulatkan floatmenjadi numerickonversi implisit hingga maksimum 17 digit.

Di bawah tingkat kompatibilitas 130, sebanyak mungkin presisi dipertahankan selama konversi. Ini didokumentasikan dalam artikel Pangkalan Pengetahuan:

SQL Server 2016 peningkatan dalam menangani beberapa tipe data dan operasi yang tidak umum

Untuk memanfaatkan ini di Azure SQL Database, Anda perlu mengatur COMPATIBILITY_LEVELke 130:

ALTER DATABASE CURRENT SET COMPATIBILITY_LEVEL = 130;

Pengujian beban kerja diperlukan karena pengaturan baru bukanlah obat mujarab. Sebagai contoh:

SELECT POWER(10., 38);

... harus melempar kesalahan karena 10 38 tidak dapat disimpan numeric(presisi maksimum 38). Kesalahan overflow menghasilkan di bawah 120 kompatibilitas, tetapi hasil di bawah 130 adalah:

99999999999999997748809823456034029568 -- (38 digits)

2

Dengan sedikit matematika kita dapat menemukan solusinya. Untuk aneh n:

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

Bahkan untuk n:

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

Salah satu cara untuk menulis itu di T-SQL:

DECLARE @exponent INTEGER = 57;

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

Diuji pada SQL Server 2008, hasilnya adalah 144115188075855872 bukannya 144115188075855870.

Ini berfungsi sampai eksponen 113. Sepertinya NUMERIC (38,0) dapat menyimpan hingga 2 ^ 126 sehingga tidak ada cakupan yang cukup penuh, tetapi rumus dapat dibagi menjadi beberapa bagian jika perlu .


0

Hanya untuk bersenang-senang, solusi CTE rekursif:

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 ;
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.