web-dev-qa-db-ja.com

BigDecimal.equalsが値とスケールの両方を個別に比較するように指定されているのはなぜですか?

これは、2つのBigDecimalオブジェクトを比較する方法に関する質問ではありません。compareToequalsの代わりにequalsを使用して比較できることを知っています。文書化:

CompareToとは異なり、このメソッドは2つのBigDecimalオブジェクトが値とスケールが等しい場合にのみ等しいと見なします(したがって、このメソッドで比較すると、2.0は2.00と等しくありません)。

問題は、なぜequalsがこの一見逆に見える方法で指定されているのですか?つまり、2.0と2.00を区別できるのはなぜ重要なのでしょうか。

Comparableメソッドを指定するcompareToのドキュメントには次のように記載されているため、これには理由があるはずです。

自然な順序付けが等しいと一致することを強くお勧めします(必須ではありません)

この推奨を無視するのには十分な理由があるに違いないと思います。

34
bacar

状況によっては、精度の表示(エラーのマージンなど)が重要になる場合があります。

たとえば、2つの物理センサーによって行われた測定を保存している場合、おそらく1つは他の10倍の精度です。この事実を表すことが重要かもしれません。

25

他のどの回答でもまだ考慮されていない点は、equalshashCodeと一貫している必要があることと、必要なhashCode実装のコストです。 123.00と同じ値を123.0に生成すること(ただし、異なる値を区別するための合理的な仕事を行うこと)は、そうする必要のなかったhashCode実装の値よりもはるかに大きくなります。現在のセマンティクスでは、hashCodeには32の乗算が必要であり、32ビットの格納値ごとに加算します。 hashCodeが異なる精度の値間で一貫している必要がある場合は、任意の値(高価)の正規化された形式を計算するか、少なくとも、base-999999999のデジタルルートを計算するような処理を行う必要があります。値に基づいて、精度に基づいてmod 999999999を乗算します。このようなメソッドの内部ループは次のようになります。

temp = (temp + (mag[i] & LONG_MASK) * scale_factor[i]) % 999999999;

31による乗算を64ビットのモジュラス演算で置き換えると、はるかに高価になります。数値的に同等のBigDecimal値を同等と見なすハッシュテーブルが必要で、テーブルで検索されるほとんどのキーが見つかる場合、目的の結果を得る効率的な方法は、次のようなハッシュテーブルを使用することです。値を直接格納するのではなく、値ラッパーを格納します。テーブル内の値を見つけるには、まず値自体を探します。何も見つからない場合は、値を正規化して探します。何も見つからない場合は、空のラッパーを作成し、元の正規化された形式の数値の下にエントリを保存します。

テーブルにないものや以前に検索されていないものを探すには、費用のかかる正規化手順が必要ですが、検索されたものを探すのははるかに高速です。対照的に、HashCodeが、精度が異なるためにまったく異なる方法で格納されている数値と同等の値を返す必要がある場合、すべてのハッシュテーブル操作が大幅に遅くなります。

7
supercat

数学では、10.0は10.00と同じです。物理学では、10.0mと10.00mは間違いなく異なります(精度は異なります)。OOPでオブジェクトについて話すとき、私は間違いなくそれらは等しくないと言います。

Equalsがスケールを無視した場合の予期しない機能について考えるのも簡単です(たとえば、a.equals(b)の場合、a.add(0.1).equals(b.add(0.1)?).

数値が丸められると、計算の精度が表示されます-言い換えると:

  • 10.0は、正確な数が9.95から10.05の間であることを意味します
  • 10.00は、正確な数が9.995から10.005の間であることを意味します

言い換えれば、それは 算術精度 にリンクされています。

5
assylias

この推奨を無視するのには十分な理由があるに違いないと思います。

そうでないかもしれない。 BigDecimalの設計者が設計の選択を誤ったという簡単な説明を提案します。

  1. 優れた設計は、一般的なユースケースに合わせて最適化されます。ほとんどの場合(> 95%)、人々は数学的な同等性に基づいて2つの量を比較したいと考えています。 2つの数値がスケールと値の両方で等しいことに本当に注意を払っている少数の時間については、その目的のために追加の方法があったかもしれません。
  2. それは人々の期待に反し、陥りやすいトラップを作成します。優れたAPIは、「最小の驚きの原則」に従います。
  3. これは、通常のJava Comparableが等式と一致しているという規則に違反しています。

興味深いことに、ScalaのBigDecimalクラス(JavaのBigDecimalを使用して実装されている)は、反対の選択をしています。

BigDecimal("2.0") == BigDecimal("2.00")     // true
2
Matt R

compareToメソッドは、末尾のゼロがBigDecimalで表される数値に影響を与えないことを認識しています。これは、compareToが注意する唯一の側面です。対照的に、equalsメソッドは通常、誰かがオブジェクトのどの側面を気にしているかを知る方法がないため、2つのオブジェクトがeveryで同等である場合にのみtrueを返す必要があります=プログラマーが興味を持つかもしれない方法。x.equals(y)がtrueである場合、x.toString().equals(y.toString())がfalseを返すのは意外です。

おそらくさらに重要なもう1つの問題は、BigDecimalが基本的にBigIntegerとスケーリング係数を組み合わせているため、2つの数値が同じ値を表しているが後続のゼロの数が異なる場合、 a bigIntegerの値は、他の10の累乗です。等式で仮数とスケールの両方が一致する必要がある場合、BigDecimalhashCode()BigIntegerのハッシュコードを使用できます。ただし、2つの値が異なるBigInteger値を含んでいても、「等しい」と見なされる可能性がある場合は、事態が非常に複雑になります。 BigDecimalではなく、独自のバッキングストレージを使用するBigInteger型をさまざまな方法で実装して、同じ数値を表す値がすばやくハッシュされるように数値をハッシュすることができます。を比較します(簡単な例として、各long値に9桁の10進数をパックし、小数点が9のグループの間にあることを常に要求するバージョンでは、末尾を無視する方法でハッシュコードを計算できます値がゼロのグループ)ですが、BigDecimalをカプセル化するBigIntegerはそれを行うことができません。

1
supercat