Go之父說:不懂浮點數不配當碼農…

2021-03-06 Go語言中文網


所以要趕緊補充一些高大上的浮點數知識吧

浮點數很重要

Go語言之父,Rob Pike大神曾經在微博吐槽過:不能掌握正則表達式或浮點數就不配當碼農!

雖然原文已經被刪除了(大神也有害怕的時候),還好我已經存檔了:

You should not be permitted to write production code if you do not have an journeyman license in regular expressions or floating point math.

關於正則表達式已經有很多權威著作或入門教程(但是很多主流的程式語言的實現居然也藏了不少爛算法,性能被幾十年前ed指數級吊打)。

但是討論浮點數的資料則比較少。一般的編程圖書也不會深入討論浮點數。但是這並不代表浮點數不重要。實際上IEEE754之父,William Kahan正是因為浮點數標準化的工作獲得了圖靈獎。

為什麼叫浮點數

人的生命是有限的,計算機的內存也是有限的。在主流的程式語言中,浮點數一般有32bit和64bit兩種,分別對應單精度和雙精度浮點數(有些地方還有半精度的浮點數),也就是說一個32bit的內存只能表達2^32種狀態(大概40億)!

但是實際上數從無窮小到無窮大範圍異常的廣泛。一個阿伏伽德羅常數要寫成60221407600000000000000,十進位都要24位,32bit的二進位位根本不夠用。此外像原子半徑之類的數又非常之小,前面要用一垃圾0來填充。

為了少寫很多0(因為紙張有限,內存也只有有限的32比特),懶惰的先賢們發明了科學記數法來表示很大或很小的數。科學記數法的思路就是用較少的指數來表示很大或很小數的係數。但是光有科學記數法還不行,比如60221407600000000000000*10^0依然沒有節省紙張。只有規範化的科學記數法才能少寫垃圾0,比如6.02*10^23看起來就很清爽了。而規範化的科學記數法中,有效位部分的小數點是隨著指數發生變化的。

浮點數也是採用類似規範化的科學記數法的思路。而IEEE754是浮點數格式的國際標準,目前主流的程式語言都是採用這個標準。

浮點數的布局

這是C語言表示的float浮點數內存布局:


union ieee754_float {
    float f;

    
    struct {
        unsigned int mantissa:23;
        unsigned int exponent:8;
        unsigned int negative:1;
    } ieee;

    
    struct {
        unsigned int mantissa:22;
        unsigned int quiet_nan:1;
        unsigned int exponent:8;
        unsigned int negative:1;
    } ieee_nan;
};

其中最低的23it是mantissa對應有效數字,然後是8bit的exponent表示指數,最高位的1bit表示符號位。需要注意的是指數部分採用移碼表示,也就是exponent作為無符號數減去127得到的最終的指數。

為何float32隻有6個精度

因為有效數字mantissa部分是23bit,而1<<23對應十進位的8388608,不能完整表示全部的7個十進位位,因此就只剩下6個有效數字了。因此fmt.Printf中的%f對應float32默認就只列印6個十進位數(因為多列印就超出mantissa的表示範圍,不能準確表達)。

浮點數的詭異:浮點數有2個0

知道了浮點數的布局,就自然可以理解為什麼浮點數會有2個0。因為浮點數有一個獨立的符號位,只要吧0.0的符號位設置為負數就可以得到一個負數0:

fmt.Println(math.Float32frombits(0))     
fmt.Println(math.Float32frombits(1<<31)) 

上面的代碼通過在0的基礎之上加1<<31將符號位設置為1,這樣就得到了負0。

因此用浮點數作為map的Key時,可能會遇到一些詭異的事件。比如-0能取到0對應的值嗎?

浮點數的詭異:沒有0.3

第一次看到這個標題,有人可能有異議。證據在這裡:

fmt.Println(0.3) 

但是作為一個槓精,我要說fmt.Println代碼是有問題的,它可能作弊了,比如:

func fmt.Println(x) {
    if x == 0.3 {
        println("0.3")
    }
}

fmt.Println內部列印的是字符串「0.3」,不是浮點數的0.3!

實際上fmt包真的是作弊了(而且作弊的算法也成了一門學問),不信看下面這個代碼:

func main() {
    fmt.Printf("%f\n", 0.3)
    fmt.Printf("%.10f\n", 0.3)
    fmt.Printf("%.20f\n", 0.3)
}





每次輸出的結果都不一樣,這不是作弊是什麼?fmt包作弊列印0.3的原因是因為IEEE754中不存在0.3這個數!

不相信的話可以換成0.5試試:

func main() {
    fmt.Printf("%f\n", 0.5)
    fmt.Printf("%.10f\n", 0.5)
    fmt.Printf("%.20f\n", 0.5)
}




輸出0.5時,就不會因為精度的變化導致輸出結果發生抖動。

