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_number입니다 DECIMAL. SQL Server는 어떤 데이터 형식으로 캐스팅 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;동일한 결과를 얻을 수 있습니다.

그래서 우리는 a DECIMALINT. 데이터 유형보다 우선 순위DECIMAL높기 때문에 나눗셈의 결과가로 변환됩니다 . INTDECIMAL

그래서 문제는 어디에 있습니까?

문제는 DECIMAL출력 의 스케일에 있습니다. 다음은 SQL Server 가 산술 연산에서 얻은 결과정밀도와 스케일을 결정하는 방법에 대한 규칙 표입니다 .

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

위의 표에 별표로 표시된 설명 의 최대 정밀도 DECIMAL는 38 입니다. 따라서 결과의 정밀도는 49에서 38로 줄었고 결과의 필수 부분이 잘리지 않도록 해당 배율이 줄어 듭니다. 이 의견 에서 규모가 어떻게 축소 되는지 는 분명 하지 않지만 우리는 이것을 알고 있습니다.

표의 공식에 따르면 두 개의 s를 나눈 후 가능한 최소 눈금 DECIMAL은 6입니다.

따라서 다음과 같은 결과가 나타납니다.

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 개의 0에 유의하십시오. 우리는 결과의 데이터 유형이 확인할 수 있습니다 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 Smith

@Martin- SQL_VARIANT_PROPERTY질문에서 논의한 것과 같은 나눗셈을 수행 하는 데 사용할 수있는 방법에 대한 예나 빠른 설명을 제공 할 수 있습니까?
Nick Chammas

1
여기에 예가 있습니다 (데이터 유형을 결정하기 위해 새 테이블을 만드는 대신)
Martin Smith

@Martin-아 네, 훨씬 더 깔끔합니다!
Nick Chammas
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.