天天看點

python 不等于None 不等于空_眼見不為實:為什麼29不等于29?

今天有一個小夥伴給我發來了一個截圖如下。

python 不等于None 不等于空_眼見不為實:為什麼29不等于29?

我下意識的說了一句:0.58*50不等于29。

python 不等于None 不等于空_眼見不為實:為什麼29不等于29?

感概,有時候,眼見也不一定為實。這就是一個浮點運算導緻的「算不準」問題。

在曆史上,其實已經有過類似因為浮點運算導緻的事故。

1994 年,英特爾的奔騰微處理器晶片的浮點計算單元出現了一個 Bug。在某些情況下,浮點除法并不能傳回正确的結果。例如 4195835.0/3145727.0 産生的是 1.33374 而不是 1.33382。雖然這個 Bug 可能對絕大多數人沒有影響,但是市面上 500 萬左右流通的晶片都存在該缺陷,随着整個事件的發酵,最終這個 Bug 給 intel 造成了 4.75 億美元的損失。這次事件也被稱為「Pentium FDIV bug」而載入了 Bug 史冊。

python 不等于None 不等于空_眼見不為實:為什麼29不等于29?

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 手機。

python 不等于None 不等于空_眼見不為實:為什麼29不等于29?

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"。