◆PROCESSING 逆引きリファレンス
カテゴリー:演算処理
誤差がない計算を行うには
【概要】
プログラムでは、小数点を扱うことが良くあります。例えば円の面積を求めたり、商品単価を計算する場合などでしょうか。
プログラムで小数点を扱うには、float や double などの浮動小数点型の変数を使うのが一般的ですが、これらの型は時として計算誤差を引き起こします。
1 2 3 4 |
float fval = 1.0 - 0.9; double dval = 1.0 - 0.9; println( fval ); //<--0.100000024 println( dval ); //<--0.10000002384185791 |
例えば上記のプログラムでは、float 型の変数 fval と double 型の変数 dval を用意して、ぞれぞれ 1.0 から 0.9 を引いています。
机上の計算では(言うまでもなく)答えは 1.0 – 0.9 = 0.1 となりますが、プログラム上の計算では 0.1 にはなりません。
このように計算誤差を起こしてほしくない場合は、float や double 型を使うのではなく、JavaのBigDecimalクラスを使って計算を行います。
ただしBigDecimalクラスを使った計算は、Mathクラスの命令やPROCESSINGの標準命令を使うのに比べて、かなり遅いです。
ですので、なんでもかんでもBigDecimalを使えばOKという事ではなく、ケース・バイ・ケースだという事に注意してください。
PROCESSINGの標準命令で四捨五入や切り捨て、切り上げを行うには「四捨五入、切り捨て、切り上げ」記事を参照してください。
【詳細】
変数作成
BigDecimal bdc = BigDecimal.valueOf( long val );
bdc : BigDecimal値
val : 変換元の値
BigDecimal クラスのインスタンス変数を作成する代表的な例です。
val には String 型や long 型以外にも double 型の値を与えることが可能ですが、オススメしません。
1 2 3 4 5 6 7 8 9 10 11 |
import java.math.BigDecimal; double val = 0.1; BigDecimal bdc1 = new BigDecimal(val); //(1) println( bdc1 ); //<--0.100000001490116119384765625 BigDecimal bdc2 = BigDecimal.valueOf(val); //(2) println( bdc2 ); //<--0.10000000149012 BigDecimal bdc3 = new BigDecimal( "0.1" ); //(3) println( bdc3 ); //<--0.1 |
上記例を御覧ください。
double 型の値( 0.1 )を与えた場合、せっかく BigDecimal クラスを利用しているのに誤差が生じています。これは double 型そのものが 0.1 という値を正確に保持できないためです。
小数点を正確に扱いたいなら、BigDecimal のインスタンス変数を作成する時にもdouble型は使わずに、(3)のように String 型で指定しましょう。
四則演算
BigDecimal bdc = bdc1.subtract( BigDecimal bdc2 ); //引き算
BigDecimal bdc = bdc1.multiply( BigDecimal bdc2 ); //掛け算
BigDecimal bdc = bdc1.divide( BigDecimal bdc2 ); //割り算
bdc : BigDecimal値
bdc1 : 計算する値1
bdc2 : 計算する値2
それぞれ四則演算を行うメソッドです。
注意は割り算です。BigDecimal クラスで割り切れない計算(1 ÷ 0.3 など)を行うと ArthmeticException 例外が発生します。
この例外を発生させない為には、割り算を行う際に計算の「丸め」を指定する必要があります。
BigDecimal bdc = bdc1.divide( BigDecimal bdc2, int scale, RoundingMode mode);
bdc : BigDecimal値
bdc1 : 計算する値1
bdc2 : 計算する値2
scale : 小数点以下の値の数。0なら小数点以下なし、1なら小数点以下1桁
mode : 丸め指定
丸め指定には以下が利用可能です。
ただし RoundingMode.UNNECESSARY を指定すると、scale 以上の桁数をもつ小数点値を与えた場合、ArthmeticException 例外となります。
丸め指示 | 動作 | 例 | |
---|---|---|---|
1 | RoundingMode.HALF_UP | 四捨五入 | +1.66 => 1.7 |
2 | RoundingMode.CEILING | 正の方向へ大きくなるように切り上げ | +1.66 => 1.7 |
3 | RoundingMode.DOWN | 切り捨て | -1.66 => -1.6 |
4 | RoundingMode.FLOOR | 負の方向へ小さくなるように切り捨て | -1.66 => -1.7 |
5 | RoundingMode.HALF_DOWN | 五捨六入 | +1.55 => 1.5、 +1.56 => 1.6 |
6 | RoundingMode.HALF_EVEN | 末尾が偶数のほうに四捨五入する(銀行丸め) | +1.65 => 1.6、 +1.55 => 1.6 |
7 | RoundingMode.UP | 0 から離れるように切り上げ | -1.64 => -1.7、 +1.64 => 1.7 |
8 | RoundingMode.UNNECESSARY | 丸めない | -1.64 => -1.64 |
丸め指定
bdc : BigDecimal値
val: 丸める値
scale : 小数点以下の値の数。0なら小数点以下なし、1なら小数点以下1桁
mode : 丸め指定
丸め指定は、BigDecimal値に対して直接指定することも可能です。
なお本記事の執筆にあたっては、以下のサイト様を参考とさせて頂きました。ありがとうございます。
- ホームページ制作のサカエン 様
- 侍エンジニア塾 様
- Java好き 様
【関連記事】
サンプルプログラム
四則演算例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import java.math.BigDecimal; /** * PROCESSING BigDecimal round Sample * @auther MSLABO * @version 2018/06 1.0 */ void setup(){ BigDecimal bdc1 = new BigDecimal("2.7"); BigDecimal bdc2 = new BigDecimal("0.9"); println( "足し算:" + bdc1.add(bdc2) ); //2.7 + 0.9 = 3.6 println( "引き算:" + bdc1.subtract(bdc2) ); //2.7 - 0.9 = 1.8 println( "掛け算:" + bdc1.multiply(bdc2) ); //2.7 × 0.9 = 2.43 println( "割り算:" + bdc1.divide(bdc2) ); //2.7 ÷ 0.9 = 3 noLoop(); } void draw(){ } |
シンプルな四則演算例です。割り算には注意してください。本例では割り切れる値を利用していますが、割り切れない場合は例外が発生します。
割り算の丸め指定例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
import java.math.BigDecimal; import java.math.RoundingMode; /** * PROCESSING BigDecimal round Sample * @auther MSLABO * @version 2018/06 1.1 */ void setup(){ BigDecimal bdc1 = new BigDecimal("11.5"); BigDecimal bdc2 = new BigDecimal("33.3"); //11.5 ÷ 33.3 = 0.345345345... println( "四捨五入:" + bdc1.divide(bdc2,RoundingMode.HALF_UP) ); println( "切り上げ:" + bdc1.divide(bdc2,RoundingMode.CEILING) ); println( "切り捨て:" + bdc1.divide(bdc2,RoundingMode.DOWN) ); println( "切り捨て:" + bdc1.divide(bdc2,RoundingMode.FLOOR) ); println( "五捨六入:" + bdc1.divide(bdc2,RoundingMode.HALF_DOWN) ); println( "銀行丸め:" + bdc1.divide(bdc2,RoundingMode.HALF_EVEN) ); println( "切り上げ:" + bdc1.divide(bdc2,RoundingMode.UP) ); //小数点の桁数指定 println("---------------"); println( "四捨五入:" + bdc1.divide(bdc2,0,RoundingMode.HALF_UP) ); println( "四捨五入:" + bdc1.divide(bdc2,1,RoundingMode.HALF_UP) ); println( "四捨五入:" + bdc1.divide(bdc2,2,RoundingMode.HALF_UP) ); println( "四捨五入:" + bdc1.divide(bdc2,3,RoundingMode.HALF_UP) ); noLoop(); } void draw(){ } |
各種丸め指定を使って、循環小数が発生する割り算を行わせています。
単体で丸め指定する例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
import java.math.BigDecimal; import java.math.RoundingMode; /** * PROCESSING BigDecimal round Sample * @auther MSLABO * @version 2018/06 1.2 */ void setup(){ //小数点の桁数指定 println( "四捨五入:" + new BigDecimal("-1.456456").setScale(2,RoundingMode.HALF_UP) ); println( "切り上げ:" + new BigDecimal("-1.456456").setScale(2,RoundingMode.CEILING) ); println( "切り捨て:" + new BigDecimal("-1.456456").setScale(2,RoundingMode.DOWN) ); println( "切り捨て:" + new BigDecimal("-1.456456").setScale(2,RoundingMode.FLOOR) ); println( "五捨六入:" + new BigDecimal("-1.456456").setScale(2,RoundingMode.HALF_DOWN) ); println( "銀行丸め:" + new BigDecimal("-1.456456").setScale(2,RoundingMode.HALF_EVEN) ); println( "切り上げ:" + new BigDecimal("-1.456456").setScale(2,RoundingMode.UP) ); noLoop(); } void draw(){ } |
BigDecimal値に丸め指定を行っています。
本ページで利用しているアイコン画像は、下記サイト様より拝借しております。各画像の著作権は、それぞれのサイト様および作者にあります。