2.3. 文字列での+演算子
また、「+」の演算子は今までのサンプルでよく見かけるように、数値だけでなく文字列を連結させる際に使うことができます。ただし、注意点として、文字列の後に「+」の演算子をつけると、それ以降の数値もすべて文字列として扱われることが挙げられます。そのため、次のような場合、
"1 + 1 = " + 1 + 1 ↓ "1 + 1 = " + "1" + "1"
と見なされ、出力される結果は「1 + 1 = 11」という文字列です。もし、数値の計算をしつつ、文字列との連結をさせる必要がある場合は、次のように数値の部分を丸カッコ「()」を使うことによって、数値として計算させることができます。
"1 + 1 = " + (1 + 1)
この場合、出力される結果は「1 + 1 = 2」です。
また、文字連結の「+」演算子は左から計算されていくため、文字列の前に数値があると数値の計算として扱われます。例えば、
1 + 2 + "3"
は「123」ではなく文字列の前の数値としての「1 + 2」が評価され、結果として「33」と表示されます。ただし、業務アプリでの開発の場合、この記述だと、仮に後で変更があり、数値の前に文字列が来た場合にきちんと対処していないと意図した値にならないので、基本は文字列内の数値計算は「()」(丸カッコ)内で行うようにします。
では、次のサンプルを実行してみましょう。
package jp.codezine.java.sample03;
/**
* 算術演算子を確認するクラス。
*/
public class ArithmeticOperator3 {
/**
* 算術演算を実行し、その内容を確認します。
*
* @param args
* コマンドライン引数。今回は使われません。
*/
public static void main(String[] args) {
// [1] カッコなし
System.out.println("[1] 1 + 1 = " + 1 + 1);
// [2] カッコあり
System.out.println("[2] 1 + 1 = " + (1 + 1));
// [3] 数値が左
System.out.println(1 + 2 + "3");
}
}
[1] 1 + 1 = 11 [2] 1 + 1 = 2 33
2.4. 浮動小数点の数値を扱った算術演算の注意点
今まで見てきたように、数値の計算を算術演算子で行うことは可能です。しかし、doubleやfloatなど浮動小数点の値で計算する場合は注意が必要です。それは浮動小数点の値を計算した場合、誤差が発生し意図した値にならないためです。まずは次のサンプルを実行してどのような結果になるのか見てみましょう。
package jp.codezine.java.sample03;
/**
* 算術演算子を確認するクラス。
*/
public class ArithmeticOperator4 {
/**
* 算術演算を実行し、その内容を確認します。
*
* @param args
* コマンドライン引数。今回は使われません。
*/
public static void main(String[] args) {
// [1] 注意点(浮動小数点の計算)
double resultDouble = 0.0;
resultDouble = 0.7 + 0.1;
System.out.println("[1] 0.7 + 0.1 = " + resultDouble);
}
}
これを実行すると次の結果が表示されます。
[1] 0.7 + 0.1 = 0.7999999999999999
この誤差はJavaだけではなくほとんどのプログラミング言語にある問題で、原因はソースコード上で記述された10進数をJavaの実行環境内で2進数に変換して計算しているため、浮動小数点の数値を扱うと、この10進数と2進数との変換の際に誤差が生じてしまうためです。
そのため、金額の計算など誤差の発生が許されず正確な数値を求められる場合、次のBigDecimalクラスを使って計算します。
2.5. BigDecimalでの計算
Javaでは、浮動小数点の数値に対して演算子を使って計算した場合に誤差が発生し、意図した値になりません。その誤差を回避するために、Javaではjava.math.BigDecimalというクラスが用意されています。そして正確な値が必要な計算の場合、このBigDecimalクラスを使って計算が行われます。
BigDecimalでの計算を行う際は、まずBigDecimalのコンストラクタの引数に数値を文字列にしたものを渡して、それぞれの数値を持つBigDecimalのインスタンスを生成します。そしてBigDecimalが持つメソッドを使って、計算結果の値を持つBigDecimalを結果として生成します。BigDecimalでは算術演算子と同様に次のメソッドが用意されています。
| メソッド名 | 同等の演算子 | 概要 |
| add | + | 足し算(加算) |
| subtract | - | 引き算(減算) |
| multiply | * | 掛け算(乗算) |
| divide | / | 割り算(除算) |
| remainder | % | 割り算の余り(剰余算) |
ここで注意すべき点は、BigDecimalを使って割り算を計算する場合、割り切れない数値を計算するとjava.lang.ArithmeticExceptionの例外が発生します。そのため、割り算する場合は、計算する小数点の桁数と桁からはみ出ている端数を切り捨てるのか切り上げるのかなどの処理を設定することが基本です。この端数処理(丸め処理)は、すでにJavaで用意されているものがありjava.math.RoundingModeで定義されているものを使う場合がほとんどです。よく使われるRoundingModeは次のものになります。
| RoundingModeの定数 | 概要 |
| HALF_UP | 四捨五入 |
| UP | 切り上げ。0から離れるように切り上げます。負の値の場合、例えば-5.5の小数点以下第1位を丸める場合、-6になります。 |
| DOWN | 切り捨て。0に近づくよう切り捨てます。負の値の場合、例えば-5.5の小数点以下第1位を丸める場合、-5になります。 |
また、切り詰めの際に、負の値の考え方にもよるのですが、次のものも業務アプリでは見かけることが多いです。
| RoundingModeの定数 | 概要 |
| CEILING | 切り上げ。正の無限大に近づくように切り上げます。正の値の場合はUPと同じです。しかし負の値の場合、例えば-5.5の小数点以下第1位を丸める場合、-5になります。 |
| FLOOR | 切り捨て。負の無限大に近づくよう切り捨てます。正の値の場合はDOWNと同じです。しかし負の値の場合、例えば-5.5の小数点以下第1位を丸める場合、-6になります。 |
それではBigDecimalがどのように使われるのか見てみましょう。次のサンプルを作成します。
package jp.codezine.java.sample03;
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
* BigDecimalのサンプルを実行しその内容を確認するクラスです。
*/
public class BigDecimalSample1 {
/**
* BigDecimalでの計算を実行し、その内容を確認します。
*
* @param args
* コマンドライン引数。今回は使われません。
*/
public static void main(String[] args) {
// [1] 足し算
BigDecimal value1 = new BigDecimal("0.7");
BigDecimal value2 = new BigDecimal("0.1");
BigDecimal result = value1.add(value2);
System.out.println("[1] 0.7 + 0.1 = " + result);
// [2] 引き算
result = value1.subtract(value2);
System.out.println("[2] 0.7 - 0.1 = " + result);
// [3] 掛け算
result = value1.multiply(value2);
System.out.println("[3] 0.7 × 0.1 = " + result);
// [4] 割り算
value1 = new BigDecimal("7.0");
value2 = new BigDecimal("3.0");
result = value1.divide(value2, 0, RoundingMode.DOWN); // 小数点未満を切り捨て
System.out.println("[4] 7.0 ÷ 3.0 = " + result);
// [5] 余り
value1 = new BigDecimal("7.0");
value2 = new BigDecimal("3.0");
result = value1.remainder(value2);
System.out.println("[5] 7.0 % 3.0 = " + result);
}
}
これを実行すると次の結果が表示されます。
[1] 0.7 + 0.1 = 0.8 [2] 0.7 - 0.1 = 0.6 [3] 0.7 × 0.1 = 0.07 [4] 7.0 ÷ 3.0 = 2 [5] 7.0 % 3.0 = 1.0
この結果を見ると分かるように、BigDecimalを使って計算を行うと、算術演算子を使った場合に発生していた誤差はなくなって意図した値になっています。このように浮動小数点の誤差が許されない計算ではBigDecimalを使った計算が基本になります。
ただし、BigDecimalを使って計算する場合も注意点があります。BigDecimalのコンストラクタの中には、引数が浮動小数点の数値を受けとるものがあります。ここで、BigDecimalのコンストラクタの引数に浮動小数点の数値を渡した場合、2進数で扱われているdouble値をBigDecimalが受け取った際に、内部で持つ10進数への変換で誤差が発生するためです。そのため、算術演算子の場合と同様、処理結果に誤差が発生してしまいます。そういう理由でBigDecimalのコンストラクタの引数に浮動小数点の数値を渡さないようにしてください。次のサンプルを実行すると、処理結果が意図したものにならないことが分かります。
package jp.codezine.java.sample03;
import java.math.BigDecimal;
/**
* BigDecimalのサンプルを実行しその内容を確認するクラスです。
*/
public class BigDecimalSample2 {
/**
* BigDecimalでの計算を実行し、その内容を確認します。
*
* @param args
* コマンドライン引数。今回は使われません。
*/
public static void main(String[] args) {
// [1] コンストラクタに浮動小数点を渡して計算
BigDecimal value1 = new BigDecimal(0.7);
BigDecimal value2 = new BigDecimal(0.1);
BigDecimal result = value1.add(value2);
System.out.println("value1= " + value1);
System.out.println("value2= " + value2);
System.out.println("0.7 + 0.1 = " + result);
}
}
value1= 0.6999999999999999555910790149937383830547332763671875 value2= 0.1000000000000000055511151231257827021181583404541015625 0.7 + 0.1 = 0.7999999999999999611421941381195210851728916168212890625
この問題の解決策として、BigDecimalにdouble値を引数にとるvalueOfメソッドでBigDecimalのインスタンスを生成する方法があります。この方法でBigDecimalのインスタンスを生成すると、コンストラクタの引数に文字列を入れた場合と同様のインスタンスを生成します。
★★ここまでCHK★★
しかし、BigDecimalのvalueOfメソッドの引数にfloat値を入れた場合、結果はまたしても意図したものになりません。これはBigDecimalのvalueOfメソッドの引数にfloat値のものが用意されておらず、Javaがfloat値をdouble値に自動で変換して処理を行っているためです。しかし、その際にfloatが持つ精度とdoubleが持つ精度では差があるため、この変換の時点で誤差が発生してしまいます。そのため、floatが暗黙的にdoubleに変換された際は、その精度の違いにより意図したdoubleの値ではなくなってしまいます。それでは次のサンプルを見てください。
package jp.codezine.java.sample03;
import java.math.BigDecimal;
/**
* BigDecimalのサンプルを実行しその内容を確認するクラスです。
*/
public class BigDecimalSample3 {
/**
* BigDecimalでの計算を実行し、その内容を確認します。
*
* @param args
* コマンドライン引数。今回は使われません。
*/
public static void main(String[] args) {
// [1] BigDecimal.valueOfメソッドを使った場合
BigDecimal value1 = BigDecimal.valueOf(0.7);
BigDecimal value2 = BigDecimal.valueOf(0.1);
BigDecimal result = value1.add(value2);
System.out.println("[1] 0.7 + 0.1 = " + result);
// [2] floatの場合
value1 = BigDecimal.valueOf(0.7f);
value2 = BigDecimal.valueOf(0.1f);
result = value1.add(value2);
System.out.println("[2] 0.7 + 0.1 = " + result);
// [3] floatがdoubleに自動変換された際の誤差
double convertedValue1 = 0.7f;
double convertedValue2 = 0.1f;
System.out.println("[3] 0.7f を double に変換 → " + convertedValue1);
System.out.println("[3] 0.1f を double に変換 → " + convertedValue2);
}
}
[1] 0.7 + 0.1 = 0.8 [2] 0.7 + 0.1 = 0.79999998956918712 [3] 0.7f を double に変換 → 0.699999988079071 [3] 0.1f を double に変換 → 0.10000000149011612
そのため、float値を扱う場合は注意が必要です。どうしてもfloatを使って正確な計算を行わないといけない場合、double値に自動的に変換される前にFloat.toStringメソッドで文字列に変換し、BigDecimalを生成するようにしましょう。
また、BigDecimalのコンストラクタの引数に文字列を渡して計算した場合、引数の小数点以下の桁数が多いほうに結果の小数点の桁数をあわせるようになっています。そのため、小数点以下の桁数が決まっている場合はBigDecimalのsetSacaleメソッドを使って桁数を調節するようにしてください。
package jp.codezine.java.sample03;
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
* BigDecimalのサンプルを実行しその内容を確認するクラスです。
*/
public class BigDecimalSample4 {
/**
* BigDecimalでの計算を実行し、その内容を確認します。
*
* @param args
* コマンドライン引数。今回は使われません。
*/
public static void main(String[] args) {
// [1] 小数点の桁数が違う場合の計算
BigDecimal value1 = new BigDecimal("0.7");
BigDecimal value2 = new BigDecimal("0.10");
BigDecimal result = value1.add(value2);
System.out.println("[1] 0.7 + 0.10 = " + result);
// [2] setScaleメソッドを使って小数点の桁数を調整
value1 = new BigDecimal("0.7");
value2 = new BigDecimal("0.10");
result = value1.add(value2);
result = result.setScale(1, RoundingMode.HALF_UP); // 小数点2位以下を四捨五入
System.out.println("[2] 0.7 + 0.10 = " + result);
}
}]
[1] 0.7 + 0.10 = 0.80 [2] 0.7 + 0.10 = 0.8
MathクラスとStrictMathクラス
正確な計算結果を求められる業務アプリの開発では、あまり使われることはありませんが、Javaでは基本的な四則計算のほかに平方根や三角関数など、さまざまな計算を行うためのメソッドを用意したjava.langMathクラスとjava.lang.StrictMathクラスがあります。Mathクラスは実行環境が持つ算出処理を利用して算出結果を返すのに対し、StrictMathクラスはすべてのJava環境で同じ結果を返すようにしています。そのため、環境によってはMathクラスとStrictMathクラスの間で実行結果の差異がでることがあります。
またMathクラスのほうは実行環境の算出処理を利用しているため、StrictMathよりも早く算出できるものとして理論上考えられています。
