1. 概要

このチュートリアルでは、Javaでdouble値を比較するさまざまな方法について説明します。 特に、他のプリミティブ型を比較するほど簡単ではありません。 実際のところ、Javaだけでなく、他の多くの言語でも問題があります。

最初に、simple ==演算子の使用が不正確であり、実行時にバグを追跡するのが困難になる可能性がある理由を説明します。 次に、プレーンJavaと一般的なサードパーティライブラリのdoubleを正しく比較する方法を示します。

2. ==演算子の使用

==演算子を使用した比較の不正確さは、double値がコンピューターのメモリーに格納される方法が原因で発生します。 限られたメモリスペース(通常は64ビット)に収まらなければならない値が無限にあることを覚えておく必要があります。 その結果、コンピューターでほとんどのdouble値を正確に表現することはできません。 保存するには丸める必要があります

丸めが不正確なため、興味深いエラーが発生する可能性があります。

double d1 = 0;
for (int i = 1; i <= 8; i++) {
    d1 += 0.1;
 }

double d2 = 0.1 * 8;

System.out.println(d1);
System.out.println(d2);

d1d2、の両方の変数は0.8に等しくなければなりません。 ただし、上記のコードを実行すると、次の結果が表示されます。

0.7999999999999999
0.8

その場合、両方の値を==演算子と比較すると、間違った結果が生成されます。 このため、より複雑な比較アルゴリズムを使用する必要があります。

丸めメカニズムを最高の精度で制御したい場合は、java.math.BigDecimalクラスを使用できます。

3. プレーンJavaでのDoubleの比較

プレーンJavaでdouble値を比較するために推奨されるアルゴリズムは、しきい値比較方法です。 この場合、両方の数値の差が、一般に epsilon:と呼ばれる指定された許容範囲内にあるかどうかを確認する必要があります。

double epsilon = 0.000001d;

assertThat(Math.abs(d1 - d2) < epsilon).isTrue();

イプシロンの値が小さいほど、比較精度は高くなります。 ただし、許容値の指定が小さすぎると、単純な==比較の場合と同じ誤った結果が得られます。 一般に、小数点以下5桁および6桁のイプシロンの値は、通常、を開始するのに適した場所です。

残念ながら、推奨される正確な方法でdouble値を比較するために使用できる標準JDKのユーティリティはありません。 幸いなことに、自分で書く必要はありません。 無料で広く知られているサードパーティライブラリが提供するさまざまな専用メソッドを使用できます。

4. ApacheCommonsMathの使用

Apache Commons Math は、数学および統計コンポーネント専用の最大のオープンソースライブラリの1つです。 さまざまなクラスとメソッドから、 特にorg.apache.commons.math3.util.Precisionクラスに焦点を当てます。 double値を正しく比較するための2つの便利なequals()メソッドが含まれています

double epsilon = 0.000001d;

assertThat(Precision.equals(d1, d2, epsilon)).isTrue();
assertThat(Precision.equals(d1, d2)).isTrue();

ここで使用されているepsilon変数は、前の例と同じ意味です。 許容される絶対誤差の量です。 ただし、しきい値アルゴリズムとの類似点はこれだけではありません。 特に、両方の equals メソッドは、内部で同じアプローチを使用します。

2引数関数バージョンは、 equals(d1、d2、1)メソッド呼び出しの単なるショートカットです。 この場合、d1d2の間に浮動小数点数がない場合、それらは等しいと見なされます。

5. グアバの使用

GoogleのGuavaは、標準のJDK機能を拡張するコアJavaライブラリの大きなセットです。 com.google.common.mathパッケージに多数の便利な数学ユーティリティが含まれています。 Guavaでdouble値を正しく比較するために、 DoubleMath class からfuzzyEquals()メソッドを実装しましょう。

double epsilon = 0.000001d;

assertThat(DoubleMath.fuzzyEquals(d1, d2, epsilon)).isTrue();

メソッド名はApacheCommonsMathとは異なりますが、内部では実質的に同じように機能します。 唯一の違いは、イプシロンのデフォルト値でオーバーロードされたメソッドがないことです。

6. JUnitの使用

JUnit は、Javaで最も広く使用されている単体テストフレームワークの1つです。 一般に、すべての単体テストは通常、期待値と実際の値の差を分析することで終了します。 したがって、テストフレームワークには、正確で正確な比較アルゴリズムが必要です。 実際、JUnitは、共通のオブジェクト、コレクション、およびプリミティブ型の一連の比較メソッドを提供します。これには、double値の同等性をチェックするための専用メソッドが含まれます。

double epsilon = 0.000001d;
assertEquals(d1, d2, epsilon);

実際のところ、これは前述のGuavaおよびApacheCommonsの方法と同じように機能します。

イプシロン引数のない非推奨の2引数バージョンもあることを指摘することが重要です。 ただし、結果が常に正しいことを確認したい場合は、3つの引数のバージョンを使用する必要があります。

7. 結論

この記事では、Javaのdouble値を比較するさまざまな方法について説明しました。

単純な比較により、実行時にバグを追跡するのが困難になる理由を説明しました。 次に、プレーンJavaと一般的なライブラリの値を正しく比較する方法を示しました。

いつものように、例のソースコードはGitHubにあります。