quabbinの日記: [Java] JavaにおけるNaN比較 2
先日の続き。
Javaではどうなるか気になったので試してみました。
C:\Develop>copy con NaN.java
public class NaN {
public static void main (String[] args) {
System.out.println(0.0 / 0 == 0.0 / 0);
}
}
^Z
1 個のファイルをコピーしました。
C:\Develop>^Z
C:\Develop>\Develop\Language\jdk1.6.0_07\bin\javac.exe NaN.java
C:\Develop>\Develop\Language\jdk1.6.0_07\bin\java.exe NaN
false
普通にfalseになるようです。
実際はどんな動きをしているか、javapを使ってみてみました。
C:\Develop>\Develop\Language\jdk1.6.0_07\bin\javap.exe -verbose NaN
(中略)
public static void main(java.lang.String[]);
Code:
Stack=2, Locals=1, Args_size=1
0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
3: iconst_0
4: invokevirtual #3; //Method java/io/PrintStream.println:(Z)V
7: return
LineNumberTable:
line 3: 0
line 4: 7
…あ、コンパイラが最適化してたorz
というわけで、やりなおし。
C:\Develop>del NaN.*
C:\Develop>copy con NaN.java
public class NaN {
public static float getZero() {
return 0.0f;
}
public static void main(String[] args) {
System.out.println(getZero() / 0 == getZero() / 0);
}
}
^Z
1 個のファイルをコピーしました。
C:\Develop>\Develop\Language\jdk1.6.0_07\bin\javac.exe NaN.java
C:\Develop>\Develop\Language\jdk1.6.0_07\bin\javap.exe -verbose NaN
(中略)
public static void main(java.lang.String[]);
Code:
Stack=4, Locals=1, Args_size=1
0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
3: invokestatic #3; //Method getZero:()F
6: fconst_0
7: fdiv
8: invokestatic #3; //Method getZero:()F
11: fconst_0
12: fdiv
13: fcmpl
14: ifne 21
17: iconst_1
18: goto 22
21: iconst_0
22: invokevirtual #4; //Method java/io/PrintStream.println:(Z)V
25: return
(中略)
C:\Develop>\Develop\Language\jdk1.6.0_07\bin\java.exe NaN
false
今度は問題無さそうです。そして動作は、やはりfalseでした。
# しかし、実行時最適化されているかどうかは不明です
# このあたりのJavaの動きは、ある種クレイジーで面白いわけですが。
では、計算しない場合はどうなのでしょう。
C:\Develop>copy con StaticNaN.java
public class StaticNaN {
public static float getNaN() {
return Float.NaN;
}
public static void main(String[] args) {
System.out.println(getNaN() == getNaN());
}
}
^Z
1 個のファイルをコピーしました。
C:\Develop>\Develop\Language\jdk1.6.0_07\bin\javac.exe StaticNaN.java
C:\Develop>\Develop\Language\jdk1.6.0_07\bin\javap.exe -verbose StaticNaN
(中略)
public static void main(java.lang.String[]);
Code:
Stack=3, Locals=1, Args_size=1
0: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
3: invokestatic #4; //Method getNaN:()F
6: invokestatic #4; //Method getNaN:()F
9: fcmpl
10: ifne 17
13: iconst_1
14: goto 18
17: iconst_0
18: invokevirtual #5; //Method java/io/PrintStream.println:(Z)V
21: return
(中略)
C:\Develop>\Develop\Language\jdk1.6.0_07\bin\java.exe StaticNaN
false
やはりfalseです。
こちらもPythonと同じ結果が得られました。
実装上、たまたまそうなっているだけでしょうか。
Java言語仕様のFloating-Point Operationsを見ると、
a numeric comparison operation involving one or two NaNs returns false and any != comparison involving NaN returns true, including x!=x when x is NaN
と、同一の変数であっても==での比較結果は常にfalseと明示的に書かれています。
世の中的には、どうやらx=NaNの時、x==xはfalseのようです。
しかし、それと数学とが一致しているかは、キット別の話なのかなぁと思いつつ、数学が弱いので判別つきません。
ん~。数学の勉強、どっかでしなければ。
N/A (スコア:0)
数学的に、というならば NaN はそもそも「値」ではなく、大小や等しいという概念もない状態なので比較はできないでしょう。(*1)
計算機としては NaN == NaN の評価値は N/A としたいところですが、Java の比較演算子の評価は true/false の値しか持たず、これをそれぞれ true or not 的な扱いで適用しているので結果的に false となっているのだと思います。
Javascript のような処理系でも比較演算子が NaN のような第三値となることはないみたいですね。
SQL では null 比較を = ではなく is や is not で行うというところにも目を向けると面白いかもしれません。
*1)
これに対して Infinity, -Infinity は(やはり値ではありませんが)比較可能な状態です。
Re:N/A (スコア:2)
本当はNaNは比較さえできないものなので、比較を含めてなんらかの処理を行った段階で例外を起こす(Signaling NaN)か、そうでなければ(QuietNaNであれば)仕方ないのでNaN!=NaNとなるのがIEEE754的な標準です。これは数学ではなく工学規格でありますが。
あと全然関係ないですが言語系やプロセッサとかエミュレータを書く人間にとってはNaNのビットパターンが複数取りうることが非常に面倒だったりします。C的に
union{int i;float f;} u1, u2; u1.i=0x7f800001; u2.i = 0x7f800002;
と宣言した後u1.i>u2.i, u1.f>u2.f, u1.fu2.f とかがどういう扱いになるかとか。無駄に奥が深い。
-- Takehiro TOMINAGA // may the source be with you!