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 的正負值不同時

好,問題來了。當 xy 的正負值不同時,也就是像是 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 的餘數同時有可能是 -12。因此,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,還有很多不常用的冷門方法等大家去認識喔~