對於Go語言中,常量和變量的運算規則是不同的,因此0.1+0.2換成變量就會發生變化:

func main() {
    var a = 0.1
    var b = 0.2
    fmt.Println(0.1+0.2, a+b)
}

第一個輸出是0.3,第二個居然不是0.3。還好我們已經知道沒有0.3這個浮點數,因此第一個0.1+0.2的結果也不是0.3。不能表示0.3的原因是因為計算機採用的是二進位的科學記數法,而0.3無法通過用2的不同指數的狀態組成得到。

不僅沒有0.3,浮點數中缺少的數字多了去了。根據抽屜原理,float32隻能表示2的32次方個狀態,也就是40多億個數。但是float32的值域可是已經大大超出了40億的範圍,因此裡面必然有很多數在吃空餉。

無窮有正負

無窮在浮點數中對應一個特殊的數(或者說指數值最大的那種0)。參考前面的浮點數內存布局,指數有8個bit表示,最大的指數表示是255。

下面我們構造一個指數部分是255的0:

fmt.Println(math.Float32frombits(255<<23)) 

這樣得到的就是一個正的無窮(255最大的指數表示窮,0有效位表示無)。

有了正無窮,得到負無窮就比較容易了。只要加一個符號標誌位即可:

fmt.Println(math.Float32frombits(255<<23 + 1<<31)) 

用科學記數法表示是這樣:0.0*2^(255-127)

Nan不是一個數,它表示的是一種bit模式

在IEEE754浮點數的指數值域中255是最重要的一個,因為無窮對應的指數就是255。在無窮中除了指數是255之外,有效位部分是0。那麼問題來了,指數為255,有效位不是0的是啥玩意?

可以跑代碼看看:

fmt.Println(math.Float32frombits(255<<23 + 1)) 
fmt.Println(math.Float32frombits(255<<23 + 2)) 

它們都是NaN,也就是Not-a-Number,非數不是數。NaN不是一個非數,而是一類非數。

Nan有個重要的特性,就是自己和自己都不相等(想想如果用它作為map的key會有什麼效果)。Nan是非法的運算得來的,比如sqrt(-1)或0/0都是非數。

浮點數不滿足結合率

浮點數不滿足結合率,比如a+(b+c)和(a+b)+c不等價。具體原因和開頭的x=x+1方程類似。

如果x=x+1成立,但是x=x+2並不一定成立。如果x!=x+2,那麼顯然它和x=(x+1)+1結果是不等價的。

四捨五入是錯誤的

四捨五入分為2個部分,四舍還是四舍,但是五入就不一定是五入了。還存在五舍的情形。五作為一個絕對中間的位置,憑什麼總是要五入(借錢的人和還錢的人肯定也有不同的看法)?

那什麼時候需要五舍?根據計算的結果長得是否漂亮,選擇五舍或五入。

指數為什麼要採用移碼

在早年間,浮點數運算晶片是一個奢侈的玩意。碼農們都不做浮點數運算。但是運氣也有背的時候,比如需要給一個浮點數表示的數組排序。

正如凌凌漆所言,IEEE754並非浪得虛名。即使沒有浮點數晶片,碼農們依然可以快速給浮點數數組排序:將浮點數數組當中整數數字進行排序就可以了。

為何?因為浮點數數組有序的話,那麼同樣bit模式的整數數組依然是有序的。其中第一個原因是符號位保證有序,第二個原因是指數採用移碼保證大指數較大,最後幾十有效數字部分比較小數點部分大數字。

解浮點數方程: x+1=x

不是純數學意義上的方程, 對應計算機的一個浮點數問題:

if((float)(x+1.0) == (float)(x)) { x = ? }

簡單分析, ieee754中float採用23bit表示有效位, 再加省略的1, 共有24bit.

當結果超出24bit時, 小數部分被被丟失:

var a float32 = 1 << 24
var b float32 = a+1

fmt.Println(math.Float32bits(a)) 
fmt.Println(math.Float32bits(b)) 

因此浮點數運算是不滿足結合率的!

浮點數分類

首先是符號位,根據符號位可以分為正數和負數兩類(包括兩種0)。不過符號位分類比較簡單,這裡忽略。

此外,根據指數位和有效數字部分的不同組合,可以分為以下幾種:

switch {
case 指數 在 1-254 之間:
    
    
    
case 指數 == 0:
    if 有效數 == 0 {
        
    } else {
        
        
    }
case 指數 == 255:
    if 有效數 == 0 {
        
    } else {
        
    }
}

所以說指數部分是IEEE754的精髓。

浮點數在數軸的分布

