【MySQL】FLOAT型やDOUBLE型は厳密な計算はできないし、WHEREで使い物にならない

MySQL

こんにちは、いっとくです!

最近はデータベース強い人を目指して爆進中です。

そんなわけで、今日の話題はMySQLのデータ型のお話です。

MySQL以外のSQLでも当てはまるかとは思います。

今回取り上げるのは小数とかを扱うFROAT型ってやつ!(DOUBLEも同様)

何も知らずに使うとそれなりに危ない特性を持っているので、そのへんをご紹介します。

スポンサーリンク

浮動小数点は勝手に丸められます

まずは浮動小数点の仕組みについて触れていきましょう。

この浮動小数点というのは書かれている数字をそのまま保存するわけではありません。

いや、そのまま保存してくれよって感じなのですが、そういう時は別の型を使う必要があります。

そういう挙動をしてしまう原因として、実はこの浮動小数点という型は2進数に変換して保存しています。

するとある問題が発生します。

全ての10進数が2進数で表せるわけではないという問題が!

この辺はちょっと数学弱いので、詳しいことはもっと数学に特化した人に聞いておくれという感じなのですが、10進数で表した小数のほとんどは2進数にすると無限小数になるそうです。

なので、どこかのタイミングで数字を打ち切って丸めて保存しています

では丸めの実験を行ってみます。

まずは小数を保存するためだけの超シンプルなテーブルを作ります。

CREATE TABLE numbers (number FLOAT);

シンプルイズザベストもここまで来るとワーストだと思います。

次はデータを突っ込みます。

INSERT INTO numbers (number) VALUES (1.5), (1.9);

値を2つ用意していますが、1.5は2進数で表すことができる小数で1.9は2進数だと無限小数になる値です。

では取得してみましょう。

mysql> SELECT * FROM numbers WHERE number = 1.5;
+--------+
| number |
+--------+
|    1.5 |
+--------+
1 row in set (0.00 sec)

できました。

ではもう一つの方もサラッと取ってやりましょう。

mysql> SELECT * FROM numbers WHERE number = 1.9;
Empty set (0.01 sec)

ぐぬぬ…!取れぬ…!

そう、これが丸めの正体です。

保存されている値が丸めによって、微妙に違う値になっているのでWHEREで検索かけても見つからないということになります。

この丸めがあるため、厳密さが求められる計算というのはFLOATとかDOUBLEでやってはいけないのです。

特に金融系のシステムでは利子の計算とかで小数点を使うと思いますが、そこでFLOATを使ってしまうとじわじわ誤差が出てきます。

金融なんてそんなことが一番あってはいけないジャンルなのに、、、

一体なぜこんな型を作ったんですかー!??って思うかもしれませんが、もともとは小さな誤差では影響が出にくい科学技術分野のための型らしいのです。

こっちの方がなんか極端に大きい、または小さい値を表現できるんですって。

じゃあ厳密な計算に使いたい時はどうしたら良いの?って言うことで解決策にお登場してもらいます。

解決方法:別の型を使いましょう

はい、というわけで解決策ですが、厳密な計算が必要になるデータについては専用の型を使用します。

その型というのがDECIMALもしくはNUMERICです!

日本語でいうと固定小数点型です。

DECIMALとNUMERICに関してはMySQLの公式リファレンスに

NUMERIC は DECIMAL として実装されるので、DECIMAL に関する次の注意事項が NUMERIC にも同様に適用されます。

とあることから、同じものです。

DECIMAL(NUMERIC)の使い方

最後にDECIMALの使い方を見ていきましょう。

まずはDECIMAL型のカラムの宣言方法ですが引数を2つ取ります。

CREATE TABLE decimals (number DECIMAL(10, 3));

内容としてはDECIMAL(最大桁数, 小数点以下の桁数)というものになっています。

最大桁数は小数点以下も含めてという意味なので、今回作成したカラムは、-9999999.999から9999999.999まで格納できるということです。

ではここに先程と同じデータを流し込んでみましょう。

INSERT INTO decimals (number) VALUES (1.5), (1.9);

そして先程できなかった取得をやってみる

mysql> SELECT * FROM decimals WHERE number = 1.9;
+--------+
| number |
+--------+
|  1.900 |
+--------+
1 row in set (0.00 sec)

取れたー!!

なんか足りない部分ゼロで埋められてるけど!

ちなみに、小数点以下の桁数が多いとエラーを吐かずにカットされます。

mysql> INSERT INTO decimals (number) VALUES (1.512213);
Query OK, 1 row affected, 1 warning (0.00 sec)

mysql> SELECT * FROM decimals;
+--------+
| number |
+--------+
|  1.500 |
|  1.900 |
|  1.512 |
+--------+
3 rows in set (0.00 sec)

これは格納されているデータがそのまま入っているので、表示内容でWHEREを使えば取り出せます。

mysql> SELECT * FROM decimals WHERE number = 1.512;
+--------+
| number |
+--------+
|  1.512 |
+--------+
1 row in set (0.00 sec)

ちなみに、最大桁数は65、小数点以下は30までサポートされています。

というわけで知っている人からすれば当たり前だけど、最近SQLをマスターしようと思った僕には新鮮なお話でした!

みなさんもFLOATやDOUBLEをつかう時は気をつけましょう!

参考資料:SQLアンチパターン

SQLアンチパターン

SQLの良書ですので、興味ある方はぜひ読んでみてください!

以上、いっとくでした!

さようなら!

コメント

タイトルとURLをコピーしました