10 ^ 37/1 अंकगणितीय अतिप्रवाह त्रुटि क्यों करता है?


11

बड़ी संख्या के साथ खेलने के अपने हालिया रुझान को जारी रखते हुए , मैंने हाल ही में एक त्रुटि उगल दी जिसे मैं नीचे दिए गए कोड में चला रहा था:

DECLARE @big_number DECIMAL(38,0) = '1' + REPLICATE(0, 37);

PRINT @big_number + 1;
PRINT @big_number - 1;
PRINT @big_number * 1;
PRINT @big_number / 1;

इस कोड के लिए मुझे मिलने वाला आउटपुट है:

10000000000000000000000000000000000001
9999999999999999999999999999999999999
10000000000000000000000000000000000000
Msg 8115, Level 16, State 2, Line 6
Arithmetic overflow error converting expression to data type numeric.

क्या?

पहले 3 ऑपरेशन क्यों काम करेंगे लेकिन आखिरी नहीं? और अगर वहाँ @big_numberस्पष्ट रूप से के उत्पादन को संग्रहीत कर सकते हैं तो एक अंकगणितीय अतिप्रवाह त्रुटि कैसे हो सकती है @big_number / 1?

जवाबों:


18

अंकगणित संचालन के संदर्भ में परिशुद्धता और स्केल को समझना

आइए इसे नीचे तोड़ते हैं और डिवाइड अंकगणितीय ऑपरेटर के विवरण पर करीब से नज़र डालते हैं । MSDN के डिवाइड ऑपरेटर के परिणाम प्रकारों के बारे में यही कहना है :

परिणाम प्रकार

उच्च वरीयता वाले तर्क का डेटा प्रकार लौटाता है। अधिक जानकारी के लिए, डेटा प्रकार की वरीयता (Transact-SQL) देखें

यदि एक पूर्णांक लाभांश एक पूर्णांक भाजक द्वारा विभाजित किया जाता है, तो परिणाम एक पूर्णांक होता है जिसमें परिणाम का कोई अंश आंशिक होता है।

हम जानते हैं कि @big_numberDECIMAL। SQL सर्वर किस डेटा प्रकार के रूप में काम करता है 1? यह एक करने के लिए यह जाती है INT। हम इसकी मदद से इसकी पुष्टि कर सकते हैं SQL_VARIANT_PROPERTY():

SELECT
      SQL_VARIANT_PROPERTY(1, 'BaseType')   AS [BaseType]  -- int
    , SQL_VARIANT_PROPERTY(1, 'Precision')  AS [Precision] -- 10
    , SQL_VARIANT_PROPERTY(1, 'Scale')      AS [Scale]     -- 0
;

किक के लिए, हम 1मूल कोड ब्लॉक को भी स्पष्ट रूप से टाइप किए गए मान के साथ बदल सकते हैं DECLARE @one INT = 1;और पुष्टि कर सकते हैं कि हमें समान परिणाम मिलते हैं।

तो हम एक DECIMALऔर एक है INT। चूंकि हमारे DECIMALपास उच्च डेटा प्रकार की पूर्ववर्ती स्थिति है INT, इसलिए हमें पता है कि हमारे डिवीजन के आउटपुट को कास्ट किया जाएगा DECIMAL

तो समस्या कहाँ है?

समस्या DECIMALआउटपुट के पैमाने के साथ है । यहाँ पर नियमों की एक तालिका है कि SQL सर्वर अंकगणितीय परिचालनों से प्राप्त परिणामों की सटीकता और पैमाने को कैसे निर्धारित करता है :

Operation                              Result precision                       Result scale *
-------------------------------------------------------------------------------------------------
e1 + e2                                max(s1, s2) + max(p1-s1, p2-s2) + 1    max(s1, s2)
e1 - e2                                max(s1, s2) + max(p1-s1, p2-s2) + 1    max(s1, s2)
e1 * e2                                p1 + p2 + 1                            s1 + s2
e1 / e2                                p1 - s1 + s2 + max(6, s1 + p2 + 1)     max(6, s1 + p2 + 1)
e1 { UNION | EXCEPT | INTERSECT } e2   max(s1, s2) + max(p1-s1, p2-s2)        max(s1, s2)
e1 % e2                                min(p1-s1, p2 -s2) + max( s1,s2 )      max(s1, s2)

* The result precision and scale have an absolute maximum of 38. When a result 
  precision is greater than 38, the corresponding scale is reduced to prevent the 
  integral part of a result from being truncated.

और यहाँ इस तालिका में चर के लिए हमारे पास क्या है:

e1: @big_number, a DECIMAL(38, 0)
-> p1: 38
-> s1: 0

e2: 1, an INT
-> p2: 10
-> s2: 0

e1 / e2
-> Result precision: p1 - s1 + s2 + max(6, s1 + p2 + 1) = 38 + max(6, 11) = 49
-> Result scale:                    max(6, s1 + p2 + 1) =      max(6, 11) = 11

