你應該知道的浮點數基礎知識

2021-02-19 藍橋杯
本文從一個有趣而詭異的實驗開始。最早這個例子博主是從 Stackoverflow上的一個問題中看到的。為了提高可讀性,博主這裡做了改寫,簡化成了以下兩段代碼:上面兩段代碼的唯一差別就是第一段代碼中y+=0.1f,而第二段代碼中是y+=0。由於y會先加後減同樣一個數值,照理說這兩段代碼的作用和效率應該是完全一樣的,當然也是沒有任何邏輯意義的。假設現在我告訴你:其中一段代碼的效率要比另一段慢7倍。想必讀者會認為一定是y+=0.1f的那段慢,畢竟它和y+=0相比看上去要多一些運算。但是,實驗結果,卻出乎意料, y+=0的那段代碼比y+=0.1f足足慢了7倍……世界觀被顛覆了有木有?博主是在自己的Macbook Pro上進行的測試,有興趣的讀者也可以在自己的筆記本上試試。(只要是支持SSE2指令集的CPU都會有相似的結果)。當然 原文中的投票最高的回答解釋的非常好,但博主第一次看的時候是一頭霧水,因為大部分基礎知識已經還給大學老師了。所以,本著知其然還要知其所以然的態度,博主做了一個詳盡的分析和思路整理過程。也希望讀者能夠從0開始解釋這個詭異現象的原因。現在讓我們複習大學計算機基礎課程。如果你熟練掌握了浮點數向二進位表達式轉換的方法,那麼你可以跳過這節。

Sign(1bit):表示浮點數是正數還是負數。0表示正數,1表示負數

Exponent(8bits):指數部分。類似於科學技術法中的M*10^N中的N,只不過這裡是以2為底數而不是10。需要注意的是,這部分中是以2^7-1即127,也即01111111代表2^0,轉換時需要根據127作偏移調整。

Mantissa(23bits):基數部分。浮點數具體數值的實際表示。

以數值5.2為例。先不考慮指數部分,我們先單純的將十進位數改寫成二進位。
小數部分我們相當於拆成是2^-1一直到2^-N的和。例如:
0.2=0.125+0.0625+0.007825+0.00390625即2^-3+2^-4+2^-7+2^-8….,也即.00110011001100110011
現在我們已經有了這麼一串二進位101.00110011001100110011。然後我們要將它規格化,也叫Normalize。其實原理很簡單就是保證小數點前只有一個bit。於是我們就得到了以下表示:1.0100110011001100110011 * 2^2。到此為止我們已經把改寫工作完成,接下來就是要把bit填充到三個組成部分中去了。
指數部分(Exponent):之前說過需要以127作為偏移量調整。因此2的2次方,指數部分偏移成2+127即129,表示成10000001填入。
整數部分(Mantissa):除了簡單的填入外,需要特別解釋的地方是1.010011中的整數部分1在填充時被捨去了。因為規格化後的數值整部部分總是為1。那大家可能有疑問了,省略整數部分後豈不是1.010011和0.010011就混淆了麼?其實並不會,如果你仔細看下後者:會發現他並不是一個規格化的二進位,可以改寫成1.0011 * 2^-2。所以省略小數點前的一個bit不會造成任何兩個浮點數的混淆。練習:如果想考驗自己是否充分理解這節內容的話,可以隨便寫一個浮點數嘗試轉換。通過 浮點二進位轉換工具可以驗證答案。

了解完浮點數的表達以後,不難看出浮點數的精度和指數範圍有很大關係。最低不能低過2^-7-1最高不能高過2^8-1(其中剔除了指數部分全0喝全1的特殊情況)。如果超出表達範圍那麼不得不捨棄末尾的那些小數,我們成為overflow和underflow。甚至有時捨棄都無法表示,例如當我們要表示一個:

1.00001111*2^-7這樣的超小數值的時候就無法用規格化數值表示,如果不想點其他辦法的話,CPU內部就只能把它當做0來處理。那麼,這樣做有什麼問題呢?最顯然易見的一種副作用就是:當多次做低精度浮點數捨棄的後,就會出現除數為0的exception,導致異常。當然精度失準嚴重起來也可以要人命,以下這個事件摘自wikipedia

