Ruby 冷知識:除法篇
相信大家小時候都學過四則運算。以 Ruby 進行四則運算時,整數的加法、減法、乘法相當單純:
> 1 + 2
=> 3
> 9 - 2
=> 7
> 3 * 2
=> 6
為什麼要強調整數呢…因為換成浮點數的話就會混進奇怪的東西 XD
> 0.1 + 0.2
=> 0.30000000000000004
> 0.3 - 0.1
=> 0.19999999999999998
> 0.1 * 0.2
=> 0.020000000000000004
簡單來說,由於電腦記憶體有限,浮點數的 0.1 和一般人腦認知的小數點 0.1有著極微妙的誤差。這是有名的「浮點數不精確問題」,不論任何程式語言都會遇到。不過今天的主題是除法,這部分就跳過吧。
那除法呢?
和加減乘法比起來,除法的花樣就多了。首先,從數學上來說,除法運算必然會產生商數和餘數:
被除數 = 商數 * 除數 + 餘數
# 14 = 4 * 3 + 2
因此,對人腦來說是簡單的 x ÷ y
,對電腦來說就是黑人問號了:「所以你現在是想知道除數還是餘數還是什麼?」
我兩個都想知道,不行嗎?
可以喔!使用 x.divmod(y)
方法,可以同時取得商數和餘數:
> 14.divmod(3)
=> [4, 2]
我想知道商數
總之先幫我隨便算一下吧
好喔,那就是最經典的 x / y
方法:
# 整數相除,得到整數的商數
> 14 / 3
=> 4
# 非整數相除,得到非整數的商數
> 14 / 3.0
=> 4.666666666666667
就是要整數
使用 x.div(y)
方法,結果會轉為整數:
> 14.div(3)
=> 4
> 14.div(3.0)
=> 4
浮點數錯了嗎?
使用 x.fdiv(y)
方法,結果會轉為浮點數:
> 14.fdiv(3)
=> 4.666666666666667
> 14.fdiv(3.0)
=> 4.666666666666667
整數什麼的最討厭了
使用 x.quo(y)
方法,會得到更精確的商數:
# 整數相除,得到分數物件(Rational)
> 14.quo(3)
=> (14/3)
# 非整數相除,得到浮點數物件(Float)
> 14.quo(3.0)
=> 4.666666666666667
我想知道餘數
我們先從最單純的情況開始。
x, y 的正負值相同時
x.modulo(y)
和 x.remainder(y)
可以取得餘數:
> 14.modulo(3)
=> 2
> 14.remainder(3)
=> 2
兩邊同樣都是負數時也是一樣:
> -14.modulo(-3)
=> -2
> -14.remainder(-3)
=> -2
其中 x.modulo(y)
也可以寫成(比較常見的?) x % y
,兩者意義是一樣的。
x, y 的正負值不同時
好,問題來了。當 x
和 y
的正負值不同時,也就是像是 14 ÷ -3
或 -14 ÷ 3
這樣的計算時,會發生什麼事情呢?
> 14.divmod(-3)
=> [-5, -1]
> 14.modulo(-3)
=> -1
> 14.remainder(-3)
=> 2
咦?為何同樣是取得餘數的 x.modulo(y)
和 x.remainder(y)
,得到的結果卻是不一樣的呢?還記得剛才的除法復習嗎:
被除數 = 商數 * 除數 + 餘數
當被除數和除數的正負值不同時,例如 14 ÷ -3
,按照數學上對餘數的定義(詳細請參考維基百科),以下兩個解法都是成立的!
14 = (-5) * (-3) + (-1)
14 = (-4) * (-3) + 2
這也就是說,在數學上,14 ÷ -3
的餘數同時有可能是 -1
和 2
。因此,Ruby 同時提供了 x.modulo(y)
和 x.remainder(y)
兩種方法,以分別取得兩個不同解法所得到的餘數。
簡單地說,能使 x = a * y + b
成立的最小商數解a
,與其搭配的餘數 b
就是 x.modulo(y)
會取得的值:
# 14 = (-5) * (-3) + (-1)
> 14.modulo(-3)
=> -1
而另一個解法所得到的餘數,就是 x.remainder(y)
的結果。它和被除數的正負值會是一致的:
# 14 = (-4) * (-3) + 2
> 14.remainder(-3)
=> 2
前述 x.divmod(y)
所取得的餘數,則是以 x.modulo(y)
的值為準。
特殊狀況
再補充一些和零有關的特殊狀況:
# 除數是零的話會噴錯
> 1 / 0
=> ZeroDivisionError
# 但可以是 0.0 XD
> 1 / 0.0
=> Infinity
# 被除數是零的話,怎麼除都會是零
> 0 / 3
=> 0
# 除非用 0.0 去除 XD
> 0 / 0.0
=> NaN
結語
以上是關於 Ruby 除法的介紹,雖然實用性應該很低(笑)想更深入理解 Ruby 的數學運算的話,可以參考官方文件的 Numeric API,還有很多不常用的冷門方法等大家去認識喔~