JavaのNaNがおかしい(というか、Doubleがおかしい)
doubleはNaN*1を値として取ることができる(その他にも、正の無限大、負の無限大をとることもできる)。これはIEEE 754で決められている。
NaNはNaNと比較しても等しくないという性質を持っている。つまり、
// aはNaNになる。 double a = 0.0 / 0.0; System.out.println(a == a);
とするとfalseと表示される。これはバグではない。仕様だ。
しかし、doubleのラッパークラス*2Doubleを用いて
// bはNaNを表すDoubleオブジェクトになる。 Double b = new Double(0.0 / 0.0); System.out.println(b.equals(b));
とするとtrueとなってしまう。
これはDouble#equalsメソッドの実装ミスではないのだろうか。それとも、NaN同士の比較結果をfalseにしてしまうとComparatorやComparableの実装上の問題*3が生じるので、あえてtrueとしているのだろうか。
追記(2008-08-05)
リファレンスできちんと言及されていた。しっかり確認しないまま書いてしまった。
ほとんどの場合、Double クラスの d1 および d2 という 2 つのインスタンスについて、d1.equals(d2) の値が true になるのは、次の式の値が true になる場合だけです。
d1.doubleValue() == d2.doubleValue()
しかし、例外事項も 2 つあります。
* d1 と d2 の両方が Double.NaN を表し、Double.NaN==Double.NaN の値が false であるにもかかわらず、equals メソッドが true を返す場合
* d1 が +0.0 を表し、d2 が -0.0 を表すか、あるいは d1 が -0.0 を表し、d2 が +0.0 を表す場合で、+0.0==-0.0 の値が true であるにもかかわらず、equal テストの値が false の場合この定義によって、ハッシュテーブルは正しく動作します。
Double#equals
*1:NaNはNot a Numberの略。非数を表す。例えば、0.0/0.0や√-1の演算結果などがNaNになる
*2:ラッパークラスとは、プリミティブ型であるbyte、short、int、long、float、double、char、boolean、voidを参照型として表したいときに用いる、Byte、Short、Integer、Long、Float、Double、Character、Boolean、Voidのことを指す。
*3:NaNとNaNは等しくないので、compareメソッドはNaN同士を比較したときに0を返してはいけない。しかし、負数を返しても正数を返しても、compareメソッドに与える引数を入れ替えると大小関係が反転してしまう。よって、どのような値を返しても不都合が起こる。