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 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;、同じ結果が得られることを確認することもできます。

したがって、DECIMALとがありINTます。DECIMALデータ型の優先順位がより大きいためINT、除算の出力はにキャストされDECIMALます。

では、問題はどこにあるのでしょうか?

問題は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

上記の表のアスタリスクのコメントによると、aの最大精度DECIMALは38です。したがって、結果の精度は49から38に削減され、「対応するスケールが縮小されて、結果の整数部分が切り捨てられないようにします」。このコメントからスケールがどのように縮小されるは明らかではありませんが、私たちはこれを知っています:

表の公式によると、2つのを除算した後に可能な最小のスケール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つのゼロに注意してください。結果のデータタイプが上記のように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
;

危険な回避策

では、どうすればこの制限を回避できますか?

まあ、それは確かにあなたがこれらの計算をしている目的に依存します。すぐにジャンプできるソリューションの1つ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
RE:「クリーナーウェイ」。あなたは見たいかもしれませんSQL_VARIANT_PROPERTY
マーティン・スミス

@Martin- SQL_VARIANT_PROPERTY質問で説明したような分割を実行するために使用できる方法の例または簡単な説明を提供できますか?
Nick Chammas

1
ここに例があります(データ型を決定するために新しいテーブルを作成する代わりに)
Martin Smith

@マーティン-ああそう、それはかなりすっきりしている!
Nick Chammas
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.