浮動小数点型の誤差による四捨五入の問題点
SQLseverにてreal型の小数に対して、CONVERTでNUMERICに変換する際に小数第4位までにしようとした。
SELECT CONVERT(Numeric(10,4), CONVERT(real, 0.00005))
当然のことながら結果は0.0001
になるべきだが、SQLserverで出力された結果は0.0000
となってしまった。
微小な誤差ではあるが問題になることもあるため調査することにした。
原因は?
まずは本当に小数第5位を四捨五入しているのかを確認するために、以下の2パターンの値で結果を確認してみた。
SELECT CONVERT(Numeric(10,4), CONVERT(real, 0.00006)) SELECT CONVERT(Numeric(10,4), CONVERT(real, 0.000051))
結果は0.0001
と初期の想定通りの動きをしていた。
0.00006
だけであれば五捨六入の可能性もあり得るが、0.000051
でも0.0001
になっていることから四捨五入であることは明白だ。
ここまで調べて思ったのは、real型は浮動小数点型なので丸め誤差があるということだ。
そこで表示桁数を増大させて0.00005
を見てみようと思う。
SELECT CONVERT(Numeric(38,38), CONVERT(real, 0.00005))
結果は0.00004999999873689000000000000000000000
となった。
やはり誤差が発生しており、しかもそれにより小数第5位が5から4になってしまっている。 これが予期せぬ四捨五入の結果になっているわけだ。
対応策は?
前述の結果の通り、浮動小数点型の場合は微小な丸め誤差が存在する。 しかしながら、あくまでも誤差は微小でしかないため、本来四捨五入したい桁よりも一つ小さい桁で四捨五入した後に、再度四捨五入をすればよい。
SELECT CONVERT(Numeric(10,4), CONVERT(Numeric(10,5), CONVERT(real, 0.00005)))
感想
結構無理やりなやり方で面倒くさく、そして気持ち悪い。やはり精度が重要になる数値を扱う場合には、最初から浮動小数点型の使用は控えるべきだろう。 でもレガシーシステムで精度が考慮されていないものをよく見るのでこんなやり方をすることもまだありそうな気はしてる。