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

quabbinの日記: [Ruby] NaNの動き 2

日記 by quabbin

Ruby も NaN == NaN が true になる可能性が…
Twitter / 星一: Ruby も NaN == NaN が true に ...

ふと気になって調べてみました。

1.4~1.9系Rubyでの動き
NaNはFloatの演算で算出することが出来ます。
つまり、

0.0/0

がNaNになるはずなので、

p 0.0/0 == 0.0/0

がtrueになるかfalseになるかを観察する方向でチェックできるはずです。

そこで手元にあったActiveScriptRubyの1.4~1.8でチェックしてみます。

C:\Users\quabbin>\Develop\Language\Ruby-1.4\ruby.exe -ve 'p 0.0/0 == 0.0/0'
ruby 1.4.6 (2000-08-16) [i386-mswin32]
true
 
C:\Users\quabbin>\Develop\Language\Ruby-1.6.8.3\bin\ruby.exe -ve 'p 0.0/0 == 0.0
/0'
ruby 1.6.8 (2002-12-24) [i586-mswin32]
true
 
C:\Users\quabbin>\Develop\Language\Ruby-1.8\bin\ruby.exe -ve 'p 0.0/0 == 0.0/0'
ruby 1.8.7 (2008-08-11 patchlevel 72) [i386-mswin32]
false

1.6以前は軒並みtrue、1.8からはfalseになりました。
さすがに1.2以前のコードはないからチェックできませんので、それ以前に関しては不明ですが。

# SVN上には1.1や1.0がありますが、コンパイルするのは大変そうです。

ここでcygwinに移って1.9を試してみます

quabbin@Litchi ~ $ ruby -ve 'p 0.0/0 == 0.0/0'
ruby 1.8.7 (2008-08-11 patchlevel 72) [i386-cygwin]
false
quabbin@Litchi ~ $ ruby1.9 -ve 'p 0.0/0 == 0.0/0'
ruby 1.9.1p129 (2009-05-12 revision 23412) [i386-cygwin]
false

1,8と1.9を試すこととなったわけですが、やはり普通にfalseとなりました。
ちなみに、ruby1.9は、1.9系rubyへのシンボリックリンクです。一応。

同一変数だとどうなる?
ここではたと、同一の変数で比較したならどうなるか気になりました。

quabbin@Litchi ~ $ ruby1.9 -e 'p nan = 0.0/0 && nan == nan'
true
</eocde>
なんと、trueです。
<ecode>
quabbin@Litchi ~ $ ruby1.9 -e 'p [(0.0/0).object_id, (0.0/0).object_id]'
[136653180, 136653170]

object_idを比べると、NaNは発生するごとにインスタンスが生成されていることが分かります。
インスタンスが別ならfalseで、インスタンスが同一ならtrueとなると、まるで==メソッドが定義されてないクラスのインスタンスみたいな振る舞いです。

quabbin@Litchi ~ $ ruby1.9 -ve 'p nan = 0.0/0 && nan == 0.0/0'
ruby 1.9.1p129 (2009-05-12 revision 23412) [i386-cygwin]
false
quabbin@Litchi ~ $ ruby1.9 --dump=insns -ve 'p nan = 0.0/0 && nan == 0.0/0'
ruby 1.9.1p129 (2009-05-12 revision 23412) [i386-cygwin]
== disasm: <RubyVM::InstructionSequence:<main>@-e>======================
local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1] s1)
[ 2] nan
0000 trace            1                                               (   1)
0002 putnil
0003 putobject        0.0
0005 putobject        0
0007 opt_div
0008 dup
0009 branchunless     22
0011 pop
0012 getdynamic       nan, 0
0015 putobject        0.0
0017 putobject        0
0019 opt_div
0020 opt_eq           <ic>
0022 dup
0023 setdynamic       nan, 0
0026 send             :p, 1, nil, 8, <ic>
0032 leave
quabbin@Litchi ~ $ ruby1.9 --dump=insns -ve 'p nan = 0.0/0 && nan == nan'
ruby 1.9.1p129 (2009-05-12 revision 23412) [i386-cygwin]
== disasm: <RubyVM::InstructionSequence:<main>@-e>======================
local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1] s1)
[ 2] nan
0000 trace            1                                               (   1)
0002 putnil
0003 putobject        0.0
0005 putobject        0
0007 opt_div
0008 dup
0009 branchunless     20
0011 pop
0012 getdynamic       nan, 0
0015 getdynamic       nan, 0
0018 opt_eq           <ic>
0020 dup
0021 setdynamic       nan, 0
0024 send             :p, 1, nil, 8, <ic>
0030 leave

上で使ったコードを代入の形にし、期待通り動くことを見てからコードのパース状態を出力させます。
一つ目のコードでは0.0/0が二回実行されているのに対し、二つ目のコードでは一回しか実行されてません。
つまり、見た目の相違以上の相違が見えません。

更にCレベルのソースを追ってみたのですが、いまひとつ原因が追いきれず、今日はここで時間切れ。
また後日とします。

# Cレベルのデバッグ方法について、あまり詳しくないのが敗因…。

ちなみにpythonでは…
Rubyとよく比較されるPythonでは、このあたりどうなっているのでしょう。
動きを見てみます。

quabbin@Litchi ~ $ python --version
Python 2.5.2
quabbin@Litchi ~ $ python -c 'n = float("NaN");print n == n'
False
quabbin@Litchi ~ $ python -c 'print id(float("NaN")), id(float("NaN"))'
17463392 17463392
quabbin@Litchi ~ $ python -c '0.0 / 0.0'
Traceback (most recent call last):
  File "<string>", line 1, in <module>
ZeroDivisionError: float division
quabbin@Litchi ~ $ python -c 'float("0.0") / float("0.0")'
Traceback (most recent call last):
  File "<string>", line 1, in <module>
ZeroDivisionError: float division

# あまりPythonに詳しくないので、これであっているかは自信がありません。
Pythonでは0除算すると0除算例外が発生してしまうようです。
また興味深いことに、NaNはSingletonなインスタンスが発生するようで、==で比較すると常にFlaseとなるようです。

NaNのまわりは、結構言語によって扱いが違うのですね。
ところでこの問題、数学的にはどう動くのが正しいのでしょう。
数学が詳しければ、いろいろと判断付くのでしょう。
…しっかり数学を勉強したい今日この頃です。

07/07 01:08追記
elderwandさんの指摘を参考に試してみたところ、Pythonでもインスタンスを複数生成できるが、比較してもFalseとなるようです。

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

    Python では、inf/inf で nan が得られるみたいですね。

    >>> 1e309
    inf
    >>> 1e309/1e309
    nan

    #その先は、私にはわかりません。

    • なるほど。
      ちょっとやってみました。

      quabbin@Litchi ~ $ python -c 'print float("inf") / float("inf")'
      nan
      quabbin@Litchi ~ $ python -c 'print float("inf") / float("inf") == float("inf") / float("inf")'
      False
      quabbin@Litchi ~ $ python -c 'print id(float("inf") / float("inf")), id(float("inf") / float("inf"))'
      16545952 16545920

      infでやるとオブジェクトは別々に出来ると。
      float()でnanを作っているから、同じものが返ってきたというだけなのですね。

      訂正します。
      ありがとうございました。

      親コメント
typodupeerror

クラックを法規制強化で止められると思ってる奴は頭がおかしい -- あるアレゲ人

読み込み中...