以下、ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-darwin14] での調査結果。
Float::NAN について知っておくべきこと
ruby の Float::NAN は、浮動小数点の非数なので、以下の様な動作になる。
nan=Float::NAN
nan==nan #=> false
nan==0 #=> false
nan<0 #=> false
nan<=0 #=> false
0<nan #=> false
0<=nan #=> false
事情を知らないとかなり思いがけない内容だけど、浮動小数点の非数とはそのようなものなので仕方がない。
この、
- 同じ値を比較しても false になる
- 大小比較をすると必ず false になる
という性質は 浮動小数点の非数がもつ一般的な性質で、ruby に限ったことではない。
これが理由で、浮動小数点数は全順序になっておらず、いろいろひどいことが起こりうる。
import random
nan=float("nan")
print( sorted( [3, nan, 1, 4, 1] ) ) #=> [3, nan, 1, 1, 4]
python の例。整列されない。C++ の std::sort なんかも同様。
一方。
ruby の場合は
nan=Float::NAN
p [3, nan, 1, 4, 1].sort #=> comparison of Float with 1 failed (ArgumentError)
例外が発生して気づく。
素晴らしい。
ちなみに Java の場合は 非数が末尾になるように整列されることになっているみたい。
Float::NAN についての、知らなくても良さそうなこと
非数をコンテナに入れてみる。
[nan]==[nan] #=> true
{nan=>0}=={nan=>0} #=> true
{0=>nan}=={0=>nan} #=> true
コンテナに入っていると、nan==nan が false になることとは関係なく、なぜか等しいということになる。
で。もうちょっといじわるしてみる。
nan=Float::NAN
[nan]==[nan+1] #=> false
[nan+1]==[nan+1] #=> false
(nan+1).__id__ == nan.__id__ #=> false
非数の演算結果は別のオブジェクトとなり、それをコンテナに入れて比較すると異なるとみなされる。Marshal
などで複製を作る場合も同様。
念の為に書いておくと、非数に1を加えても非数である。
この不自然な動き、バグなのかもしれないけどよくわからない。
気持ちとしては、true
でも false
でもいので、
[nan]==[nan]
と [nan+1]==[nan]
が同じ値になってほしいと思う。
どっちかというと false
がいいような気がするけどどうだろう。
この奇妙な動きは、無限大では起こらない。
∞=Float::INFINITY
∞==∞ #=> true
[∞]==[∞] #=> true
[∞]==[∞+1] #=> true
[∞+1]==[∞+1] #=> true
(∞+1).__id__ == ∞.__id__ #=> false
無限大の場合は、複製や演算で別のオブジェクトにはなるものの、比較の結果には影響を与えない。
正しい動きのような感じ。
ちなみに。Float の動きは難しく、
[10**77, 10**77].map{|x| x.to_f.__id__ }.uniq.size #=> 1
[10**78, 10**78].map{|x| x.to_f.__id__ }.uniq.size #=> 2
のようになる。
たぶん、絶対値が小さいと Fixnum のような感じで振る舞い、絶対値が大きいと Bignum のように振る舞うんじゃないかと思う。整数と違って、その差を知る方法はあまりない。