web-dev-qa-db-ja.com

Java二重比較イプシロン

Javaで2つのdoubleを使用して、等しい、より小さい、より大きいをテストするクラスを作成しました。私の一般的なケースは、半セントの精度を持つことができる価格を比較することです。 59.395と比較した59.005。私が選んだイプシロンは、これらの場合に適切ですか?

private final static double EPSILON = 0.00001;


/**
 * Returns true if two doubles are considered equal.  Tests if the absolute
 * difference between two doubles has a difference less then .00001.   This
 * should be fine when comparing prices, because prices have a precision of
 * .001.
 *
 * @param a double to compare.
 * @param b double to compare.
 * @return true true if two doubles are considered equal.
 */
public static boolean equals(double a, double b){
    return a == b ? true : Math.abs(a - b) < EPSILON;
}


/**
 * Returns true if two doubles are considered equal. Tests if the absolute
 * difference between the two doubles has a difference less then a given
 * double (epsilon). Determining the given epsilon is highly dependant on the
 * precision of the doubles that are being compared.
 *
 * @param a double to compare.
 * @param b double to compare
 * @param epsilon double which is compared to the absolute difference of two
 * doubles to determine if they are equal.
 * @return true if a is considered equal to b.
 */
public static boolean equals(double a, double b, double epsilon){
    return a == b ? true : Math.abs(a - b) < epsilon;
}


/**
 * Returns true if the first double is considered greater than the second
 * double.  Test if the difference of first minus second is greater then
 * .00001.  This should be fine when comparing prices, because prices have a
 * precision of .001.
 *
 * @param a first double
 * @param b second double
 * @return true if the first double is considered greater than the second
 *              double
 */
public static boolean greaterThan(double a, double b){
    return greaterThan(a, b, EPSILON);
}


/**
 * Returns true if the first double is considered greater than the second
 * double.  Test if the difference of first minus second is greater then
 * a given double (epsilon).  Determining the given epsilon is highly
 * dependant on the precision of the doubles that are being compared.
 *
 * @param a first double
 * @param b second double
 * @return true if the first double is considered greater than the second
 *              double
 */
public static boolean greaterThan(double a, double b, double epsilon){
    return a - b > epsilon;
}


/**
 * Returns true if the first double is considered less than the second
 * double.  Test if the difference of second minus first is greater then
 * .00001.  This should be fine when comparing prices, because prices have a
 * precision of .001.
 *
 * @param a first double
 * @param b second double
 * @return true if the first double is considered less than the second
 *              double
 */
public static boolean lessThan(double a, double b){
    return lessThan(a, b, EPSILON);
}


/**
 * Returns true if the first double is considered less than the second
 * double.  Test if the difference of second minus first is greater then
 * a given double (epsilon).  Determining the given epsilon is highly
 * dependant on the precision of the doubles that are being compared.
 *
 * @param a first double
 * @param b second double
 * @return true if the first double is considered less than the second
 *              double
 */
public static boolean lessThan(double a, double b, double epsilon){
    return b - a > epsilon;
}
41
rich

お金を表すためにdoubleを使用しないでください。決してない。つかいます - Java.math.BigDecimal 代わりに。

次に、丸めの正確な方法を指定することができます(これは、金融アプリケーションの法律によって決定される場合があります!)このイプシロンのような愚かなハックを行う必要はありません。

真剣に、浮動小数点型を使用してお金を表すことは非常に専門的ではありません。

101

はい。 Java doublesは、指定された0.00001のイプシロンよりも精度が高くなります。

浮動小数点値の格納のために発生する丸めエラーは、0.00001未満で発生します。私は定期的に1E-6またはJavaの問題なしの二重イプシロンの場合は0.000001。

関連するメモでは、epsilon = 1E-5;読みやすいと思うので(1E-5 Java = 1 x 10 ^ -5)。10001はコードを読むときに1E-5と区別しやすいが、0.00001と0.000001は、コードを一見すると同じように見えますが、同じ値だと思います。

11
Alex B