On 25 February 1991, a loss of significance in a MIM-104 Patriot missile battery prevented it intercepting an incoming Scud missile in Dhahran, Saudi Arabia, contributing to the death of 28 soldiers from the U.S. Army’s 14th Quartermaster Detachment.[25] See also: Failure at Dhahran
於是乎就出現了Denormalized Number(後稱非規格化浮點)。他和規格浮點的區別在於,規格浮點約定小數點前一位默認是1。而非規格浮點約定小數點前一位可以為0,這樣小數精度就相當於多了最多2^22範圍。
但是,精度的提升是有代價的。由於CPU硬體只支持,或者默認對一個32bit的二進位使用規格化解碼。因此需要支持32bit非規格數值的轉碼和計算的話,需要額外的編碼標識,也就是需要額外的硬體或者軟體層面的支持。以下是wiki上的兩端摘抄,說明了非規格化計算的效率非常低。一般來說,由軟體對非規格化浮點數進行處理將帶來極大的性能損失,而由硬體處理的情況會稍好一些,但在多數現代處理器上這樣的操作仍是緩慢的。極端情況下,規格化浮點數操作可能比硬體支持的非規格化浮點數操作快100倍。
For example when using NVIDIA’s CUDA platform, on gaming cards, calculations with double precision take 3 to 24 times longer to complete than calculations using single precision.
如果要解釋為什麼有如此大的性能損耗,那就要需要涉及電路設計了,超出了博主的知識範圍。當然萬能的wiki也是有答案的,有興趣的讀者可以自行查閱。
於是我們就可以發現通過幾十上百次的循環後,y中存放的數值無限接近於零。CPU將他表示為精度更高的非規格化浮點。而當y+0.1f時為了保留跟重要的底數部分,之後無限接近0(也即y之前存的數值)被捨棄,當y-0.1f後,y又退化為了規格化浮點數。並且之後的每次y*x和y/z時,CPU都執行的是規劃化浮點運算。

而當y+0,由於加上0值後的y仍然可以被表示為非規格化浮點,因此整個循環的四次運算中CPU都會使用非規格浮點計算,效率就大大降低了。

當然,也有在程序內部也是有辦法控制非規範化浮點的使用的。在相關程序的上下文中加上fesetenv(FE_DFL_DISABLE_SSE_DENORMS_ENV);就可以迫使CPU放棄使用非規範化浮點計算,提高性能。我們用這種辦法修改上面實驗中的代碼後,y+=0的效率就和y+=0.1f就一樣了。甚至還比y+=0.1f更快了些,世界觀又端正了不是麼:) 修改後的代碼如下:

