Subject: Re: Scalar class?

正木です。

|[ruby-math:00733] Re: Scalar class?
|From: matz / ruby-lang.org (Yukihiro Matsumoto)

|整合性が取れないから「Floatから/や*という演算を取り除く」と
|いう話にはならないわけですよね。

勿論そういう極端なことを言っているわけではありません。
Float では数学的な整合性がとれないから、まともな method は提供
できないので、標準で用意するのは必要最小限にとどめて、それ以外は
user にまかせたら?と言っているだけです。

step が必要な場合でも
(0.0).step(12,0.3)
がちゃんと動かなくてもかまわないという level から
(0.0).step(0.9999999999999999, 1.0/103)
が正しく動作しなくては困るという level まで目的に応じて要求水準
が違うでしょうから、user がそれに応じて自分で作ればいいのでは
ないですか?

||(まさかこれが期待どうりの結果だという人はいないと思いますが)

|期待通りだとは思いませんが、「Floatには誤差は付きもの」とい
|う当たり前の事実があるだけのように思えます。なんか間違ってま
|す?

どうも話が噛み合っていないようなので、引用された文章の意味をもう
少し詳しく書きます:
(0.0).step(0.9999999999999999, 1.0/103)
が(正確には)0.9999999999999999 まで出力すると言うのは、user の期待する
通りの結果ではないというのが私の考えです。
それに対して、現在の Float の仕様の枠内だけで考えればある意味で当然な、
次のような主張をする人がいるかも知れません。
step という method が実際に受け取る引数は(今の仕様では)それぞれ
(正確を期すために二進法で書けば)
max=0.11111111111111111111111111111111111111111111111111111
step=0.00000010011111000100010110010111100111001001010100100000010
であり、Float の計算では
step*103==max
となるので、与えられた引数に対して max まで出力するのは、期待通りの
当然の結果である。逆にそうでなければ step の所に上記の数値を与えた場合
に期待通りの結果にならない。
以上のような考え方をする人がいるかもしれないと思って確かめてみただけです。
(ただし、本当は正確に計算すると 
step*103=0.1111111111111111111111111111111111111111111111111111100111
で
step*103 > max
なのでこの例は上記の主張には一寸不利かも知れません)

まつもとさんのはそういう主張では無さそうなので、ここではそれに
対する反論は書きません。

話の本筋は、標準で用意するかは別として Float で step を実装する時の
問題点についてだったので少し補足します。便宜上

class Float
  def step0(max,step)
    idx = self
    while idx <= max
      yield idx
      idx += step
    end
  end

  EPSILON=2**(-52)
  def step1(max,step)
    n = (max-self)/step
    n = (n + n*EPSILON).floor + 1
    n.times{|i| yield i*step+self}
  end

  def step2(max,step)
    i = 0
    x = self
    while x <= max
      yield x
      i += 1
      x = self+i*step
    end
  end
end

とします。

最初に田中さん指摘の
(0.0).step0(4.0, 0.1){|x| p x}
の結果が
...
3.7
3.8
3.9

となる問題ですが、 code を見ても method の image 通りのことを
忠実にやっているだけのようにみえます。
これに対しても、まつもとさんの意見は

|期待通りだとは思いませんが、「Floatには誤差は付きもの」とい
|う当たり前の事実があるだけのように思えます。

ですか?

一方 step1,step2 では
...
3.7
3.8
3.9
4.0

と期待通りの結果になります。

何が違うのか調べてみるために
(0.0).step0(4.01, 0.1){|x| x.printd(56,2);print "\n"}
としてみると(printd は私が使っている小数表示の method です)
0
0.00011001100110011001100110011001100110011001100110011010
...
11.11100110011001100110011001100110011001100110011100000000
100.00000000000000000000000000000000000000000000000010000000

step0 で期待通りの結果が得られなかった原因は、足し算の
繰り返しでは誤差が累積するからというよりも、誤差がプラスに
なったことにあることが分かります。

ついでに step0 ではうまくいかない例をもう一つあげておきます:
(0.0).step0(20*PI, PI)
は 20*PI を出力しません。

step2 でも
(0.0).step2(1.0,1.0/49){|x| x.printd(53,2);print "\n"}
...
0.11111010110001101000011111010110001101000011111010110
0.11111111111111111111111111111111111111111111111111111

のように誤差は出るのですがマイナス側にでるので、表面には出ない
わけです。ですから step2 でも誤差がプラス側に出る場合があれば
失敗します。実際にそういう case があるかどうかは今の所分かりません。

step1 だとうまく行かない例は
(0.0).step1(0.9999999999999997, 1.0/103){|x| p x}
で、1.0 まで出力します。

要するに言いたかったことは:
「Floatには誤差は付きもの」だから完全なものはできないけれども
実装の仕方で誤差の出方は異なるということです。
できるだけ誤差が小さくなるように実装すべきですが、きりがないことなので
どこかで妥協する必要があります。
本当に step を標準で提供するつもりなら、どの程度で良しとするかという
ことについて充分検討しなければいけないと思うのですが?

実は上記の問題は step に rational を使えるようにすれば簡単に解決
します。
(0.0).step2(0.9999999999999999, 1/103){|x| p x}
は完璧に動作します。(勿論 1/103=Rational(1,103))
Rational が標準装備になったら、Float#step の問題点は、step が
rational でない場合の問題は残るものの、かなり少なくなります。

(最後にこういうことを書くのも一寸変な話ですが)
この問題については、int/int -> int の場合と違って、積極的な
反対ではありません。実装する人は大変だろうなと思っただけです。
色々手を加えた結果 Real Class がある程度実用に耐えるように
なってきたので、実は Float を殆ど使っていません。
ですから Float の user でもない私の意見は無視していただいて
結構です。