パスワードを忘れた? アカウント作成
117894 journal

quabbinの日記: [Java] JavaにおけるNaN比較 2

日記 by quabbin

先日の続き。
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のようです。

しかし、それと数学とが一致しているかは、キット別の話なのかなぁと思いつつ、数学が弱いので判別つきません。
ん~。数学の勉強、どっかでしなければ。

この議論は賞味期限が切れたので、アーカイブ化されています。 新たにコメントを付けることはできません。
  • by Anonymous Coward on 2009年07月08日 15時03分 (#1601785)

    数学的に、というならば NaN はそもそも「値」ではなく、大小や等しいという概念もない状態なので比較はできないでしょう。(*1)

    計算機としては NaN == NaN の評価値は N/A としたいところですが、Java の比較演算子の評価は true/false の値しか持たず、これをそれぞれ true or not 的な扱いで適用しているので結果的に false となっているのだと思います。

    Javascript のような処理系でも比較演算子が NaN のような第三値となることはないみたいですね。

    SQL では null 比較を = ではなく is や is not で行うというところにも目を向けると面白いかもしれません。

    *1)
    これに対して Infinity, -Infinity は(やはり値ではありませんが)比較可能な状態です。

    • by tt (2867) on 2009年07月09日 0時59分 (#1602147) 日記
      ですよね。自分の日記にも書きましたが、NaNは数学というよりは、工学ですよね(たぶん大多数の普通の人にとっては…)

      本当は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!
      親コメント
typodupeerror

未知のハックに一心不乱に取り組んだ結果、私は自然の法則を変えてしまった -- あるハッカー

読み込み中...