ただ枯れゆく大樹が如く

技術の話やら愚痴やら節操もなく徒然と

浮動小数点型の誤差による四捨五入の問題点

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)))

感想

結構無理やりなやり方で面倒くさく、そして気持ち悪い。やはり精度が重要になる数値を扱う場合には、最初から浮動小数点型の使用は控えるべきだろう。 でもレガシーシステムで精度が考慮されていないものをよく見るのでこんなやり方をすることもまだありそうな気はしてる。