無限比較がNaNに適用されるロジックに従っていないのはなぜですか?このコードはfalse
を3回出力します。
double a = Double.NaN;
double b = Double.NaN;
System.out.println(a == b); // false
System.out.println(a < b); // false
System.out.println(a > b); // false
ただし、Double.NaN
からDouble.POSITIVE_INFINITY
、等しい場合はtrue
を取得しますが、大なり比較と小なり比較の場合はfalse
を取得します。
double a = Double.POSITIVE_INFINITY;
double b = Double.POSITIVE_INFINITY;
System.out.println(a == b); // true
System.out.println(a < b); // false
System.out.println(a > b); // false
これは危険なようです。無限値がオーバーフローの結果であると仮定すると、無限大として終了した2つの変数が、完全な計算では実際には等しくない可能性が高いと思います。
あなたの推論は、正確さの損失の結果として得られた可能性が高いので、Double.POSITIVE_INFINITY
はそれ自体と等しくないはずです。
この推論の行は、すべての浮動小数点に適用されます。不正確な操作の結果として、任意の有限値を取得できます。それはIEEE 754標準化委員会に==
を有限値に対して常にfalseと評価するように定義するようにプッシュしなかったので、なぜ無限大は異なるのでしょうか?
定義されているように、==
は、それが何をするかを理解している人に役立ちます(つまり、取得した浮動小数点値をテストします、実際の計算で得られるはずの値ではありません)。それを理解していて、無限大を含まない計算でも浮動小数点を使用するためにそれを理解する必要がある場合、Double.POSITIVE_INFINITY == Double.POSITIVE_INFINITY
をtrueに評価すると、浮動小数点の結果かどうかをテストするだけで便利です浮動小数点計算はDouble.POSITIVE_INFINITY
です。
これはなぜNaNが特別な振る舞いをする余裕があるのかという疑問を残し、無限大は有限値と同じ一般原則に従う必要があります。 NaNは無限大とは異なります。IEEE754標準の基本的な原則は、値が正確に何であるかということですが、演算の結果は、実際の結果、およびこの場合、結果の浮動小数点値に対して概算できます。丸めモードに従って取得されます。
1.0 / 0.0
が+ infとして定義されていることを一瞬忘れてください。これは、この説明では厄介です。 Double.POSITIVE_INFINITY
の瞬間は、1.0e100 / 1.0e-300
やDouble.MAX_VALUE + Double.MAX_VALUE
などの操作の結果としてのみ考えてください。これらの演算の場合、有限の結果を生成する演算と同様に、+ infは実際の結果に最も近い近似です。対照的に、NaNは、操作が意味をなさない場合に得られる結果です。 NaNを特別に動作させることは防御できますが、infは、表現するには大きすぎるすべての値の近似にすぎません。
実際には、1.0 / 0.0
も+ infを生成しますが、thatは例外と見なす必要があります。その演算の結果をNaNとして定義することも同じように一貫していたでしょうが、+ infとして定義することは、一部のアルゴリズムの実装ではより便利でした。例は Kahanのノート の10ページにあります。 記事「複雑な基本関数の分岐カット、または何もない符号ビットに関する多くのやりかた」 に詳細が記載されています。また、IEEE 754でNaNフラグとは別の「ゼロによる除算」フラグが存在することを、ユーザーがNaNを生成するように定義されていなくても、ユーザーがゼロによる除算を特別に処理したいという認識として解釈します。
それが標準だからです。無限大は、Double.MAX_VALUE/-Double.MAX_VALUEより大きいまたは小さい数値を表します。
NaNは、意味のない操作の結果を表します。つまり、操作は数字で出てこなかったのかもしれません。
私は、論理が数値が十分大きくなると(無限大)、ロジックは浮動小数点数の制限のため、それに数値を追加しても結果は変化しないので、その「無限」のようになると思います。
したがって、本当に大きな数値と比較したい場合、ある時点で、これらの2つの大きな数値は、すべての意図と目的にとって十分に近いと言えます。しかし、両方が数値ではない2つのものを比較したい場合は、それらを比較できないため、偽になります。少なくとも、それらをプリミティブとして比較することはできませんでした。
なぜ無限大は等しいのですか?それが機能するので。
浮動小数点演算は、エラーを保存する(比較的)高速な計算を生成するように設計されています。長い計算中にオーバーフローやその他のナンセンスをチェックしないという考えです。それが完了するまで待ちます。これが、NaNがその方法で伝播する理由です。NaNを取得すると、それを解消するために実行できることはほとんどありません。計算が終了したら、NaNを探して、問題が発生したかどうかを確認できます。
無限大についても同じです。オーバーフローの可能性がある場合は、無限大を捨てるようなことをしないでください。
遅く安全にしたい場合、IEEE-754にはトラップハンドラーをインストールするメカニズムがあり、計算の結果がNaNまたは無限大になる場合にコードにコールバックを提供します。ほとんどそれは使用されません。コードが適切にデバッグされた後は、通常は非常に遅く、無意味です(これは簡単ではありません。これを行うには、PhDをうまく取り入れることができます)。
「無限」の値が等しいことを正当化する別の視点は、 カーディナリティー の概念を完全に回避することです。基本的に、「値が別の値と比較してどれだけ無限であるかを仮定して、両方が無限であることを前提として」推測できない場合は、Inf = Inf
であると仮定する方が簡単です。
編集:カーディナリティに関するコメントの明確化として、無限量の比較(または同等性)に関する2つの例を示します。
正の整数S1 = {1,2,3, ...}
のセットを考えます。これは無限です。また、無限の偶数整数S2 = {2,4,6, ...}
のセットも考慮してください。 S1にはS2の2倍の数の要素がありますが、セット間で1対1の関数を簡単に持つことができるため、これらの要素には「等しい数の」要素があります。つまり、1 -> 2
、2-> 4
、 ...したがって、カーディナリティは同じです。
代わりに、実数のセットR
と整数のセットI
を検討してください。この場合も、両方とも無限のセットです。ただし、各整数i
には、(i, i+1)
の間に実数が無限にあります。したがって、これらの2つのセットの要素を1対1の関数でマップすることはできないため、カーディナリティは異なります。
ボトムライン:無限量の等価性は複雑であり、命令型言語では回避しやすい:)
私にとっては、「ゼロと同じように動作する必要があるため」が適切な答えになると思われます。算術オーバーフローとアンダーフローも同様に処理できるはずです。
フロートに格納できる最大に近い無限小の値からアンダーフローすると、ゼロが取得され、ゼロは同一と比較されます。
フロートに格納できる最大に近い無限大の値からオーバーフローすると、INFが取得され、INFは同じものとして比較されます。
これは、両方向で範囲外の数値を処理するコードでは、どちらか一方に個別の特別なケースを必要としないことを意味します。代わりに、両方を処理するか、どちらも処理を変える必要があります。
そして、最も単純な要件は「どちらでもない」ケースでカバーされています。何かがオーバーフローまたはアンダーフローしているかどうかを確認したい場合、現在の言語の特別な構文を知らなくても、通常の算術比較演算子だけを使用してそれをゼロ/ INFと比較できます。チェックコマンド:Math.isInfinite()、Float.checkForPositiveInfinity()、hasOverflowed()...ですか?
正解の答えは単純です。「 標準 (そして ドキュメント )がそう言うからです」 。しかし、それがあなたが求めているものではないことは明らかなので、私はシニカルではありません。
ここでの他の答えに加えて、無限を飽和演算に関連付けようとします。
他の回答では、NaNでの比較の結果がtrue
になると既に述べているため、死んだ馬を倒すつもりはありません。
グレースケールカラーを表す飽和整数があるとします。飽和演算を使用するのはなぜですか?白よりも何か明るいはまだ白であり、黒よりもより暗いのでまだ黒です( オレンジ を除く)。つまり、BLACK - x == BLACK
およびWHITE + x == WHITE
です。理にかなっていますか?
ここで、それらのグレースケールカラーを(符号付き) 1の補数 8ビット整数(BLACK == -127
およびWHITE == 127
)で表現したいとします。なぜ1が補完するのですか? IEEE 754浮動小数点 のように 符号付きゼロ を与えるからです。また、飽和算術演算を使用しているため、-127 - x == -127
および127 + x == 127
。
これは浮動小数点無限大とどのように関係していますか?整数を浮動小数点に、BLACK
をNEGATIVE_INFINITY
に、WHITE
をPOSITIVE_INFINITY
に置き換えます。何が得られますか? NEGATIVE_INFINITY - x == NEGATIVE_INFINITY
およびPOSITIVE_INFINITY + x == POSITIVE_INFINITY
。
POSITIVE_INFINITY
を使用したので、私も使用します。最初に、飽和する整数ベースの色を表すクラスが必要です。それをSaturatedColor
と呼び、Javaの他の整数と同様に機能すると仮定します。次に、コードを取得して、double
を独自のSaturatedColor
に置き換え、Double.POSITIVE_INFINITY
をSaturatedColor.WHITE
に置き換えます。
SaturatedColor a = SaturatedColor.WHITE;
SaturatedColor b = SaturatedColor.WHITE;
上記で確立したように、SaturatedColor.WHITE
(上記のWHITE
のみ)は127
なので、ここでそれを実行しましょう。
SaturatedColor a = 127;
SaturatedColor b = 127;
次に、使用したSystem.out.println
ステートメントを取得し、a
とb
をそれらの値(値?)に置き換えます。
System.out.println(127 == 127);
System.out.println(127 < 127);
System.out.println(127 > 127);
これが何を印刷するかは明らかです。
Double.Nan.equals(Double.NaN)について触れたので、これは、数値を計算して数値を比較するときに起こるべきことの1つですが、オブジェクトの動作を考えると、まったく異なります。
典型的な2つの問題のケースは次のとおりです。数値の配列をソートすること、およびハッシュ値を使用して辞書、セットなどを実装すること。 <、=、>の通常の順序付けが適用されない2つの例外的なケースがあります。1つは+0 = -0で、もう1つはNaN≠NaNであり、x <NaN、x> NaN、x = NaNです。 xが何であれ常にfalseになります。
並べ替えアルゴリズムはこれで問題に陥る可能性があります。ソートアルゴリズムは、x = xが常にtrueであると想定する場合があります。したがって、xが配列に格納されていることを知っていて、それを探す場合、その検索で何かを見つける必要があるため、境界チェックを行わない可能性があります。 xがNaNでない場合。並べ替えアルゴリズムでは、a <bとa> = bのどちらか一方のみが真である必要があると想定する場合があります。 NaNの場合はそうではありません。したがって、NaNが存在する場合、単純なソートアルゴリズムがクラッシュする可能性があります。配列をソートするときにNaNをどこに配置するかを決定し、それが機能するように比較コードを変更する必要があります。
さて、辞書とセット、そして一般的にハッシング:NaNをキーとして使用するとどうなりますか?セットには一意のオブジェクトが含まれています。セットにNaNが含まれていて、もう1つ追加しようとした場合、すでに存在するものと等しくないので一意ですか? +0と-0はどうですか、それらは等しいか異なると考えるべきですか?等しいと見なされる2つの項目は、同じハッシュ値でなければならないという規則があります。したがって、賢明なことは、(おそらく)ハッシュ関数がすべてのNaNに対して1つの一意の値を返し、+ 0と-0に対して1つの一意の値を返すことです。そして、実際に等しい同じハッシュ値を持つ要素を見つける必要がある場合のハッシュルックアップの後、2つのNaNは等しいと見なされます(ただし、他のものとは異なります)。
これが、Double.Nan.equal()の動作が==と異なる理由です。
これは、NaNが数値ではないため、NaNを含むどの数値とも等しくないためです。