算術演算のコンテキストでの精度とスケールの理解
これを分解して、除算算術演算子の詳細を詳しく見てみましょう。これは、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;
、同じ結果が得られることを確認することもできます。
したがって、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
;
そして、そこにあります。私の子供たち、注意深く分けてください。