MySQLでのDouble vs Decimalの違い。注意すべきは誤差の有無

MySQL(SQL)で前から気になってたことです。

Double vs Decimal

両者とも次の共通点はあります。

  • 小数点を含む自然数を扱える
  • (M,N)で精度の指定も可能

ほとんど似たものという認識でした。

ですが厳密さが求められる場面では注意が必要です。
そこでDouble / Decimalの違いについて解説

Doubleでは内部的な丸め誤差が発生してしまう

まずはDouble型について

こちらは内部で誤差が発生すること前提で使います。

ちょっと分かりやすいSQL例で考えてみました。

▼ まず適当にテーブル作成・データ挿入

▼ 各カラムの合計値を計算して表示

▼ こういう結果になった

grp s1 s2
1 3.1399999999999997 3.14

手計算するなら SUM(d1) = 0.00 + -10.00 + 12.10 + 1.04 = 3.14になるはずだし、 SUM(d2) = 0.00 + 0.00 + 2.1+1.04 = 3.14 と同じ結果になるはず

なのに違った結果になってます。

小数点を2進数に表す場合、整数と違って循環小数も存在します。そしてDOUBLEのサイズは8バイトだから、それに合わせるように丸めが起こるというわけですね。

▼ この記事の説明が分かりやすいと思う

10進数「0.3」 を 2進数に変換すると、「0.010011001100...」となり、2進数「0.010011001100」を 10進数に変換すると、「0.2998046875」となります。

0x0.1は、有限小数に対して、0b0.010011001100は、循環節が「1100」の循環小数となります。double型の桁数は有限であるため、無限小数は必ず丸め込みが発生し、誤差が出てします。

引用元 : 数値Double型の誤差が発生する原因

つまりは不動少数点計算では誤差が生じるため。人間からしたら理解ができない挙動だけど、CPUは有限の数(2進数)しか扱えないゆえに誤差が生まれます。

Decimalは(M,N)の範囲で誤差のない計算ができる

一方のDecimalについて

これは(M,N)で精度を指定できます。

▼ Decimalを宣言する例

(M,N)という書き方ですが M は全体の桁数(precision)を表し、 N は小数点以降の桁数(scale)を表しています。つまり整数部分は M-N桁、小数部分をN桁として宣言できるということ。

例えば Decimal(8, 2) だったら全体の桁数は8桁、つまり整数部分は 6桁になって小数部分は2桁になるってことです(例 : 123456.78)

 

それではDoubleの時と同じ検証をしてみます。

▼ 適当なテーブル作成とレコード挿入

▼ 各カラムの合計値を計算して表示

▼ 表示結果

grp s1 s2
1 3.1400000000 3.1400000000

今度は期待通りの結果になりました。

DOUBLEの時は無限の数を有限のメモリで扱うから誤差が生じていましたが、DECIMALでは初めから精度指定が必須なのでそういったことは起きません。

DOUBLEとDECIMALの違いと使い分け方

精度が重要でないならDOUBLEを使えばOK

DOUBLEを使うのが推奨されるケース。

例えばこういう場面です。

  • カラム同士の計算が必要ない
  • 数値の範囲(M,N)が分からない時
  • お金とか科学計算の分野ではない

つまり一般的にはDOUBLEで十分かも

ただし範囲が「10桁以上・小数点3桁まで」などと明確に決まっていたり、上限下限がはっきりしているなら DECIMAL を使う方が無駄がないです。

精度優先の金融・科学などはDECIMALを使う

逆にDECIMALを使うべきケース。

こういう場面が該当します。

  • カラム同士の計算が必要である
  • 大事なお金を取り扱う金融分野
  • 精度が最優先される化学分野

特に金融なんかはDOUBLEを使うことはないはずです。

DOUBLEなんて誤差が発生するのは確実だし、そんなのが金融分野・科学分野に使われてたら社会が大混乱になっちゃいますね 笑