読者です 読者をやめる 読者になる 読者になる

かとじゅんの技術日誌

技術の話をするところ

小数点の計算をやるからといってすぐにfloatやdoubleを使ってはいけない

Java

おつかれさまです。

型を選ぶというのはプログラミングの基礎中の基礎ですが、以外に開発の現場でも注意が必要なのが、floatとdoubleの精度の問題があります。これぐらい当然と思っていたのですが、最近よく耳にしますので、あえて言及w

これらの型の目的が科学技術計算のための用途。多少誤差が生じても高速に演算をすることを目的としています。グラフィックなどの描画系とかですかね。

このように、float型やdouble型は誤差を含む可能性があるため、科学・工学計算で多少の誤差は許容できる場合はよいのですが、金融・会計分野のように正確な値が要求される計算には向きません。

どうしても正確な値が欲しい場合は、BigDecimalクラスを使用することになります。基本データ型のdoubleやfloatと比較すると不便かつ低速ですが、精度が保証されており、丸め方も指定できます。

ここで言及されているように、お金や会計などの正確性を問われる計算では使えません。基本的にBigDecimalを利用しないといけませんね。(桁数によってはintやlongを整数部と小数部に分けて演算する方法が考えられますがずばり面倒ですw←勘違いでしたw 多少演算速度が遅くともBigDecimalがよいですw)
なので、小数点を計算するからこの変数はfloatか、dobuleかなという安易な考えはよくないのです。目的に応じた型を選ぶことが重要。

たとえば、Efficetive Javaでも登場するこの式。当然これでは誤差が発生します。

System.out.println(1.00-9*.10);

私の環境でこのような結果になりました。

0.09999999999999998

次はBigDecimalの例。
ちょっとコードが面倒な感じになってきましたw

	// 1.0 - 9 * 0.1
	BigDecimal b1 = new BigDecimal(1.0);
	BigDecimal b2 = new BigDecimal(-9);
	BigDecimal b3 = new BigDecimal("0.1");
	BigDecimal result = b1.add(b2.multiply(b3));	
	System.out.println(result.toString());

でも、結果は期待どおりです。

0.1

0.1の部分だけが文字列表現ですが、これは精度が落ちないために文字列で初期化しています。数値で渡すと誤差が発生するので気をつける。

BigDecimal b3 = new BigDecimal("0.1");

詳しくはこちら。

doubleからBigDecimalオブジェクトを作成する場合、

BigDecimal bd = new BigDecimal(doubleVal);

としてしまうと、浮動小数点で正確に表せない数値の場合、期待結果とズレる場合があります。

小数点以下まできっちり計算しないといけない用途ではBigDecimalということで。
間違っても、floatやdoubleでお金の計算をしておいて、発生したその誤差を調整するようなロジックは書いてはいけませんよ。

追記:
Effective Java 初版 31項、第二版 48項に事例の紹介があります。