ทำไม“ SELECT POWER (10.0, 38.0);” โยนข้อผิดพลาดทางคณิตศาสตร์มากเกินไป?


15

ฉันอัปเดตของฉันIDENTITYสคริปต์เช็คล้นไปยังบัญชีสำหรับDECIMALและNUMERIC IDENTITYคอลัมน์

เป็นส่วนหนึ่งของการตรวจสอบฉันคำนวณขนาดของช่วงชนิดข้อมูลสำหรับทุกIDENTITYคอลัมน์ ฉันใช้มันเพื่อคำนวณเปอร์เซ็นต์ของช่วงนั้นหมดไป สำหรับDECIMALและNUMERIC ขนาดของช่วงนั้นเป็น2 * 10^p - 2ที่ที่pมีความแม่นยำ

ฉันสร้างกลุ่มตารางทดสอบด้วยDECIMALและNUMERIC IDENTITYคอลัมน์และพยายามคำนวณช่วงดังนี้:

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

สิ่งนี้ทำให้เกิดข้อผิดพลาดต่อไปนี้:

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

ฉัน จำกัด ให้แคบลงไปยังIDENTITYคอลัมน์ประเภทDECIMAL(38, 0)(เช่นด้วยความแม่นยำสูงสุด) ดังนั้นฉันจึงลองPOWER()คำนวณโดยตรงกับค่านั้น

แบบสอบถามต่อไปนี้ทั้งหมด

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

ยังส่งผลให้เกิดข้อผิดพลาดเดียวกัน

  • เหตุใด SQL Server จึงพยายามแปลงผลลัพธ์POWER()ของชนิดFLOATใดเป็นNUMERIC(โดยเฉพาะเมื่อFLOATมีลำดับความสำคัญสูงกว่า )
  • ฉันจะคำนวณช่วงของ a DECIMALหรือNUMERICคอลัมน์แบบไดนามิกสำหรับการกำหนดที่เป็นไปได้ทั้งหมด (รวมถึงp = 38แน่นอน) ได้อย่างไร

คำตอบ:


18

จากPOWERเอกสารประกอบ :

วากยสัมพันธ์

POWER ( float_expression , y )

ข้อโต้แย้ง

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

Y
คืออำนาจในการที่จะยกระดับfloat_expression yสามารถเป็นนิพจน์ของประเภทข้อมูลตัวเลขหรือตัวเลขที่แน่นอนโดยประมาณยกเว้นชนิดข้อมูลบิต

ประเภทผลตอบแทน

ผลตอบแทนที่ได้ประเภทเดียวกับที่ส่งในfloat_expression ตัวอย่างเช่นถ้าทศนิยม (2,0) ถูกส่งเป็น float_expression ผลลัพธ์ที่ได้คือทศนิยม (2,0)


อินพุตแรกจะถูกส่งไปโดยปริยายfloatหากจำเป็น

การคำนวณภายในจะดำเนินการโดยใช้floatเลขคณิตโดยฟังก์ชัน C Runtime Library (CRT) powมาตรฐาน

floatผลลัพธ์จากการpowถูกทิ้งแล้วกลับไปที่ประเภทของมือซ้ายตัวถูกดำเนินการ (โดยนัยจะเป็นnumeric(3,1)เมื่อคุณใช้มูลค่าที่แท้จริง 10.0)

การใช้floatงานที่ชัดเจนในกรณีของคุณ:

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

ผลลัพธ์ที่แน่นอนสำหรับ 10 38ไม่สามารถเก็บไว้ใน SQL Server ได้decimal/numericเนื่องจากจะต้องมีความแม่นยำ 39 หลัก (1 ตามด้วย 38 ศูนย์) ความแม่นยำสูงสุดคือ 38


23

แทนที่จะเข้าไปยุ่งกับคำตอบของมาร์ตินอีกต่อไปฉันจะเพิ่มการค้นพบที่เหลือของฉันPOWER()ที่นี่

ยึดมั่นในกางเกงของคุณ

คำนำ

ก่อนอื่นฉันขอแสดงเอกสาร A ของ MSDN สำหรับPOWER() :

วากยสัมพันธ์

POWER ( float_expression , y )

