เหตุใด TSQL จึงส่งกลับค่าที่ไม่ถูกต้องสำหรับ POWER (2. , 64)


14

select POWER(2.,64.)ส่งกลับแทน18446744073709552000 18446744073709551616ดูเหมือนว่าจะมีความแม่นยำเพียง 16 หลัก (ปัดเศษที่ 17)

แม้จะทำให้ความแม่นยำชัดเจนselect power(cast(2 as numeric(38,0)),cast(64 as numeric(38,0)))มันยังคงส่งกลับผลลัพธ์ที่ถูกปัดเศษ

ดูเหมือนว่าเป็นการดำเนินการขั้นพื้นฐานที่สวยงามที่จะแสดงผลออกมาอย่างแม่นยำด้วยความแม่นยำ 16 หลักเช่นนี้ สูงสุดที่จะสามารถคำนวณได้อย่างถูกต้องเป็นเพียงความล้มเหลวสำหรับPOWER(2.,56.) POWER(2.,57.)เกิดขึ้นที่นี่คืออะไร?

สิ่งที่น่ากลัวจริงๆก็คือการ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.;คืนค่าที่เหมาะสม มากสำหรับความอดทน


คำตอบ:


17

จากเอกสารออนไลน์ :

POWER ( float_expression , y )  

ข้อโต้แย้ง

float_expression คือการแสดงออกของประเภทลอยหรือประเภทที่สามารถแปลงโดยปริยายไปลอย

ความหมายก็คือสิ่งที่คุณผ่านเป็นพารามิเตอร์แรกจะถูกโยนไปโดยปริยายfloat(53) ก่อนที่จะมีการดำเนินการฟังก์ชั่น อย่างไรก็ตามนี่ไม่ใช่ (เสมอ?) กรณีนี้

หากเป็นกรณีนี้มันจะอธิบายการสูญเสียความแม่นยำ:

การแปลงค่าทศนิยมที่ใช้เครื่องหมายทางวิทยาศาสตร์เป็นทศนิยมหรือตัวเลขจะถูก จำกัด เฉพาะค่าความแม่นยำ 17 หลักเท่านั้น ค่าใด ๆ ที่มีความแม่นยำสูงกว่า 17 รอบถึงศูนย์

ในทางตรงกันข้ามตัวอักษร2.เป็นประเภทnumeric... :

DECLARE @foo sql_variant;
SELECT @foo = 2.;
SELECT SQL_VARIANT_PROPERTY(@foo, 'BaseType');
GO
| (ไม่มีชื่อคอลัมน์)
| : --------------- |
| ตัวเลข |

dbfiddle ที่นี่

... และผู้ประกอบการคูณกลับชนิดข้อมูลของการโต้แย้งที่มีลำดับความสำคัญสูง

ดูเหมือนว่าในปี 2559 (SP1) ความแม่นยำทั้งหมดจะยังคงอยู่:

SELECT @@version;
GO
| (ไม่มีชื่อคอลัมน์)
| : ------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- ------- |
| Microsoft SQL Server 2016 (SP1) (KB3182545) - 13.0.4001.0 (X64) <br> 28 ตุลาคม 2559 18:17:30 น. - ลิขสิทธิ์ (c) Microsoft Corporation รุ่น Express (64 บิต) บน Windows Server 2012 R2 มาตรฐาน 6.3 <X64> (รุ่น 9600:) (Hypervisor) <br> |
SELECT POWER(2.,64.);
GO
| (ไม่มีชื่อคอลัมน์)
| : ------------------- |
| 18446744073709551616 |

dbfiddle ที่นี่

… แต่ในปี 2014 (SP2) พวกเขาไม่ใช่:

