次のような計算をするメソッドを作って動かす。
- a. 2 つの実数を与え、和を返す (差、商、積も)。気づいたことがあれば述べよ。
- b. 「%」という演算子は剰余 (remainder) を求める演算である。上と同様にやってみよ。
- c. 数値 x を与え、逆数 $1 \over x$ を出力する (分子は「1.0」とした方がいいかもしれない)。
- d. 数値 x を与え、その 8 乗を返す。ついでに 6 乗、7 乗もやるとなおよい。 なお、Ruby のべき乗演算「**」は使わず、なおかつ乗除算が少ないことが望ましい。
- e. 円錐の底面の半径と高さを与え、体積を返す。
- f. 実数 x を与え、x の平方根を出力する。さまざまな値で計算し、精度を検討せよ。11
- g. その他、自分が面白いと思う計算を行うメソッドを作って動かせ。
その他、コンピュータの実数計算が数学と違っている具体例を示すようなプログラムを好きなように探究してみてください。
コードは以下のようになります。(演習 6 で作成した Sym class を事前に定義する必要があります。)
# a. 2 つの実数を与え、和を返す (差、商、積も)。
def add(x, y) x + y end
def sub(x, y) x - y end
def mul(x, y) x * y end
def div(x, y) x / y end
# b. 剰余 (remainder) を求める演算
def dec(x, y) x % y end
# c. 数値 x を与え、逆数 $1 \over x$ を出力する
def inv(x) 1.0 / x end
# d. 数値 x を与え、その 8 乗を返す
def oct(x) x ** 2 ** 2 ** 2 end
# e. 円錐の底面の半径と高さを与え、体積を返す。
def cone(r, h) r ** 2 * Math::PI * h / 3.0 end
# f. 実数 x を与え、x の平方根
def sqrt(x) x ** 0.5 end
# g その他、自分が面白いと思う計算を行うメソッドを作って動かせ。
x, y = (0..1).map do Sym.new end
# a ~ f 課題の記号計算
a0 = add(x, y)
a1 = sub(x, y)
a2 = mul(x, y)
a3 = div(x, y)
b = dec(x, y)
d = oct(x)
e = cone(x, y)
f = sqrt(x)
puts " i | j | i+j | i-j | i*j | i/j | i%j | i^8 | core | sqrt "
puts ":- |:- | :-- | :-- | :-- | :-- | :-- | :-- | :--- | :---"
(1..5).each do |i|
(1..5).each do |j|
x <=> i
y <=> j
puts "#{i} | #{j} | #{a0} | #{a1} | #{a2} | #{a3} | #{b} | #{d} | #{e} | #{f}"
end
end
Sym は代数計算をするために各演算を override している class です。
数学のように変数から式をつくり、後から数字を代入できるようになります。
仕組みとしてはまず、Sym class の演算子を override をすることで、
演算毎におこりうる処理を lambda 関数として継承させつつ、
@_ で新しく Sym object を生成して return させます。
つぎに Sym object に x <=> 1.0 などで数値を代入したあと
to_f が呼ばれたときに、再帰的に全ての処理を実行できます。
class Sym
def initialize(init = 0.0)
@v = init
@_ = ->fun{Sym.new(fun)}
end
def to_f() (@v.is_a? Numeric) ? @v: @v[] end
def to_i() self.to_f.to_i end
def to_s() self.to_f.to_s end
def +(_) @_[->{self.to_f + _.to_f}] end
def -(_) @_[->{self.to_f - _.to_f}] end
def *(_) @_[->{self.to_f * _.to_f}] end
def /(_) @_[->{self.to_f / _.to_f}] end
def %(_) @_[->{self.to_f % _.to_f}] end
def **(_) @_[->{self.to_f ** _.to_f}] end
def ==(_) self.to_f == _.to_f end
def <=>(_) @v = _.to_f end
end
x, y = (0..1).map do Sym.new end
z = (x ** 2 + y ** 2)
x <=> 3
y <=> 4
p "expect #{z} to be 25.0"
x <=> 7
y <=> 24
p "expect #{z} to be 625.0"
実行結果は以下のようになります。
(演習 3 - c の逆数についてですが、演算の左側のオペランドが Sym objectではなく 1.0 なので
同様にテストすることが出来ませんでしたが、_1 = Sym(1.0) を用いると同様に実行できます。)
| i | j | i+j | i-j | i*j | i/j | i%j | i^8 | core | sqrt |
|---|---|---|---|---|---|---|---|---|---|
| 1 | 1 | 2.0 | 0.0 | 1.0 | 1.0 | 0.0 | 1.0 | 1.0471975511965976 | 1.0 |
| 1 | 2 | 3.0 | -1.0 | 2.0 | 0.5 | 1.0 | 1.0 | 2.0943951023931953 | 1.0 |
| 1 | 3 | 4.0 | -2.0 | 3.0 | 0.3333333333333333 | 1.0 | 1.0 | 3.141592653589793 | 1.0 |
| 1 | 4 | 5.0 | -3.0 | 4.0 | 0.25 | 1.0 | 1.0 | 4.1887902047863905 | 1.0 |
| 1 | 5 | 6.0 | -4.0 | 5.0 | 0.2 | 1.0 | 1.0 | 5.235987755982989 | 1.0 |
| 2 | 1 | 3.0 | 1.0 | 2.0 | 2.0 | 0.0 | 65536.0 | 4.1887902047863905 | 1.4142135623730951 |
| 2 | 2 | 4.0 | 0.0 | 4.0 | 1.0 | 0.0 | 65536.0 | 8.377580409572781 | 1.4142135623730951 |
| 2 | 3 | 5.0 | -1.0 | 6.0 | 0.6666666666666666 | 2.0 | 65536.0 | 12.566370614359172 | 1.4142135623730951 |
| 2 | 4 | 6.0 | -2.0 | 8.0 | 0.5 | 2.0 | 65536.0 | 16.755160819145562 | 1.4142135623730951 |
| 2 | 5 | 7.0 | -3.0 | 10.0 | 0.4 | 2.0 | 65536.0 | 20.943951023931955 | 1.4142135623730951 |
| 3 | 1 | 4.0 | 2.0 | 3.0 | 3.0 | 0.0 | 43046721.0 | 9.42477796076938 | 1.7320508075688772 |
| 3 | 2 | 5.0 | 1.0 | 6.0 | 1.5 | 1.0 | 43046721.0 | 18.84955592153876 | 1.7320508075688772 |
| 3 | 3 | 6.0 | 0.0 | 9.0 | 1.0 | 0.0 | 43046721.0 | 28.274333882308138 | 1.7320508075688772 |
| 3 | 4 | 7.0 | -1.0 | 12.0 | 0.75 | 3.0 | 43046721.0 | 37.69911184307752 | 1.7320508075688772 |
| 3 | 5 | 8.0 | -2.0 | 15.0 | 0.6 | 3.0 | 43046721.0 | 47.1238898038469 | 1.7320508075688772 |
| 4 | 1 | 5.0 | 3.0 | 4.0 | 4.0 | 0.0 | 4294967296.0 | 16.755160819145562 | 2.0 |
| 4 | 2 | 6.0 | 2.0 | 8.0 | 2.0 | 0.0 | 4294967296.0 | 33.510321638291124 | 2.0 |
| 4 | 3 | 7.0 | 1.0 | 12.0 | 1.3333333333333333 | 1.0 | 4294967296.0 | 50.26548245743669 | 2.0 |
| 4 | 4 | 8.0 | 0.0 | 16.0 | 1.0 | 0.0 | 4294967296.0 | 67.02064327658225 | 2.0 |
| 4 | 5 | 9.0 | -1.0 | 20.0 | 0.8 | 4.0 | 4294967296.0 | 83.77580409572782 | 2.0 |
| 5 | 1 | 6.0 | 4.0 | 5.0 | 5.0 | 0.0 | 152587890625.0 | 26.179938779914945 | 2.23606797749979 |
| 5 | 2 | 7.0 | 3.0 | 10.0 | 2.5 | 1.0 | 152587890625.0 | 52.35987755982989 | 2.23606797749979 |
| 5 | 3 | 8.0 | 2.0 | 15.0 | 1.6666666666666667 | 2.0 | 152587890625.0 | 78.53981633974483 | 2.23606797749979 |
| 5 | 4 | 9.0 | 1.0 | 20.0 | 1.25 | 1.0 | 152587890625.0 | 104.71975511965978 | 2.23606797749979 |
| 5 | 5 | 10.0 | 0.0 | 25.0 | 1.0 | 0.0 | 152587890625.0 | 130.89969389957471 | 2.23606797749979 |
a + b - c * d / e のような演算を図にすると以下のようになります。
ab = a + b に対して、 a, b を parent、ab を childとしたとき、
parentでする処理を各演算子 override で child に継承させることで、
最後の childが to_f を実行すると、全ての親の to_fが再帰的に実行させます。
ab = a + b # add(a, b)
cd = c * d # mul(cd)
cde = cd / e # div(cd, e)
= c * d / e # div(mul(c, d), e) ∵ cd
abcde = ab - cde # sub(ab, cde)
= a + b - c * d / e # sub(add(a, b), div(mul(c, d), e)) ∵ ab, cde
