Tại sao, LỰA CHỌN POWER POWER (10.0, 38.0); Hãy đưa ra lỗi tràn số học?


15

Tôi đang cập nhật tập lệnh kiểm tra tràn của tôiIDENTITY vào tài khoản DECIMALNUMERIC IDENTITYcác cột .

Là một phần của kiểm tra, tôi tính kích thước của phạm vi loại dữ liệu cho mỗi IDENTITYcột; Tôi sử dụng nó để tính toán bao nhiêu phần trăm của phạm vi đó đã cạn kiệt. Cho DECIMALNUMERIC kích thước của phạm vi đó là2 * 10^p - 2 nơi pchính xác.

Tôi đã tạo ra một loạt các bảng thử nghiệm với DECIMALNUMERIC IDENTITYcác cột và cố gắng tính toán phạm vi của chúng như sau:

SELECT POWER(10.0, precision)
FROM sys.columns
WHERE 
       is_identity = 1
   AND type_is_decimal_or_numeric
;

Điều này đã ném lỗi sau:

Msg 8115, Level 16, State 6, Line 1
Arithmetic overflow error converting float to data type numeric. 

Tôi đã thu hẹp nó xuống các IDENTITYcột loại DECIMAL(38, 0)(nghĩa là với độ chính xác tối đa), vì vậy tôi đã thử POWER()tính toán trực tiếp trên giá trị đó.

Tất cả các truy vấn sau đây

SELECT POWER(10.0, 38.0);
SELECT CONVERT(FLOAT, (POWER(10.0, 38.0)));
SELECT CAST(POWER(10.0, 38.0) AS FLOAT);

cũng dẫn đến lỗi tương tự.

  • Tại sao SQL Server cố gắng chuyển đổi đầu ra POWER()của loại FLOAT, thành NUMERIC(đặc biệt là khi FLOATcó độ ưu tiên cao hơn )?
  • Làm cách nào tôi có thể tính toán linh hoạt phạm vi của một DECIMALhoặc NUMERICcột cho tất cả các giới hạn có thể ( p = 38tất nhiên bao gồm cả )?

Câu trả lời:


18

Từ POWERtài liệu :

Cú pháp

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 .

y
Là sức mạnh để nâng float_expression . y có thể là một biểu thức của thể loại dữ liệu số chính xác hoặc gần đúng, ngoại trừ loại dữ liệu bit .

Các loại trả về

Trả về cùng loại như đã gửi trong float_expression . Ví dụ: nếu một số thập phân (2,0) được gửi dưới dạng float_expression, kết quả trả về là số thập phân (2,0).


Đầu vào đầu tiên được ngầm định sử dụng floatnếu cần thiết.

Việc tính toán nội bộ được thực hiện bằng cách sử dụng floatsố học theo hàm CRT Library (CRT) tiêu chuẩn pow.

Đầu floatra từ powsau đó được chuyển trở lại loại toán hạng bên trái (ngụ ý là numeric(3,1)khi bạn sử dụng giá trị bằng chữ 10.0).

Sử dụng một floatcông việc rõ ràng hoạt động tốt trong trường hợp của bạn:

SELECT POWER(1e1, 38);
SELECT POWER(CAST(10 as float), 38.0);

Một kết quả chính xác cho 10 38 không thể được lưu trữ trong Máy chủ SQL decimal/numericvì nó sẽ yêu cầu 39 chữ số chính xác (1 theo sau là 38 số không). Độ chính xác tối đa là 38.


23

Thay vì can thiệp với câu trả lời của Martin hơn nữa, tôi sẽ thêm phần còn lại của những phát hiện của tôi POWER()vào đây.

Giữ chặt lấy quần lót của bạn.

Lời nói đầu

Đầu tiên, tôi giới thiệu với bạn triển lãm A, tài liệu MSDN choPOWER() :

Cú pháp

POWER ( float_expression , y )

Tranh luận

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

Các loại trả về

Tương tự như float_expression.

Bạn có thể kết luận từ việc đọc dòng cuối cùng đó POWER()là loại trả về FLOAT, nhưng đọc lại. float_expressionlà "thuộc loại float hoặc thuộc loại có thể được chuyển đổi hoàn toàn thành float". Vì vậy, mặc dù tên của nó, float_expressionthực sự có thể là a FLOAT, a DECIMALhoặc an INT. Vì đầu ra của POWER()giống như của float_expressionnó, nó cũng có thể là một trong những loại đó.

Vì vậy, chúng ta có một hàm vô hướng với các kiểu trả về phụ thuộc vào đầu vào. Nó có thể là?

Quan sát

Tôi giới thiệu với bạn triển lãm B, một thử nghiệm chứng minh rằng POWER()đưa đầu ra của nó sang các loại dữ liệu khác nhau tùy thuộc vào đầu vào của nó .

SELECT 
    POWER(10, 3)             AS int
  , POWER(1000000000000, 3)  AS numeric0     -- one trillion
  , POWER(10.0, 3)           AS numeric1
  , POWER(10.12305, 3)       AS numeric5
  , POWER(1e1, 3)            AS float
INTO power_test;

EXECUTE sp_help power_test;

DROP TABLE power_test;

Các kết quả có liên quan là:

Column_name    Type      Length    Prec     Scale
-------------------------------------------------
int            int       4         10       0
numeric0       numeric   17        38       0
numeric1       numeric   17        38       1
numeric5       numeric   17        38       5
float          float     8         53       NULL

Có gì dường như xảy ra là POWER()dàn diễn viên float_expressionvào loại nhỏ nhất mà phù hợp nó, không bao gồm BIGINT.

Do đó, SELECT POWER(10.0, 38);không thành công với lỗi tràn vì 10.0được truyền tới mức NUMERIC(38, 1)không đủ lớn để giữ kết quả của 10 38 . Đó là bởi vì 10 38 mở rộng để lấy 39 chữ số trước số thập phân, trong khi NUMERIC(38, 1)có thể lưu trữ 37 chữ số trước số thập phân cộng với một chữ số sau nó. Do đó, giá trị tối đa NUMERIC(38, 1)có thể giữ là 10 37 - 0,1.

Với sự hiểu biết này, tôi có thể tạo ra một lỗi tràn khác như sau.

SELECT POWER(1000000000, 3);    -- one billion

Một tỷ (trái ngược với một nghìn tỷ từ ví dụ đầu tiên, được sử dụng NUMERIC(38, 0)) chỉ đủ nhỏ để phù hợp với một INT. Tuy nhiên, một tỷ được tăng lên sức mạnh thứ ba là quá lớn INT, do đó lỗi tràn.

Một số chức năng khác thể hiện hành vi tương tự, trong đó loại đầu ra của chúng phụ thuộc vào đầu vào của chúng:

Phần kết luận

Trong trường hợp cụ thể này, giải pháp là sử dụng SELECT POWER(1e1, precision).... Điều này sẽ làm việc cho tất cả các biện pháp có thể kể từ khi 1e1được truyền tới FLOAT, có thể chứa số lượng lớn một cách lố bịch .

Vì các chức năng này rất phổ biến, điều quan trọng là phải hiểu rằng kết quả của bạn có thể được làm tròn hoặc có thể gây ra lỗi tràn do hành vi của chúng. Nếu bạn mong đợi hoặc dựa vào một loại dữ liệu cụ thể cho đầu ra của mình, hãy bỏ rõ ràng đầu vào có liên quan nếu cần.

Vì vậy, những đứa trẻ, bây giờ bạn biết điều này, bạn có thể đi ra ngoài và thịnh vượng.

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.