今天有一個小夥伴給我發來了一個截圖如下。
我下意識的說了一句:0.58*50不等于29。
感概,有時候,眼見也不一定為實。這就是一個浮點運算導緻的「算不準」問題。
在曆史上,其實已經有過類似因為浮點運算導緻的事故。
1994 年,英特爾的奔騰微處理器晶片的浮點計算單元出現了一個 Bug。在某些情況下,浮點除法并不能傳回正确的結果。例如 4195835.0/3145727.0 産生的是 1.33374 而不是 1.33382。雖然這個 Bug 可能對絕大多數人沒有影響,但是市面上 500 萬左右流通的晶片都存在該缺陷,随着整個事件的發酵,最終這個 Bug 給 intel 造成了 4.75 億美元的損失。這次事件也被稱為「Pentium FDIV bug」而載入了 Bug 史冊。
R 語言中的實際示例
我們分别在 R 中計算 0.58*50,0.29*100,然後讓他們和 29 互相比一比。
29)
結果如下所示:
29)
我們看到螢幕上顯示的 a , b和 c 都是29,但是如果用
==
進行一下比較,發現
0.58*50
和
0.29*100
竟然都不等于29。嗯,很迷。
浮點數的精度和計算問題,在任何程式設計語言裡都類似。
浮點數在計算機中不能以任意精讀存儲,都會有一個準确性的限制,通過有限的連續字元來儲存浮點數。
目前常見的浮點格式包括:單精度,雙精度和擴充雙精度。他們的準确性從前到後依次提高,其中單精度可以用于一般計算,雙精度用于科學計算。在 R 中就是采用了雙精度來對浮點數進行儲存。如下所示:
我們讓 d 等于 10,通過
class
可以看出它是一個數值型變量,如果用
typeof
檢視,就會發現他的存儲模式(storage mode)是一個雙精度浮點型數字。
這裡又涉及到了存儲模式和類型的問題。在R中
class
傳回的是一個對象的進階類,而
typeof
傳回的是對象的内部低級類。如果用
calss
檢視一個資料框,其傳回結果就是他的類(calss)為資料框,如果使用
typeof
檢視,傳回的結果就是内部類型為清單,如下所示。(嗯,想起了果子老師課上講的,data.frame 就是一種特殊的 list)
繼續回來說浮點數的比較。因為精度問題,在R語言中使用
==
進行浮點數的比較是非常危險的。
就像文章開頭的例子,此時假設你有一個 if 語句,想進行一下結果的判斷。如果判斷條件是 0.29*100 == 29,就會傳回 F。因為 0.29*100 的實際值在計算機中比 29 要小一些。當你使用
as.interger
去處理的時候,結果為28。
0.29*
這也就是為什麼
rep(1,29)
有29個數字,但是
rep(1,0.29*100)
有28個數字。
如何避免浮點數計算的坑
如何解決這個問題呢?在 R 語言中有一個函數叫做
all.equal
,雖然名字是 equal,但是他其實是 almost equal。
這讓我不禁想到了一款果子和洲更(當然還有我)都在用全面屏 Almost 手機。
all.equal()
的功能是 Test If Two Objects Are (Nearly) Equal。也就是它可以略微忍受兩個對象之間有一丢丢差别。如下所示:
29)
你可能會問,這個一丢丢究竟是多少呢?
通過幫助文檔,我們可以發現這個一丢丢實際是
tolerance = sqrt(.Machine$double.eps)
。如果實際運作一下會發現
.Machine$double.eps
等于2.220446e-16,那麼
sqrt(.Machine$double.eps)
就是 1.490116e-08。
這裡的
.Machine
是一個存儲了 R 正在運作機器的數值特性的變量,其中包括了最小正浮點數(double.eps)。是以,以後想進行類似的比較,記得使用
all.equal()
。另外,多說一句,在 if 判斷中不要直接使用它,而是寫成
isTRUE(all.equal(....))
。
回到最開始的問題,如果确實需要
rep(1,rep(1,0.29*100))
傳回 29 個數字,就可能需要用到取整。
在 R 中有3種常見的取整,分别是:
- 向下取整:
floor()
- 向上取整:
ceiling()
- 四舍五入:
round()
此外還有一種不常用的「向0看齊」取整方法
trunc()
。
他們的差别如下:
4, by =
是以通過
rep(1,round(0.58*50))
就可以得到和
rep(1,29)
一緻的結果了。
參考資料:請點選閱讀原文,有一個特别長的文檔,叫作"Frequently Asked Questions on R"。