उपरोक्त तालिका में तारांकित टिप्पणी के अनुसार, अधिकतम सटीक अधिकतम DECIMAL38 हो सकते हैं । इसलिए हमारे परिणाम की सटीकता 49 से घटकर 38 हो जाती है, और "परिणाम को छोटा करने से रोकने के लिए इसी पैमाने को कम किया जाता है।" इस टिप्पणी से यह स्पष्ट नहीं है कि पैमाना कैसे घटा है, लेकिन हम यह जानते हैं:

तालिका में सूत्र के अनुसार, दो एस को विभाजित करने के बाद आपके पास न्यूनतम संभव पैमाने 6 हो सकते हैं DECIMAL

इस प्रकार, हम निम्नलिखित परिणामों के साथ समाप्त होते हैं:

e1 / e2
-> Result precision: 49 -> reduced to 38
-> Result scale:     11 -> reduced to 6  

Note that 6 is the minimum possible scale it can be reduced to. 
It may be between 6 and 11 inclusive.

यह कैसे अंकगणित अतिप्रवाह की व्याख्या करता है

अब उत्तर स्पष्ट है:

हमारे डिवीजन के आउटपुट को कास्ट किया जाता है DECIMAL(38, 6), और DECIMAL(38, 6)10 37 नहीं पकड़ सकता है ।

इसके साथ, हम एक और विभाजन का निर्माण कर सकते हैं जो यह सुनिश्चित करता है कि परिणाम में फिट हो सकता है DECIMAL(38, 6):

DECLARE @big_number    DECIMAL(38,0) = '1' + REPLICATE(0, 37);
DECLARE @one_million   INT           = '1' + REPLICATE(0, 6);

PRINT @big_number / @one_million;

परिणाम है:

10000000000000000000000000000000.000000

दशमलव के बाद 6 शून्य पर ध्यान दें। हम पुष्टि कर सकते हैं कि परिणाम का डेटा प्रकार ऊपर के रूप में DECIMAL(38, 6)उपयोग करके SQL_VARIANT_PROPERTY()है:

DECLARE @big_number   DECIMAL(38,0) = '1' + REPLICATE(0, 37);
DECLARE @one_million  INT           = '1' + REPLICATE(0, 6);

SELECT
      SQL_VARIANT_PROPERTY(@big_number / @one_million, 'BaseType')  AS [BaseType]  -- decimal
    , SQL_VARIANT_PROPERTY(@big_number / @one_million, 'Precision') AS [Precision] -- 38
    , SQL_VARIANT_PROPERTY(@big_number / @one_million, 'Scale')     AS [Scale]     -- 6
;

एक खतरनाक कसरत

तो हम इस सीमा के आसपास कैसे पहुँचें?

खैर, यह निश्चित रूप से इस बात पर निर्भर करता है कि आप इन गणनाओं के लिए क्या कर रहे हैं। FLOATगणना के लिए अपनी संख्या बदलने के लिए एक समाधान जिसे आप तुरंत कूद सकते हैं , और फिर DECIMALजब आप कर रहे हों तब उन्हें वापस रूपांतरित करें ।

यह कुछ परिस्थितियों में काम कर सकता है, लेकिन आपको यह समझने में सावधानी बरतनी चाहिए कि वे परिस्थितियां क्या हैं। जैसा कि हम सभी जानते हैं, संख्याओं को और इससे परिवर्तित FLOATकरना खतरनाक है और आपको अप्रत्याशित या गलत परिणाम दे सकता है।

हमारे मामले में, 10 से 37 को परिवर्तित करना और FLOATएक परिणाम मिलता है जो कि केवल गलत है :

DECLARE @big_number     DECIMAL(38,0)  = '1' + REPLICATE(0, 37);
DECLARE @big_number_f   FLOAT          = CAST(@big_number AS FLOAT);

SELECT
      @big_number                           AS big_number      -- 10^37
    , @big_number_f                         AS big_number_f    -- 10^37
    , CAST(@big_number_f AS DECIMAL(38, 0)) AS big_number_f_d  -- 9999999999999999.5 * 10^21
;

आखिर तुमने इसे हासिल कर ही लिया है। मेरे बच्चों को ध्यान से विभाजित करो।



2
रे: "क्लीनर वे"। आप देखना चाहेंगेSQL_VARIANT_PROPERTY
मार्टिन स्मिथ

@Martin - क्या आप इस बात का उदाहरण या त्वरित विवरण प्रदान कर सकते हैं कि मैं SQL_VARIANT_PROPERTYप्रश्न में चर्चा की तरह विभाजन का उपयोग कैसे कर सकता हूं ?
निक चामास

1
यहाँ एक उदाहरण है (डेटाटाइप को निर्धारित करने के लिए एक नई तालिका बनाने के विकल्प के रूप में)
मार्टिन स्मिथ

@ मर्तिन - आह हाँ, यह बहुत ज्यादा बकवास है!
निक चामास
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.