ข้อโต้แย้ง

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

ประเภทผลตอบแทน

float_expressionเช่นเดียวกับ

คุณอาจสรุปได้จากการอ่านบรรทัดสุดท้ายที่POWER()เป็นประเภทการส่งคืนFLOATแต่อ่านอีกครั้ง float_expressionคือ "ประเภทลอยหรือประเภทที่สามารถแปลงเป็นลอย" โดยปริยาย ดังนั้นแม้จะมีชื่อของมันfloat_expressionจริงอาจจะFLOATเป็นหรือDECIMAL INTเนื่องจากเอาต์พุตของPOWER()เหมือนกันกับของfloat_expressionมันก็อาจเป็นหนึ่งในประเภทเหล่านั้น

ดังนั้นเราจึงมีฟังก์ชันสเกลาร์พร้อมชนิดส่งคืนที่ขึ้นอยู่กับอินพุต มันอาจจะเป็น?

ข้อสังเกต

ผมนำเสนอให้คุณแสดง B, แสดงให้เห็นถึงการทดสอบที่ปลดเปลื้องการส่งออกไปยังชนิดข้อมูลที่แตกต่างกันขึ้นอยู่กับปัจจัยการผลิตPOWER()

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;

ผลลัพธ์ที่เกี่ยวข้องคือ:

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

สิ่งที่ดูเหมือนจะเกิดขึ้นคือPOWER()ปลดเปลื้องลงในประเภทที่เล็กที่สุดที่เหมาะกับมันไม่รวมfloat_expressionBIGINT

ดังนั้นSELECT POWER(10.0, 38);ด้วยข้อผิดพลาดล้นเพราะ10.0ได้รับการโยนไปNUMERIC(38, 1)ซึ่งไม่ใหญ่พอที่จะถือผล 10 38 นั่นเป็นเพราะ 10 38ขยายตัวให้เป็น 39 หลักก่อนจุดทศนิยมในขณะที่NUMERIC(38, 1)สามารถเก็บ 37 หลักก่อนทศนิยมบวกหนึ่งหลัง ดังนั้นค่าสูงสุดNUMERIC(38, 1)สามารถถือได้คือ 10 37 - 0.1

ด้วยความเข้าใจนี้ฉันสามารถปรุงความล้มเหลวได้อีกด้วย

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

หนึ่งพันล้านบาท (เมื่อเทียบกับหนึ่งล้านล้านจากตัวอย่างแรกซึ่งจะโยนไปNUMERIC(38, 0)) INTเป็นพอเพียงเพื่อให้พอดีกับขนาดเล็กใน หนึ่งพันล้านยกกำลังสามอย่างไรก็ตามใหญ่เกินไปINTดังนั้นข้อผิดพลาดล้น

ฟังก์ชั่นอื่น ๆ อีกหลายตัวแสดงพฤติกรรมที่คล้ายกันโดยที่ประเภทเอาท์พุทขึ้นอยู่กับอินพุต:

ข้อสรุป

SELECT POWER(1e1, precision)...ในกรณีนี้โดยเฉพาะอย่างยิ่งการแก้ปัญหาคือการใช้งาน สิ่งนี้จะใช้ได้กับพื้นที่ที่มีความเป็นไปได้ทั้งหมดตั้งแต่1e1เข้าสู่พื้นที่FLOATซึ่งสามารถเก็บจำนวนมากได้อย่างน่าขัน

เนื่องจากฟังก์ชั่นเหล่านี้เป็นเรื่องธรรมดาจึงเป็นเรื่องสำคัญที่จะต้องเข้าใจว่าผลลัพธ์ของคุณอาจถูกปัดเศษหรืออาจทำให้เกิดข้อผิดพลาดมากเกินไปเนื่องจากพฤติกรรมของพวกเขา หากคุณคาดหวังหรือพึ่งพาประเภทข้อมูลเฉพาะสำหรับผลลัพธ์ของคุณให้ส่งสัญญาณที่เกี่ยวข้องตามความจำเป็นอย่างชัดเจน

ดังนั้นเด็ก ๆ เมื่อคุณรู้สิ่งนี้คุณอาจออกไปข้างนอกและประสบความสำเร็จ

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