相關焦點

  • 今天學Python第三課常用的數據類型有三種字符串,整數,浮點數
    name='小美'print("Let's go go go")是不是很熟悉? 你猜對了,穿上單引號、雙引號、三引號黃袍的內容就是字符串,無論引號裡面內容是中文、英文、法文、數字、符號、甚至是火星文。在上面代碼裡 小美' ,"Let's go go go" 都屬於字符串類型。
  • 真有小夥伴不知道浮點數如何轉二進位嗎?
    來吧,坐下聊先前在前文 《老大說:誰要再用double定義商品金額,就自己收拾東西走》 中已經痛徹心扉地聊過:在處理諸如 訂單交易、貨幣計算、以及商品金額時慎用浮點數(double/float)去定義變量,否則可能會遇到各種奇葩的問題
  • 社畜碼農
    這說的不就是碼農嗎?像剛過去的雙11節,是不少社畜碼農加班熬來的。有網友說,老闆要他一兩個月寫個類似於京東的 APP,大概是200-300個頁面。IT行業發展迅速,新技術迭代太快,從web端到客戶端再到H5,小程序,每個公司都在搶佔各端的市場,只會單一技術的碼農很容易被時代拋棄。
  • 蓋裡奇不配吃烤鴨是什麼梗 很多網友不懂這都是啥意思
    蓋裡奇不配吃烤鴨是什麼梗 很多網友不懂這都是啥意思時間:2019-07-15 17:04   來源:福建閩南網   責任編輯:凌君 川北在線核心提示:原標題:蓋裡奇不配吃烤鴨是什麼梗很多網友不懂這都是啥意思 近期蓋裡奇上熱搜,很多網友調侃蓋裡奇才是歐美圈的頂流,還有不少網友吐槽蓋裡奇不配吃烤鴨,蓋裡奇不值得,不過很多網友不懂這都是啥意思
  • 誰說35歲碼農沒未來?刷寶短視頻帶你了解不一樣的碼農生活
    誰說35歲碼農沒未來?刷寶短視頻帶你了解不一樣的碼農生活 午夜凌晨, 34 歲的金鵬飛依舊坐在電腦前加班,他的這種工作狀態已經持續了近三年。
  • 賈母說讓鴛鴦做賈璉房裡人,王熙鳳為什麼說他不配?
    紅樓夢裡,賈赦逼娶鴛鴦一回,惹得賈母大怒,後來還是王熙鳳乖覺,一個玩笑說賈母把人調理的水蔥兒似的,難怪人惦記,就緩解了緊張尷尬的氣氛。賈母就玩笑說要把鴛鴦給了賈璉,留作房裡人,讓賈赦難堪,王熙鳳一聽話頭不對,就趕緊婉言拒絕了,並且說賈璉不配。
  • 「go out」不只是「出去」,可不能隨便說!會錯意了很尷尬!
    其實,「go out」不只有「出去」的意思,作為一種比較常見的表達,如果會錯意了可能會很尷尬。今天,普特君就和大家詳細說一說。我們經常所說的「出去吃;下館子」,正確的表達應該是「eat out」。如果你說「eat outside」其實強調的是空間,在屋外(戶外)吃。
  • Here we go
    然後我算是一路見證了這首歌的創作過程,當時的羅彈著旋律沒填詞嘴裡還嘟囔著一些我聽不懂的語言,然後到後來的填詞,然後到期中考試。編了個簡單的曲。  前兩天羅還說這首編曲是不是太簡單了,他的想法是還想要再加入其他的樂器,我跟他說可以簡單先出個流行版的,以後還可以再重製,重新編曲再做一遍。  所以有了這第一版「粗製濫造」的《Here we go》。
  • 老外常說的「there you go」是什麼意思呢?說出3種算你厲害!
    老外口中有一些經常說的詞彙,放在不同的語境裡面,所表現出來的意思完全不同,比如大白今天要說的「There you go」。了解的人一定都知道There you go可能是老外最常說的句子之一了,裡面包含的意思特別特別的多,但是你要是有外國朋友問他這是什麼意思,他可能一時也說不明白,大白簡單的整理了一下,希望能幫助大家理解其意思。
  • 從碼農島上誕生的那些APP 上
    搜了搜,碼農島果然名不虛傳。試著做了一下發現東西實在是不少,只得分成兩期。如果有問為什麼沒有xx的……不如等明天看後再說?HippoHippo大家應該很熟悉,畢竟每天雙擊紫島版本號獲得奈奈語音的肥宅連起來可繞島好幾周。
  • 《碼農修真》:修真所需金手指是編程,普通玩家只能幹瞪眼!
    《碼農修真》,維度論,3級作者。分類:科幻-未來世界。【簡介】張德明穿越了,但是,為什麼別人穿越都是自帶系統金手指,各種抽獎,加點,殺人得經驗,再不濟也是天道酬勤,付出就有收穫。而今天這本《碼農修真》,2020年10月1日上架,首訂180。目前起點收藏11萬,書城收藏41,粉絲數20萬,盟主2位,有一位是白銀盟。除了首訂比較離譜之外,其他越看越精彩。故事主要圍繞主角張德明開始。
  • 矽谷碼農和Hipster們,為什麼永遠當不了英雄
    就像中國遊戲界可以出《中關村啟示錄》,製作人可以以「工長君」為筆名標榜工時之長,而西方遊戲界加班雖然在DOOM1時代被標榜,但很快成為不能向外說的禁忌。而對於現實的網際網路業題材,科幻片裡可以騎高達炸死星,真實科技卻萬不能在遊戲中露面,因為一點也不酷。種種文化上的蛛絲馬跡,都像一副讓人費解的謎團。其實這也很好解讀:當代科技產業所代表的文化,和美國的核心價值觀,差了一百八十度。
  • 囚徒困境下的996碼農們
    996的事情,我總是希望一次可以把它說的透徹一些。所以我就從各個不同的維度展開吧。希望這樣可以幫助大家理解什麼是詩和遠方,什麼是當下,什麼是理想,什麼是現實。無論如何,真的英雄是認清了生活的真相還依然愛它的人。
  • 推薦一部美劇《天生不配》,各位劇荒的朋友
    天生不配今天我要給各位劇荒的朋友推薦一部美劇《天生不配》。家有兒女《天生不配》是一部充滿歡樂的幽默家庭劇,有一定類似美版的《家有兒女》,不同的在於《天生不配天生不配劇中三個孩子雖然都擁有高智商,但每個人側重的領域都不同,大兒子布萊恩看似成熟,有一種「嚴肅爸爸」的氣質,熱愛研究,二女兒妮可一心從政,從小就規劃好了自己的人生,現代女青年一名,最愛和家裡人比智商,三兒子馬克智商最高,藝術細胞高,小女兒蕾拉每天就吃吃喝喝,蹦蹦跳跳,說著一些看似非常符合一個
  • HBO新劇《矽谷》:終於有部美劇講科技圈和碼農了!
    在HBO的最新喜劇片《矽谷》(Silicon Valley)中,一系列諷刺、惡搞和誇張手法把科技圈的怪現象一一抖落了出來。鑑於該劇所引起的圈內爭議,HBO近日已經宣布續訂第二季。不同於一般的情景喜劇,《矽谷》有一個較為清晰的故事主線:創業者Richard和他的碼農朋友們如何在創業之路上化解各種坎坷。
  • 英語單詞go,千萬不要說它是走的意思
    go crazy 瘋了,go hungry挨餓,go rich富了,go bad 變質了,壞了,go pale 變蒼白,go bald變禿,go cold 變冷,go naked裸體go unpunished不受懲罰,go missing失蹤,
  • 24歲碼農在家孵鴕鳥 百萬「雲爸媽」圍觀 - 泉州晚報數字報·泉州網
    最近,24歲的「碼農」謝濤就在家裡當起了「鴕鳥爸爸」,他製作的視頻《歷時5個月,鴕鳥孵化成功》播放量已超220萬!其在家孵鴕鳥的經歷引發網友熱議。 用CPU孵出一窩小鵪鶉 這位碼農真會玩 謝濤在一家雲計算公司工作,是一個「95後」,工作之餘,他有倆愛好:做B站UP主、孵蛋。
  • 不懂得珍惜的人,不配談戀愛
    > ♫朗讀:代代來源:大唐三公子末尾歌曲:房東的貓 - 雲煙成雨在網上看到這樣一句話: 不懂得珍惜的人,不配談戀愛
  • 24歲碼農在家用CPU孵鴕鳥 引百萬「雲爸媽」圍觀
    最近,24歲的碼農謝濤就在家裡當起了 " 鴕鳥爸爸 ",他製作的視頻《歷時5個月,鴕鳥孵化成功》播放量已超220萬,而他在家孵鴕鳥的經歷也引發網友熱議。自己能孵化鴕鳥蛋嗎?怎麼孵化?鴕鳥長大了怎麼辦?帶著這些疑問,現代快報記者採訪了謝濤和動物專家。
  • 裴秀智新戲有碼農,不寫代碼負責哭,情人終成眷屬觀眾想分手
    寫一個創投企業,還有裡面要演幾個碼農,這個跟吃著泡菜談戀愛的編劇很熟悉的體裁差了好遠好遠。這種題材有一點新穎,如果演好了,那可能是又一部《未生》。當然這部《Startup》沒有達到《未生》的高度。這個片子裡面的幾個配角,其實演的都還不錯,有那麼一點點碼農的意思。至於秀智小姐,因為有龐大的粉絲,大家也不能怎麼評價。美如畫的秀智,更多時候讓人覺得在電視上看到她更像領導著一個女團的隊長。