iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 4
0
Software Development

每天 Racket 3 分鐘系列 第 4

(define day-0.30000000000000004 (+ 0.1 0.2)) ;; Racket 的數值系統

1. Scheme 規格書說了什麼

Racket 的數值系統,核心架構仍然是以 Scheme 的為基礎。在 Scheme 的規格書 [1] 裡頭,列舉了以下的數值類型,其範圍由上到下限縮:

  1. number (數值)
  2. complex (複數)
  3. real (實數)
  4. rational (有理數)
  5. integer (整數)

最上方的 number 是最廣的,以集合論來說,就是下面四項的宇集。因此,你在任何地方,問 Racket 一個數字是否是 number 時,一定會得到 true,不管它是什麼樣的數字。

接下來,可以說是這門從學術界發展出來的語言的特色:可以直接進行複數操作,不必透過特殊的函式庫。

複數以降,就是實數,數學上來看,這範圍包含有理數與無理數,或正數與負數。無理數就是無法算至小數點下某一位開始循環的數字,例如圓周率、黃金分割、根號 2。有理數就是會在某一位小數開始循環,並能轉成分數的數字。因此,實數可以是無理數、複數、有理數、正數。

實數之下便是有理數,在 Racket 裡的有理數,真的就是分數,不信的話,你可以執行看看:

(/ 1 3)

最後,是整數,整數包含了正整數與負整數

2. Racket 的精準度

我們來試著算算看圓面積,用 高解析度圓周率,取到小數點以下 25 位數(到第三行),然後用半徑為 5 來算圓周看看:

(define pi 3.1415926535897932384626433)
(* 5 5 pi)

最後運算產生結果時,理論上來說,至少應該要到小數點以下 25 位,但你會看到小數位數在某個地方就停了:

78.53981633974483

這是因為,在電腦裡面,浮點數小數位數會因為實作機制可容許的數值空間有限,使得小數位數無法想到幾位就到幾位。不管是在 Racket、Scheme、Lisp、Python、Java 都會如此。

然而,在 Racket/Scheme 裡頭,有一種特殊的數值型態,稱為 exact,這不是說在上述的數值類別之外還有一個類別,它是一種精確度表示法,相反地,也有個 inexactexact 可以用以下方式來呈現:

;; 用 #e 來表示 exact:
(define pi #e3.1415926535897932384626433)
(* 5 5 pi)

改寫了這程式,重新運行後,你會發現結果變成如下:

   215926535897932384626433
78 ------------------------
   400000000000000000000000

Racket 會以有理數來呈現計算結果,若直接印出 pi 的話,會是這樣的值:

> pi
   1415926535897932384626433
3 -------------------------- 
  10000000000000000000000000

換句話說,它會把你給它不論多少位數的小數,完整地用有理數保存下來,好使你在計算時,可以精準到你要的程度。那另一個角度來說,inexact 就不是如此,它是 Racket 裡頭浮點數的預設操作與表示方式(雖然也可以用 #i 的方式來指定)。inexact 的操作就像原本的圓面積計算一樣,不會精準到你給的小數位數。

3. 0.1 + 0.2 檢查你是不是機器人

test robot

這張圖它在表示只有機器人能答對這個問題:0.1 + 0.2,你可以在 Racket 裡試試:

(+ 0.1 0.2)

為何若用 inexact 無法進行精準的小數點處理呢?甚至 0.1 + 0.2 這個值沒辦法得到一個正確的結果呢?這是計算機概論書本一定會談到的電腦浮點運算所使用的 IEEE 754 系統 [2],簡單來說,因為電腦是用二進位資料進行數值資料儲存,在從十進位轉換到二進位時,會產生無法完整轉換的問題。因此在進行 0.1 + 0.2 時,會發生從二進位轉十進位的精確度誤差。

這時候,你可以想見,或許用上方的 exact 型態來處理就可以了:

(+ #e0.1 #e0.2)

是的,雖然這可以算出正確值,還是必須說明:這是用計算效能得來的結果。inexact 的計算只需要透過底層原有的計算機制來進行,而exact 的計算卻需要對數值進行一層包裝,好使得數值不會受到小數位數與整數位數的影響。這當然就需要相當的效能作為代價。 然而,我們在這不完美的世界解決各項問題,本來就需要針對不同的處境權衡解決之道。

4. 豐富的數值系統

除了以上談到的常用數值型態,Racket 像 Python、Ruby 之類的語言,支援了無限長度的整數位數,例如你可以把 pi 的小數點拿掉:

(define pi 31415926535897932384626433)
(* 5 5 pi)

這樣仍然可以得到一個正確的結果,這是因為一旦整數位數超過了平台提供的長度,它會自動轉為 bignum 型態,這點倒是不用像 exactinexact 那樣用外在的字面方式來區別。

回到前頭,Racket 也支援複數運算,複數的表示用 i,如下:

(* 1+1i 2+2i)

上一篇
(define day-02 "Racket 不會咬人 — define and REPL")
下一篇
(define day-04 ""Hello world!" 怎麼這時候才出現!— Racket 的字串型態")
系列文
每天 Racket 3 分鐘17

尚未有邦友留言

立即登入留言