2009-01-14練習:たのしいRuby P.172 - 173
練習問題 (1)
華氏を摂氏に変換するメソッド fahr2celsius を定義しましょう。 摂氏と華氏の変換の公式:摂氏 = 5 × (華氏 - 32) ÷ 9
書いたもの
def fahr2celsius(fahr) 5 * (fahr - 32).quo(9) end p fahr2celsius(-40) p fahr2celsius(-17.8) p fahr2celsius(0) p fahr2celsius(98.6)
実行結果
-40.0 -27.6666666666667 -17.7777777777778 0.0 37.0
最初は、こんなふうに書いた。
def fahr2celsius(fahr) 5 * (fahr - 32) / 9 end
fahr2celsius(0) が -18 になった。この結果は正しくない。そこで、Rubyリファレンスマニュアル - trap::Numeric を参考に、Numeric#quo を使うことにした。
Rubyリファレンスマニュアル - Numeric#truncate にあるように任意桁の四捨五入を行うメソッドを定義し、小数点以下第 2 位を四捨五入して、小数点第 1 位まで表示するようにしようかとも思ったけど、変換した値を使った計算をしたら、誤差が出てしまうかもしれないから、丸めるのはやめた。
練習問題 (2)
摂氏を表すクラス Celsius を定義しましょう。 このクラスは、以下のメソッドを持つとします。 ■Celsius.new(c) 摂氏の温度 c の値を持つ Celsius クラスのインスタンスを作る。 ■Celsius#to_celsius 摂氏の温度を数値として返す。 ■Celsius#to_fahr 華氏の温度を数値として返す。 ■Celsius#+(cl) 摂氏オブジェクト cl と足し算をして、新しい摂氏オブジェクトを返す。
書いたもの
$KCODE = "SJIS" class Celsius # 摂氏の温度 c の値を持つ Celsius クラスのインスタンスを作る。 def initialize(c) @c = c end # 摂氏の温度を数値として返す。 def to_celsius @c end # 華氏の温度を数値として返す。 def to_fahr (@c * 9).quo(5) + 32 end # cl が摂氏オブジェクトか数値オブジェクトだったら、足し算をして、 # 新しい摂氏オブジェクトを返す。そうでなかったら、エラーを返す。 def +(cl) case cl when Celsius Celsius.new(@c + cl.to_celsius) when Numeric Celsius.new(@c + cl) else $stderr.puts "in `+': Celsius can't be coerced into #{cl.class} (TypeError)" end end end cel1 = Celsius.new(37.0) cel2 = Celsius.new(-2) cel3 = Celsius.new(-17.7777777777778) p "cel1: 摂氏 #{cel1.to_celsius} 度、華氏 #{cel1.to_fahr} 度" p "cel2: 摂氏 #{cel2.to_celsius} 度、華氏 #{cel2.to_fahr} 度" p "cel3: 摂氏 #{cel3.to_celsius} 度、華氏 #{cel3.to_fahr} 度" # 摂氏オブジェクトと摂氏オブジェクトの足し算 cel4 = cel1 + cel2 p cel4.is_a?(Celsius) p "摂氏 #{cel1.to_celsius} 度 + 摂氏 #{cel2.to_celsius} 度 = 摂氏 #{cel4.to_celsius} 度" # 摂氏オブジェクトと数値オブジェクトの足し算 cel5 = cel1 + 3.5 p cel5.is_a?(Celsius) p "摂氏 #{cel1.to_celsius} 度 + 摂氏 3.5 度 = 摂氏 #{cel5.to_celsius} 度" # 摂氏オブジェクトと文字列オブジェクトの足し算 cel6 = cel1 + "絶対零度"
実行結果
"cel1: 摂氏 37.0 度、華氏 98.6 度" "cel2: 摂氏 -2 度、華氏 28.4 度" "cel3: 摂氏 -17.7777777777778 度、華氏 -4.2632564145606e-014 度" true "摂氏 37.0 度 + 摂氏 -2 度 = 摂氏 35.0 度" true "摂氏 37.0 度 + 摂氏 3.5 度 = 摂氏 40.5 度" in `+': Celsius can't be coerced into String (TypeError)
ありゃ。cel3 が思っていた結果になっていない。練習問題 (1) の結果から、"摂氏 -17.7777777777778 度、華氏 0 度" になると思ったのに。
原因は、自分で丸めなくても、コマンドラインの制限?で計算結果が丸められていたからみたい。小数点以下を 1 桁増やして、cel3 = Celsius.new(-17.77777777777778) にしたら、"cel3: 摂氏 -17.7777777777778 度、華氏 0.0 度" になった。
別の方法で何とかならないかなと思い、ためしに、四捨五入して表示するようにしてみた。そうしたら、小数点以下第 3 位までなら「華氏 0.0 度」になったけど、小数点以下第 4 位までにすると「華氏 -0.0004 度」になった。
私にとっては温度は小数点以下第 2 位まであれば十分で、それなら、メソッドの定義はこのままで、小数点以下第 3 位を四捨五入して、小数点以下第 2 位までの結果を得るようにすればよさそう。
より誤差が少なくて、使い勝手もよいものにするには、どうすべきなんだろう。
摂氏 <=> 華氏 が成り立つといいなと思ったけど、Celsius#to_celsius も Celsius#to_fahr も数値を返すから、メソッドチェーンはできないし、摂氏から華氏に変換したものをまた摂氏に変換しなおすことってあまりなさそうだし、いつまでも悩むのも何だし、次の問題にいこうかな。そういえば、Celsius#+(cl) では、何となく、数値オブジェクトも足せたほうがいいかなと思って足せるようにしたけど、Celsius#to_celsius や Celsius#to_fahr では数値オブジェクトは扱えないから、いい仕様ではないのかも。・・・未消化だけど、先に進んでみよう。
練習問題 (3)
数値 num が素数であるかを調べるメソッド prime?(num) を定義しましょう。
書いたもの
def prime?(num) # 小数は素数ではない if num.is_a?(Float) return false end # 2 より小さい整数は素数ではない if num < 2 return false end # 1 とその数自身以外の約数を持つ 2 以上の整数は素数ではない(合成数) (2..Math::sqrt(num)).each {|i| if num % i == 0 return false end } # 2 以上の自然数で、かつ 合成数でないなら、素数 return true end p prime?(2.1) p prime?(1) p prime?(2) p prime?(997) p prime?(9971) p prime?(9973)
実行結果
false false true true false true
単に「数値」と言われたとき、自分が入れそうなもの = 小数と整数 について判定できるようにしてみた。