44

これは、2つのオブジェクトを比較する方法についての質問ではありません。次のように文書化されているため、その代わりにBigDecimal使用できることを知っています。compareToequalsequals

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

問題は、なぜequalsこの一見直感に反する方法で指定されたのかということです。つまり、2.0と2.00を区別できることが重要なのはなぜですか。

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

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

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

4

7 に答える 7

34

状況によっては、精度(つまり許容誤差)の表示が重要になる場合があるためです。

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

于 2012-12-31T13:20:33.807 に答える
12

の一般的な規則equalsは、2つの等しい値が互いに代替可能である必要があるということです。つまり、1つの値を使用して計算を実行すると何らかの結果が得られる場合equals、同じ計算に値を代入するとequals、最初の結果となる結果が得られるはずです。これは、、、などStringの値であるオブジェクトに適用されます。IntegerBigDecimal

ここで、BigDecimal値2.0と2.00について考えます。それらが数値的に等しいことはわかっており、compareToそれらについては0を返します。しかしequalsfalseを返します。なんで?

これらが代替可能ではない例を次に示します。

var a = new BigDecimal("2.0");
var b = new BigDecimal("2.00");
var three = new BigDecimal(3);

a.divide(three, RoundingMode.HALF_UP)
==> 0.7

b.divide(three, RoundingMode.HALF_UP)
==> 0.67

結果は明らかに等しくないため、の値は。のa代わりにはなりませんb。したがって、a.equals(b)falseである必要があります。

于 2021-02-12T23:49:28.103 に答える
9

他のどの回答でもまだ考慮されていない点は、equalsと一致する必要があること、および123.00と同じ123.0の値を生成するために必要な実装hashCodeのコストです(ただし、hashCode異なる値を区別すること)は、そうする必要がなかったhashCode実装のそれよりもはるかに大きくなります。現在のセマンティクスでhashCodeは、31を掛けて、保存された値の32ビットごとに加算する必要があります。もしもhashCode精度の異なる値間で一貫性を保つ必要がある場合は、任意の値の正規化された形式(高価)を計算するか、少なくとも、値の基数99999999の数字根を計算して、それを乗算するようなことを行う必要があります。精度に基づく999999999。このようなメソッドの内部ループは次のようになります。

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

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

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

于 2014-07-25T23:25:37.967 に答える
6

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

また、equalsがスケールを無視した場合、予期しない機能を考えるのも簡単です(たとえば、a.equals(b)の場合、a.add(0.1).equals(b.add(0.1)?)を期待しませんか?)。

于 2012-12-31T13:21:14.503 に答える
5

数値が四捨五入されると、計算の精度が示されます。つまり、次のようになります。

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

つまり、算術精度にリンクされています。

于 2012-12-31T13:22:45.830 に答える
2

このcompareToメソッドは、末尾のゼロがaで表される数値に影響を与えないことを認識しています。BigDecimalこれは唯一の側面compareToです。対照的に、このequalsメソッドは通常、誰かが気にかけているオブジェクトの側面を知る方法がないため、プログラマーが関心を持つ可能性のあるすべてtrueの点で2つのオブジェクトが同等である場合にのみ返されます。 falseを生成するため。x.equals(y)x.toString().equals(y.toString())

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

于 2013-01-21T20:20:20.017 に答える
2

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

そうでないかもしれない。BigDecimalのデザイナーが悪いデザインを選んだという簡単な説明を提案します。

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

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

BigDecimal("2.0") == BigDecimal("2.00")     // true
于 2014-07-11T08:24:41.043 に答える