SELECT @@version;
GO
| (ไม่มีชื่อคอลัมน์)
| : ------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- ------------------------------------ |
| Microsoft SQL Server 2014 (SP2) (KB3171021) - 12.0.5000.0 (X64) <br> 17 มิถุนายน 2016 19:14:09 - ลิขสิทธิ์ (c) Microsoft Corporation รุ่น Express (64 บิต) บน Windows NT 6.3 <X64> (รุ่น 9600:) (Hypervisor) <br> |
SELECT POWER(2.,64.);
GO
| (ไม่มีชื่อคอลัมน์)
| : ------------------- |
| 18446744073709552000 |

dbfiddle ที่นี่


1
ดังนั้นฟังก์ชั่น POWER นั้นไม่มีประโยชน์อะไรกับสิ่งที่ต้องการความแม่นยำมากกว่า 17 หลัก นั่นเป็นเหตุผลที่ทำให้เกิดผลลัพธ์ที่ถูกต้องPOWER(2.,56.) = 72057594037927936แต่ไม่สูงขึ้น ฉันเดาว่าฉันจะต้องเขียนฟังก์ชั่น POWER ของตัวเองที่เพิ่มทวีคูณเป็นวง lol
Triynko

14

ผลลัพธ์ของ 2 64สามารถแสดงได้อย่างชัดเจนในfloat(และrealสำหรับเรื่องนั้น)

ปัญหาเกิดขึ้นเมื่อผลลัพธ์ที่แม่นยำนี้ถูกแปลงกลับเป็นnumeric(ชนิดของPOWERตัวถูกดำเนินการแรก)

ก่อนที่จะมีการนำระดับความเข้ากันได้ของฐานข้อมูล 130 มาใช้ SQL Server floatจะปัดเศษเพื่อnumericแปลงเป็นค่าสูงสุด 17 หลัก

ภายใต้ความเข้ากันได้ระดับ 130 ความแม่นยำมากที่สุดเท่าที่จะเป็นไปได้จะถูกเก็บไว้ในระหว่างการแปลง นี่คือเอกสารในบทความฐานความรู้:

การปรับปรุง SQL Server 2016 ในการจัดการชนิดข้อมูลบางอย่างและการดำเนินการที่ผิดปกติ

ในการใช้ประโยชน์จากสิ่งนี้ในฐานข้อมูล Azure SQL คุณต้องตั้งค่าเป็นCOMPATIBILITY_LEVEL130:

ALTER DATABASE CURRENT SET COMPATIBILITY_LEVEL = 130;

การทดสอบเวิร์กโหลดเป็นสิ่งจำเป็นเนื่องจากการจัดเรียงใหม่ไม่ใช่ยาครอบจักรวาล ตัวอย่างเช่น:

SELECT POWER(10., 38);

... ควรโยนข้อผิดพลาดเพราะ 10 38ไม่สามารถเก็บไว้ในnumeric(ความแม่นยำสูงสุด 38) ข้อผิดพลาดโอเวอร์โฟลว์ภายใต้ความเข้ากันได้ 120 แต่ผลลัพธ์ภายใต้ 130 คือ:

99999999999999997748809823456034029568 -- (38 digits)

2

ด้วยคณิตศาสตร์เล็กน้อยเราสามารถหาวิธีแก้ปัญหา สำหรับคี่n:

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

สำหรับคู่n:

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

วิธีหนึ่งในการเขียนใน T-SQL:

DECLARE @exponent INTEGER = 57;

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

ทดสอบกับ SQL Server 2008 แล้วผลลัพธ์คือ 144115188075855872 แทนที่จะเป็น 144115188075855870

วิธีนี้ใช้งานได้จนถึงเลขยกกำลัง 113 ซึ่งดูเหมือนว่า NUMERIC (38,0) สามารถเก็บได้มากถึง 2 ^ 126 ดังนั้นจึงไม่มีการครอบคลุมที่สมบูรณ์ แต่สูตรสามารถแบ่งออกเป็นชิ้น ๆ ได้หากจำเป็น .


0

เพื่อความสนุก CTE แบบวนซ้ำ:

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 ;
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.