相關焦點

  • 15 張圖帶你深入理解浮點數
    忘了當時同事分享什麼主題,涉及到浮點數相關知識。於是我決定分享一期關於浮點數的,而且 Go 之父 Rob Pike 說不懂浮點數不配當碼農。。。So?!本著「要學習就系統透徹的學」這個原則,本文通過圖的方式儘可能詳細的講解浮點數,讓大家能夠對浮點數有一個更深層次的認識。
  • 一文讀懂浮點數
    e 為啥要減 127 呢?看到上面的公式,你可能會感到疑惑,1.f 是二進位數,但是不是二進位數(二進位數裡面只有 0 和 1),那這個公式應該怎麼算?其實可以這樣,如果你想要二進位數,就根據 e 去移動 1.f 中的小數點,當 e>0 時,小數點向右移動,當 e<0 時,小數點向左移動,e 是幾就移動幾位,移動完小數點就得到了二進位的浮點數;如果你想要十進位數,就把 1.f 轉成十進位,然後按照十進位的規則去計算,最後把兩者相乘,就得到了十進位的浮點數。
  • 你應該知道的前端小知識
    19.sessionStoragesessionStorage和localStorage的區別是,存在當前會話,很多人理解的是瀏覽器關閉,這是不對的,假設你在A頁面存儲了sessionStorage,新開選項卡將A頁面的連結粘貼進去打開頁面,sessionStorage也是不存在的。
  • 浮點數在計算機中是如何表示的
    如果你不確定答案,那麼你應該好好看看本文。float f = 8.25f;int i = *(int*)&f;IEEE浮點表示IEEE浮點標準用的形式近似表示一個數。並且將浮點數的位表示劃分為三個欄位:符號(sign)s決定這個數是負數(s=1)還是正數(s=0)。
  • 雙精度(64位)浮點數轉單精度(32位)浮點數
    1、浮點數格式: 64位浮點數(雙精度)格式為:(來自:http://baike.baidu.com/item/雙精度浮點數)
  • 看完這篇文章,你肯定理解什麼是浮點數了!
    浮點數是我們在編程中常用的一個數據類型,不知道大家想過沒有,它為什麼叫做float呢?還有,計算機對浮點數的內部表示方法IEEE 874到底是怎麼回事?要徹底理解浮點數,需要從計算機的底層存儲開始。假設有一個32 bit的計算機,需要你來設計一個支持存儲「小數」的方案,你會怎麼辦呢?
  • JavaScript浮點數陷阱,你需要了解一下
    0.000000001 和 0.999999999 這樣奇怪的結果,如 0.1+0.2=0.30000000000000004、1-0.9=0.09999999999999998,很多人知道這是浮點數誤差問題,但具體就說不清楚了。
  • 今天學Python第三課常用的數據類型有三種字符串,整數,浮點數
    數據類型在Python裡,常用的數據類型有三種,字符串,整數,浮點數字符串首先, 我們來看一下「黃袍加身」的字符串, 字符串英文名string,簡稱str。 其實, 在上一課的時候, 你已經用過它了,看面龐很熟悉。
  • 關於AppleTV遙控器的基礎知識以及基礎功能,你都知道多少?
    現在越來越多的人在使用AppleTV,但是對於新手而言,也許不大熟悉,如何使用AppleTV遙控器是個問題,關於AppleTV遙控器的基礎知識以及基礎功能,你知道多少?如果你有一臺新的Apple TV?以下是如何充分利用支持Siri的遙控器。
  • 【第1081期】JavaScript 浮點數陷阱及解法
    正文從這開始~眾所周知,JavaScript 浮點數運算時經常遇到會 0.000000001 和 0.999999999 這樣奇怪的結果,如 0.1+0.2=0.30000000000000004、1-0.9=0.09999999999999998,很多人知道這是浮點數誤差問題,但具體就說不清楚了。
  • 單精度浮點數與十六進位轉換
    #include <stdio.h>/*--十六進位到浮點數
  • python浮點數表示專題及常見問題 - CSDN
    浮點數用來存儲計算機中的小數,與現實世界中的十進位小數不同的是,浮點數通過二進位的形式來表示一個小數。在深入了解浮點數的實現之前,先來看幾個 Python 浮點數計算有意思的例子:0.1 == 0.10000000000000000000001True0.1+0.1+0.1 == 0.3FalseIEEE 浮點數表示法這些看起來違反常識的「錯誤」並非 Python 的錯,而是由浮點數的規則所決定的,即使放到其它語言中結果也是這樣的
  • Python實現一個類似range函數的浮點數生成器 - python高手養成
    range()函數生成一個整形序列通過前面幾篇內容的學習(有了它,出門旅遊再也不怕感冒了,Python使用迭代器進行天氣預報),我們知道,通過實現一個自定義類的__iter__()魔法方法,我們可以自定義一個可迭代對象。
  • 福建醫學基礎知識:你知道靜息電位嗎?
    福建醫學基礎知識:你知道靜息電位嗎? 【導讀】福建衛生人才網:提供2018福建醫療衛生招聘信息,如需了解關於醫療衛生招聘的報考指南、歷年試題、招聘信息等,請點擊上方連結了解更多詳情。靜息電位你了解嗎?
  • (小數)浮點數轉16進位數工具
    不知道大家在工具中是否有遇到過小數和十六進位數互相轉換的情況,如果有那你是如何處理的呢?
  • 醫學基礎知識:你知道靜息電位嗎?
    醫療衛生招聘考試資料:北京衛生人才網提供北京醫療衛生人才招聘考試複習資料,包括2018北京醫療衛生招聘考試資料、醫學基礎知識考點、解題技巧及醫生 護士招聘筆試面試輔導。 醫學基礎知識:你知道靜息電位嗎?。
  • 這些安全小知識,你應該知道!
    道路交通安全知識,這些你應該知道! 如今,交通事故頻發,除了不可避免的因素之外,其中很多是因為沒有遵守交通規則而造成的,這也為我們敲了一個警鐘,提醒我們應當加強學習交通安全知識,認真遵守交通規則。今天,小編為大家收集了一些交通安全知識,趕快來學習吧!
  • 關於變頻器,這些基礎知識你要知道
    變頻器在工業生產中作用很大,那麼關於變頻器的一些基礎知識你了解多少呢?今天請和供應商小喇叭一起來看一下吧。1、PWH和PAM的不同點是什麼?PWM是英文Pu 1 se Width Modulation(脈衝寬度調製)縮寫, 按一定規律改變脈衝列的脈衝寬度,以調節輸出量和波形的一種調 值方式。
  • 小板慄學編程2-動物知識競猜
    原創:小板慄啊,我終於開始學一些基礎了,從我的第一個程序開始講吧,我做的第一個程序是print(『Hello, World!』)。我把TA給保存,然後點了run,再選擇run module。因為我那個時候不會使用F5,所以很慢。爸爸說這是每個學習編程人的第一個程序。有點像我的程序寶寶對世界說的第一句話呢!
  • Java基礎學習:java中的基本數據類型
    這兩個類型可能大部分情況下都說不明白關係和區分,首先要理解幾個基礎概念。 浮點數:在計算機中用以近似表示任意某個實數。具體的說,這個實數由一個整數或定點數乘以某個基數(計算機中通常是2)的整數次冪得到 單精度浮點數:單精度浮點數是用來表示帶有小數部分的實數,一般用於科學計算。