Tại sao TSQL trả lại giá trị sai cho POWER (2., 64.)?


14

select POWER(2.,64.)trả lại 18446744073709552000thay vì 18446744073709551616. Nó dường như chỉ có 16 chữ số chính xác (làm tròn số 17).

Ngay cả làm cho độ chính xác rõ ràng select power(cast(2 as numeric(38,0)),cast(64 as numeric(38,0)))nó vẫn trả về kết quả làm tròn.

Đây có vẻ như là một thao tác khá cơ bản để nó có thể tự ý bóc ra ở 16 chữ số chính xác như thế này. Cao nhất nó có thể tính toán chính xác là chỉ POWER(2.,56.), thất bại cho POWER(2.,57.). Chuyện gì đang xảy ra ở đây?

Điều thực sự khủng khiếp là 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.;thực sự trả về đúng giá trị. Quá nhiều cho sự căng thẳng.


Câu trả lời:


17

Từ các tài liệu trực tuyến :

POWER ( float_expression , y )  

Tranh luận

float_expression Là một biểu thức của kiểu float hoặc thuộc loại có thể được chuyển đổi hoàn toàn thành float

Hàm ý là bất cứ điều gì bạn vượt qua như tham số đầu tiên sẽ được chuyển hoàn toàn sang một float(53) trước khi hàm được thực thi. Tuy nhiên, đây không phải là (luôn luôn?) Trường hợp .

Nếu đó là trường hợp, nó sẽ giải thích sự mất độ chính xác:

Việc chuyển đổi các giá trị float sử dụng ký hiệu khoa học thành số thập phân hoặc số chỉ được giới hạn ở các giá trị có độ chính xác 17 chữ số. Bất kỳ giá trị nào với độ chính xác cao hơn 17 vòng đến không.

Mặt khác, nghĩa đen 2.là loại numeric:

DECLARE @foo sql_variant;
SELECT @foo = 2.;
SELECT SQL_VARIANT_PROPERTY(@foo, 'BaseType');
GO
| (Không có tên cột) |
| : --------------- |
| số |

dbfiddle ở đây

Mạnh và toán tử nhân trả về kiểu dữ liệu của đối số có độ ưu tiên cao hơn .

Có vẻ như vào năm 2016 (SP1), tất cả độ chính xác được giữ lại:

SELECT @@version;
GO
| (Không có tên cột) |
| : ------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- ------- |
| Microsoft SQL Server 2016 (SP1) (KB3182545) - 13.0.4001.0 (X64) <br> 28 tháng 10 2016 18:17:30 <br> Bản quyền (c) Microsoft Corporation <br> Express Edition (64-bit) trên Windows Server Tiêu chuẩn 2012 R2 6.3 <X64> (Bản dựng 9600 :) (Hypervisor) <br> |
SELECT POWER(2.,64.);
GO
| (Không có tên cột) |
| : ------------------- |
| 18446744073709551616 |

dbfiddle ở đây

Tuy nhiên, vào năm 2014 (SP2), họ không:

SELECT @@version;
GO
| (Không có tên cột) |
| : ------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- ------------------------------------ |
| Microsoft SQL Server 2014 (SP2) (KB3171021) - 12.0.5000.0 (X64) <br> 17 tháng 6 năm 2016 19:14:09 <br> Bản quyền (c) Microsoft Corporation <br> Express Edition (64-bit) trên Windows NT 6.3 <X64> (Bản dựng 9600 :) (Hypervisor) <br> |
SELECT POWER(2.,64.);
GO
| (Không có tên cột) |
| : ------------------- |
| 18446744073709552000 |

dbfiddle ở đây


1
Về cơ bản, hàm POWER là vô dụng đối với mọi thứ đòi hỏi độ chính xác cao hơn 17 chữ số. Đó là lý do tại sao nó tạo ra kết quả đúng cho POWER(2.,56.) = 72057594037927936nhưng không cao hơn. Tôi đoán tôi sẽ phải viết hàm POWER của riêng mình, nó chỉ nhân lên trong một vòng lặp, lol.
Triynko

14

Kết quả của 2 64 là chính xác đại diện trong float(và realcho vấn đề đó).

Vấn đề phát sinh khi kết quả chính xác này được chuyển đổi trở lại numeric(loại POWERtoán hạng đầu tiên ).

Trước khi mức độ tương thích cơ sở dữ liệu 130 được giới thiệu, SQL Server đã làm tròn floatđể numericchuyển đổi ngầm định thành tối đa 17 chữ số.

Dưới mức tương thích 130, độ chính xác nhất có thể được bảo toàn trong quá trình chuyển đổi. Điều này được ghi lại trong bài viết Cơ sở Kiến thức:

SQL Server 2016 cải tiến trong việc xử lý một số loại dữ liệu và các hoạt động không phổ biến

Để tận dụng lợi thế này trong Cơ sở dữ liệu SQL Azure, bạn cần đặt thành COMPATIBILITY_LEVEL130:

ALTER DATABASE CURRENT SET COMPATIBILITY_LEVEL = 130;

Kiểm tra khối lượng công việc là cần thiết bởi vì sự sắp xếp mới không phải là thuốc chữa bách bệnh. Ví dụ:

SELECT POWER(10., 38);

... Nên ném lỗi vì 10 38 không thể được lưu trữ numeric(độ chính xác tối đa là 38). Một lỗi tràn kết quả dưới khả năng tương thích 120, nhưng kết quả dưới 130 là:

99999999999999997748809823456034029568 -- (38 digits)

2

Với một chút toán chúng ta có thể tìm ra cách giải quyết. Đối với lẻ n:

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

Cho thậm chí n:

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

Một cách để viết điều đó trong T-SQL:

DECLARE @exponent INTEGER = 57;

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

Đã thử nghiệm trên SQL Server 2008, kết quả là 144115188075855872 thay vì 144115188075855870.

Công cụ này hoạt động đến hết số mũ 113. Có vẻ như SỐ MỘT (38,0) có thể lưu trữ tới 2 ^ 126 nên không có phạm vi bảo hiểm khá đầy đủ, nhưng công thức có thể được chia thành nhiều phần nếu cần thiết .


0

Chỉ để cho vui, một giải pháp CTE đệ quy:

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 ;
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.