LoginSignup
2
1

More than 1 year has passed since last update.

ruby の数値の等値

Last updated at Posted at 2021-07-04

これは何?

https://qiita.com/scivola/items/8db8f88c9bb0ddcf41cd#comment-c26bb656a534b70ea86a
の続き。

等しいということ

たとえば float が普通の単精度、double が普通の倍精度である C言語(float が単精度で計算される環境の場合)で。

C
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 の比較

想像より難しいことがわかった。

ruby3.0
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 にしてから比較してるのかなと思ったんだけど

ruby3.0
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==if.to_f == i.to_f の結果が異なるのでそうでもない。
何が起こっているのかわからない。

Float と Rational の比較

ruby3.0
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 に変換してから比較している感じ。

rby3.0
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 の実装がそうなっているから。

ruby3.0
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 とはちょっと振る舞いがちがう。

ruby3.0
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_floatprev_float も比較結果が true だが、これもそういう coerce だから。

ruby3.0
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進数にまるめる桁数は、レシーバの桁数で決まるので

ruby3.0
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

前述のとおり、 IntegerFloat の部分が謎。なんだろう。

"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

ちなみに、

ruby3.0
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 なので、わかりやすく書くと下記のような感じ。

ruby3.0
r=Rational("1e-400");[r==0.0, r==0]
#=> [true, false]

難しい。

2
1
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1