これは何?
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]
難しい。