おおおおおおお。通貨に浮動小数点を使用している特定の理由がありますか、または 任意精度、固定小数点数形式 の方が良いでしょうか?あなたが解決しようとしている特定の問題が何であるかはわかりませんが、半セントが本当にあなたが扱いたいものであるかどうか、またはそれが不正確な数値形式を使用するだけの成果物であるかどうかを考える必要があります。

8
Josh Lee

BigDecimalを使用できる場合は、それを使用します。それ以外の場合:

/**
  *@param precision number of decimal digits
  */
public static boolean areEqualDouble(double a, double b, int precision) {
   return Math.abs(a - b) <= Math.pow(10, -precision);
}
6
carlosvin

お金を扱う場合は、Moneyのデザインパターンを確認することをお勧めします(元々 Martin Fowlerのエンタープライズアーキテクチャ設計に関する本 から)。

モチベーションのためにこのリンクを読むことをお勧めします: http://wiki.moredesignpatterns.com/space/Value+Object+Motivation+v2

5
Yuval Adam

ダブルは金銭的に悪いという考えには同意しますが、ダブルを比較するという考え方には興味があります。特に、提案されているイプシロンの使用は、特定の範囲の数値にのみ適しています。次に、2つの数値の比率に対するイプシロンのより一般的な使用方法を示します(0のテストは省略されます)。

boolean equal(double d1, double d2) {
  double d = d1 / d2;
  return (Math.abs(d - 1.0) < 0.001);
}
2
Bill

浮動小数点数の有効桁数は非常に多くなりますが、さらに大きくなる可能性があります。アプリで大量のデータを処理する場合、イプシロン値は異なるはずです。

0.001 + 0.001 = 0.002ただし、浮動小数点と倍精度を使用している場合は、12,345,678,900,000,000,000,000 + 1 = 12,345,678,900,000,000,000,000です。このシステムで100万ドル以上を決して処理しないと確信しているのでない限り、それはお金の良い表現ではありません。

1
Karl

セント?お金の値を計算する場合、実際には浮動小数点値を使用しないでください。お金は実際には数えられる価値です。セントやペニーなどは、整数の最下位2桁(または何でも)と見なすことができます。お金の値を整数として保存および計算し、100で除算することができます(たとえば、最後の2桁の前にドットまたはコンマを2つ配置します)。フロートを使用すると、奇妙な丸めエラーが発生する可能性があります...

とにかく、イプシロンが精度を定義することになっている場合、イプシロンは少し小さすぎます(正確すぎます)...

1

他のコメンターが正しく指摘しているように、通貨値などの正確な値が必要な場合は、never浮動小数点演算を使用する必要があります。主な理由は確かに浮動小数点に固有の丸め動作ですが、浮動小数点を扱うことは無限値とNaN値も扱う必要があることを忘れないでください。

あなたのアプローチが単にうまくいかないという実例として、ここにいくつかの簡単なテストコードがあります。単にEPSILON10.0に追加し、結果が10.0と等しいかどうかを確認します-違いは明らかではないのでlessEPSILONより:

    double a = 10.0;
    double b = 10.0 + EPSILON;
    if (!equals(a, b)) {
        System.out.println("OK: " + a + " != " + b);
    } else {
        System.out.println("ERROR: " + a + " == " + b);
    }

驚き:

    ERROR: 10.0 == 10.00001

エラーは、2つの浮動小数点値の指数が異なる場合、減算時に有効ビットが失われると発生します。

他のコメンターが提案する、より高度な「相対差」アプローチを適用することを考えている場合は、Bruce Dawsonの優れた記事 Comparing Floating Point Numbers、2012 Edition を読む必要があります。実際には、noすべての範囲の浮動小数点数で機能するフェイルセーフ近似浮動小数点比較があります。

物事を短くするには:金銭的価値のためにdoublesを控え、BigDecimalなどの正確な数値表現を使用します。効率を上げるために、オーバーフローとアンダーフローを確実に防ぐ限り、「[ミリ]」と解釈されるlongsを使用することもできます。これにより、9'223'372'036'854'775.807の表現可能な最大値が得られます。これは、ほとんどの実際のアプリケーションに十分なはずです。

0
Franz D.