これは何?
https://qiita.com/scivola/items/8db8f88c9bb0ddcf41cd#comment-c26bb656a534b70ea86a
の続き。
等しいということ
たとえば float が普通の単精度、double が普通の倍精度である C言語(float が単精度で計算される環境の場合)で。
double d1 = 1.1;
float f1 = 1.1f;
puts( d1 == f1 ? "true" : "false" ); // A
double d2 = 99999999.0;
float f2 = 99999999.0f;
int32_t i2 = 99999999;
puts( d2==f2 ? "true" : "false" ); // B
puts( f2==i2 ? "true" : "false" ); // C
puts( i2==d2 ? "true" : "false" ); // D
とした場合。
A は、両辺 double
に変換される。
d1
は 1.10000000000000009 ぐらい。
f1
は 1.10000002 ぐらいで、 double
に変換すると 1.10000002384185791 ぐらいになるので等しくない。
B も、両辺 double
に変換される。
d2
は、99999999.0 になるが、
f2
は、単精度では 99999999 を表現できないので、 100000000.0 になる。
なので、等しくない。
C は、両辺 float
に変換されての比較になる。
i2
は 99999999 だが、比較に当たり float
に変換されて 100000000.0 になる。
なので、等しい。
D は、両辺 double
に変換されての比較になる。
i2
は 99999999 で、比較に当たり double
に変換される。 double
なので桁落ちせず、 99999999.0 になる。
なので、等しい。
見てのとおり、推移律が成立していない。
しかし。C や Java の場合、同じ ==
演算子で、何と何を比較するのかが動的に決まることはない(ないよね?)ので、推移律が成立しないような気分にはあまりならないと思う。
で。
ruby の場合はどうかというのがこの記事の主題。
ruby の場合
ruby にもいくつかの数値型がある
名前 | 内部 | 特徴 |
---|---|---|
Integer | 略 | 整数ならメモリの許す限り何でもOK |
Float | 浮動小数点数の2進数 | 2進数なので、 0.1 のような値は正確に表現できない |
Rational | Integer 2個 | 四則演算している限り誤差なし |
BigDecimal | 10進数(のような感じ) | 10進数なので、1÷3 の値は正確に表現できない |
Integer と Float の比較
想像より難しいことがわかった。
p 1.1==1 #=> false
p 1.0==1 #=> true
p 1==1.0.next_float #=> false
p 1==1.0.prev_float #=> false
このあたりを見ると、ああ Float にしてから比較してるのかなと思ったんだけど
s="7"*17
f = s.to_f
i = s.to_i
p [ f, i ] #=> [7.777777777777778e+16, 77777777777777777]
p [f==i, f.to_f==i.to_f, f-i==0, (f-i).class]
#=> [false, true, true, Float]
p f.to_f==i.to_f #=> true
f==i
と f.to_f == i.to_f
の結果が異なるのでそうでもない。
何が起こっているのかわからない。
Float と Rational の比較
p 1.1==1r #=> false
p 1.0==1r #=> true
p 1.0==1.000000000000001r #=> false
p 1.0==1.0000000000000001r #=> true
s="7"*17
f = s.to_f
r = s.to_r
p [ f, r ] #=> [7.777777777777778e+16, (77777777777777777/1)]
p [f==r, f.to_f==r.to_f, f-r==0, (f-r).class]
#=> [true, true, true, Float]
p f.to_f==r.to_f #=> true
Float にしてから比較しているとしか言いようがない結果。
Float より Rational のほうが表現できる値が広いので、わりと予期せぬ結果になりがちだと思う。
Float と BigDecimal の比較
こちらは BigDecimal に変換してから比較している感じ。
p 1.1==BigDecimal("1") #=> false
p 1.0==BigDecimal("1") #=> true
p 1.0==BigDecimal("1.000000000000001") #=> false
p 1.0==BigDecimal("1."+"0"*200+"1") #=> false
p 1.0.next_float==BigDecimal("1")#=> true
p 1.0.prev_float==BigDecimal("1")#=> false
p 1.0.next_float==BigDecimal("1")#=> true
が怪しげだが、これは coerce
の実装がそうなっているから。
p BigDecimal("1").coerce( 1.0.next_float ) #=> [0.1e1, 0.1e1]
p BigDecimal("1").coerce( 1.0.prev_float ) #=> [0.9999999999999999e0, 0.1e1]
そうなってしまうのはなぜかというと、たぶん、 bigdecimal.h
で
#define BIGDECIMAL_DOUBLE_FIGURES (1+DBL_DIG)
としているのが原因で
#define BIGDECIMAL_DOUBLE_FIGURES (2+DBL_DIG)
とすれば治るかも、と思っているけど、思っているだけで確かめてはいない。
Rational と BigDecimal の比較
こちらも BigDecimal に coerce しているけど、Float とはちょっと振る舞いがちがう。
p 1.1r==BigDecimal("1") #=> false
p 1.0r==BigDecimal("1") #=> true
p 1.0r==BigDecimal("1.000000000000001") #=> false
p 1.0r==BigDecimal("1."+"0"*200+"1") #=> false
p 1.0.next_float.to_r==BigDecimal("1")#=> true
p 1.0.prev_float.to_r==BigDecimal("1")#=> true
next_float
も prev_float
も比較結果が true
だが、これもそういう coerce
だから。
p BigDecimal("1").coerce( 1.0.next_float.to_r ) #=> [0.1e1, 0.1e1]
p BigDecimal("1").coerce( 1.0.prev_float.to_r ) #=> [0.1e1, 0.1e1]
ちなみに。 coerce
で有理数を 10進数にまるめる桁数は、レシーバの桁数で決まるので
s="1.00000000001000000000010000000000100000000001000000000010000000000100000000001"
BigDecimal(s[0,10])==s.to_r # => true
BigDecimal(s[0,20])==s.to_r # => false
BigDecimal(s[0,50])==s.to_r # => true
BigDecimal(s[0,60])==s.to_r # => false
と、奇妙なことが起こる。
様々な型で等値比較
BigDecimal(a)==Float(a)
みたいなことを、 a
を色々変えて試してみた。
全部 TRUE になっていないということは、推移律が成立していないということで、わりと気持ち悪いことではある。
"77777777777777777"
Integer | Float | Rational | BigDecimal | |
---|---|---|---|---|
Integer | TRUE | false | TRUE | TRUE |
Float | false | TRUE | TRUE | false |
Rational | TRUE | TRUE | TRUE | TRUE |
BigDecimal | TRUE | false | TRUE | TRUE |
前述のとおり、 Integer
と Float
の部分が謎。なんだろう。
"100..001"
(400桁)
Integer | Float | Rational | BigDecimal | |
---|---|---|---|---|
Integer | TRUE | false | TRUE | TRUE |
Float | false | TRUE | TRUE | false |
Rational | TRUE | TRUE | TRUE | TRUE |
BigDecimal | TRUE | false | TRUE | TRUE |
Float
は無限大になる。
にもかかわらず、 Float(略)==Rational(略)
が true
になるのは、右辺も Float
に変換された結果無限大になってしまうから。
"1.0000000000000002"
この値は 1.0.next_float.to_s
。
Float | Rational | BigDecimal | |
---|---|---|---|
Float | TRUE | TRUE | false |
Rational | TRUE | TRUE | TRUE |
BigDecimal | false | TRUE | TRUE |
次の "0.9999999999999999"
と結果が違うところがおそろしい。
"0.9999999999999999"
この値は 1.0.prev_float.to_s
。
Float | Rational | BigDecimal | |
---|---|---|---|
Float | TRUE | false | TRUE |
Rational | false | TRUE | TRUE |
BigDecimal | TRUE | TRUE | TRUE |
ちなみに、
0.9999999999999999==0.9999999999999999.to_r
#=> true
0.9999999999999999==0.9999999999999999r
#=> false
である。
"1e-400"
Float | Rational | BigDecimal | |
---|---|---|---|
Float | TRUE | TRUE | false |
Rational | TRUE | TRUE | TRUE |
BigDecimal | false | TRUE | TRUE |
Float("1e-400")
は 0.0
なので、わかりやすく書くと下記のような感じ。
r=Rational("1e-400");[r==0.0, r==0]
#=> [true, false]
難しい。