「計算機原理」| 為什么浮點數(shù)運算不精確?(阿里筆試)

點贊關(guān)注,不再迷路,你的支持對我意義重大!

?? Hi,我是丑丑。本文 「計算機組成原理」| 導(dǎo)讀 —— 已收錄,這里有 Android 進階成長路線筆記 & 博客,歡迎跟著彭丑丑一起成長。(聯(lián)系方式在 GitHub)

前言

最近在公眾號阿里技術(shù)上看到一套孤盡老師出的 10道Java測試題(據(jù)說阿里 P7 工程師的答題正確率只有 50%) ,其中有幾道題是關(guān)于浮點數(shù)的,聰明的你,在評論區(qū)留下答案吧。

(1)
float a = 0.125f; 
double b = 0.125d;
System.out.println((a - b) == 0.0); 
代碼的輸出結(jié)果是什么?

A. true
B. false
(2)
double c = 0.8;
double d = 0.7;
double e = 0.6;

那么 c-d 與 d-e 是否相等?

A. true
B. false
(3)
System.out.println(1.0 / 0); 的結(jié)果是什么?

A. 拋出異常
B. Infinity
C. NaN
(4)
System.out.println(0.0 / 0.0); 的結(jié)果是什么?

A. 拋出異常
B. Infinity
C. NaN
D. 1.0
(5) 引用自《技術(shù)之瞳》
以下數(shù)字在表示為double(8字節(jié)的雙精度浮點數(shù))時存在舍入誤差的有:

A 100 
B 根號2 
C 10^30
D 0.1 
E 0.5
(6) 
寫出float x 與“零值”比較的if語句

目錄


1. 相關(guān)概念

關(guān)于浮點數(shù)的相關(guān)概念如下,在下面的分享中,我將不重復(fù)解釋:

2. 計算機中數(shù)據(jù)的表示方法

  • 在你的Chrome瀏覽器上按F12,然后找到console,輸入表達式0.1 + 0.2,回車
  • 在你的電子計算器上按0.1 + 0.2 =

你會發(fā)現(xiàn)前者的結(jié)果是0.30000000000000004,而后者的結(jié)果是0.3(當然了!)。那么,為什么計算機的準確度,連普通的電子計算器的都比不上?關(guān)鍵在于計算機與計算器使用了不同的數(shù)據(jù)表示方法

2.1 n 位二進制可以表示的信息量

對于整數(shù)來說,大家都知道8位有符號整數(shù)可以表示[-128,127],8位無符號整數(shù)可以表示[0,255],不管怎么樣,8位二進制無論如何也只能表示256個整數(shù)。當需要表示257這個數(shù),有且只有兩個辦法:

  • 1、增加位數(shù),例如9位二進制可表示的數(shù)值范圍就可以容納257這個數(shù)
  • 2、改變編碼規(guī)則,例如規(guī)定真值是在機器數(shù)的基礎(chǔ)上加一,這樣的話,0000,0000就表示數(shù)11111,1111就表示數(shù)257。(事實上,這就是移碼干的事情,3.1節(jié)會再提到)

這就是計算機的自有屬性,數(shù)字計算機只能處理離散數(shù)據(jù),二進制的位數(shù)直接決定了它能表示的離散數(shù)據(jù)個數(shù),也決定了它所能表示的信息個數(shù),對于n位二進制數(shù),它可以表示的信息量為2^N

同理,我們把問題域擴展到全體實數(shù),8位二進制同樣也只能表示256個實數(shù)。假如約定這樣一種8位編碼:最低兩位為小數(shù)區(qū)域,其余是整數(shù)區(qū)域,這樣就有:

000000.00 // 表示 0.0
000000.01 // 表示 0.25
000000.10 // 表示 0.5
000000.11 // 表示 0.75
000001.00 // 表示 1.0
000001.01 // 表示 1.25
... 此處省略250個數(shù)

我們發(fā)現(xiàn),介于0.0到0.25的數(shù)字被跳過了,而即使把小數(shù)區(qū)域的位長擴大到8位、16位、甚至一個極大的位數(shù),也無法充分表示介于0.0到0.25所有的數(shù)。這是因為,在0.0到0.25之間的數(shù)是連續(xù)的,有無限多個數(shù),但是有限的N位長二進制最多只能表示2^N個信息量,有限的信息量無法表示無限的數(shù)據(jù)量,這就是現(xiàn)實世界與計算機世界的矛盾。

2.2 定點數(shù)表示

實數(shù)有兩種表示格式,分別是定點數(shù)浮點數(shù)。像上面說的這種約定整數(shù)部分和小數(shù)部分為固定位置的格式,就是定點數(shù)表示。

  • 定義:
    定點數(shù)(fixed point numbers)約定機器數(shù)中的小數(shù)點總是固定在某個特定的位置。

  • 格式:
    分為符號位、整數(shù)部分、隱含的小數(shù)點、小數(shù)部分。

  • 特點:
    整數(shù)部分和小數(shù)部分位長固定,當需要表示絕對值特大或者特小的數(shù)需要很大的空間

2.3 浮點數(shù)表示

我們已經(jīng)知道32位二進制可以表示的信息量有2^{32}\approx 4*10^9,但是很多語言都會宣稱它們的32位單精度浮點數(shù)的數(shù)值范圍約為-3.4*10^{38}~ 3.4*10^{38}(左右邊界),這是因為采用了浮點數(shù)格式。

  • 定義:
    浮點數(shù)(floating point numbers)使用科學(xué)計數(shù)法存儲數(shù)字,小數(shù)點的位置根據(jù)指數(shù)的大小而浮動。

  • 格式:
    分為符號位、指數(shù)、尾數(shù) :

N=2^E*M

  • 特點:
    一部分位作為指數(shù),可以擴大所表示的數(shù)值范圍

  • 意義:
    是數(shù)字計算機表示實數(shù)的格式,并以IEEE 754 (IEEE Standard for Binary Floating-Point Arithmetic)為標準。

2.4 定點數(shù)和浮點數(shù)的區(qū)別

  • 表示范圍:浮點數(shù)一部分位為指數(shù),相同位長,浮點數(shù)格式所能表示的數(shù)值范圍遠遠大于定點數(shù)格式
  • 精度大小:浮點數(shù)格式只有一部分位是有效數(shù)值位,相同位長,浮點格式的精度比定點格式低
  • 運算復(fù)雜度:浮點數(shù)主要包括指數(shù)和尾數(shù)兩部分,運算時需要對階、尾數(shù)計算、規(guī)格化等步驟,浮點運算比定點運算復(fù)雜
  • 溢出:定點運算在數(shù)超過可表示數(shù)值范圍即發(fā)生溢出;在浮點運算中,只有規(guī)格化后數(shù)值超過指數(shù)所能表示的范圍才溢出。

2.5 計算機表示實數(shù)的步驟

前面講到相關(guān)概念時提到了實數(shù)的概念,具體如下:

復(fù)數(shù)的分類 示意圖

一個虛數(shù)上相當于兩個實數(shù),所以我們只需要關(guān)心實數(shù)在計算機中的表示即可,將一個實數(shù)裝載入計算機需要分為三個步驟:

  • 1、轉(zhuǎn)換為二進制數(shù)格式
    這個步驟可能損失精度,換句話說,有些數(shù)會損失精度,而有些數(shù)不會,這取決于表示這個數(shù)需要的信息量和浮點數(shù)的存儲格式

    • 無理數(shù)(無限不循環(huán)小數(shù))包含的信息量是無限的,例如圓周率\pi,沒有任何一本書能夠?qū)懙綀A周率最后一位,java.lang.Math.PI也只是\pi的近似值,類似的,使用有限的二進制位自然無法精確表示;
    • 有限循環(huán)小數(shù)包含的信息量是有限的,它的信息量分為整數(shù)部分+小數(shù)不循環(huán)部分+小數(shù)循環(huán)部分,例如1.8333333... = 1.8\overline{3}。但是浮點數(shù)的表示方法分為符號位、指數(shù)區(qū)域和尾數(shù)區(qū)域,并不會單獨用一塊區(qū)域來存儲循環(huán)的部分,因此有限循環(huán)小數(shù)也無法精確表示;
    • 最后剩下整數(shù)和有限小數(shù),它們包含的信息量也是有限的,關(guān)鍵看是否有因子5。舉兩個例子:0.1和1萬億,請問哪個數(shù)能用二進制數(shù)精確表示?
      從十進制看,0.1擁有2個信息量(個位數(shù)為0,第一位小數(shù)為1),1萬億擁有一萬億個信息量,二選一的話,肯定是選擇信息量更低的0.1。但是,從二進制看,我們會發(fā)現(xiàn)0.1轉(zhuǎn)換為二進制居然是一個無限循環(huán)小數(shù)0.0\overline{0011}(將整數(shù)部分除2取余、小數(shù)部分乘2取整來完成轉(zhuǎn)換),所以答案是:1萬億可以精確表示,而0.1無法精確表示!
      事實上,在0.1 到 0.9 的 9 個小數(shù)中,只有 0.5 可以用二進制精確的表示。怎么理解呢?我們把1想象成一個圓,在十進制里,它可以劃分為10等分;但在二進制里,它只能劃分為2等分。
      也就是說二進制里一位,要么表示0,要么表示一半,它沒有辦法像十進制那樣表示3/10、4/10、6/10...... 1的一半在十進制里是什么?0.5,所以二進制可以精確表示0.5,任何包含因子5的數(shù)都可以用二進制精確表示。無法精確表示的數(shù)字,存儲值只能是真實值的近似表示。
    提示

    類似地,思考下十進制數(shù)格式可以精確表示1/3嗎?

  • 2、轉(zhuǎn)換為二進制科學(xué)計數(shù)法表示
    這個步驟將二進制小數(shù)轉(zhuǎn)換為規(guī)范化的科學(xué)計數(shù)法表示:N = a * B^E,因為只是寫法的轉(zhuǎn)換,所以這一步?jīng)]有精度損失。

  • 3、轉(zhuǎn)換為IEEE 754 標準格式
    IEEE 754嚴格規(guī)定了尾數(shù)域和指數(shù)域可表示的大小,位數(shù)有限,意味著信息量是有限的。有些數(shù)需要的二進制數(shù)據(jù)量巨大,在這個步驟自然會損失精度,具體如下:

    • 大于浮點數(shù)可以表示的最大絕對值:上溢(溢出到\pm\infty

    • 小于浮點數(shù)可以表示的最小絕對值:下溢(溢出到\pm0

    • 尾數(shù)有效位數(shù)超過尾數(shù)域位數(shù)(另外還有隱含的整數(shù)位1):舍入誤差


3. IEEE 754 標準的浮點數(shù)

IEEE 二進制浮點數(shù)算術(shù)標準(IEEE 754)是廣泛使用的浮點數(shù)運算標準,是大多數(shù)高級語言的現(xiàn)行浮點運算標準,例如C/C++、Java、JavaScript等。

3.1 一般格式

浮點數(shù)格式的關(guān)鍵是科學(xué)計數(shù)法格式:N = a * B^E,其中:

  • a稱為尾數(shù)(mantissa),或稱有效數(shù)字(significand)
  • B稱為基數(shù)(base),在二進制數(shù)中,基數(shù)是2
  • E稱為指數(shù)(exponent)

一個數(shù)的科學(xué)計數(shù)法表示是不唯一的,舉個例子,對于二進制數(shù)1111.0000_{(2)}來說,以下都是合法的科學(xué)計數(shù)法表示:111.1*211.11*2^211110*2^{-1},但這些都不是規(guī)格化的表示,唯一規(guī)格化的表示為:1.111*2^3

對于一個科學(xué)計數(shù)法表示,當尾數(shù)a的整數(shù)部分有且僅有一位有效數(shù)字時,我們稱它是規(guī)格化的。由于0在數(shù)字的最左邊是無效的,而在二進制的世界里只有0和1,因此,二進制數(shù)使用規(guī)格化的科學(xué)計數(shù)法時,整數(shù)部分固定為1。

既然整數(shù)部分1是固定的,那么就沒有必要存儲整數(shù)部分的信息了。正因如此,IEEE 754 標準的浮點數(shù)采用隱藏位的策略,整數(shù)部分的1是隱含的,不需要占用一位比特,這樣是使得尾數(shù)可以多一位有效數(shù)。

綜上,IEEE 754 浮點數(shù)的一般格式如下:
N = (-1)^s*1.f*2^E

IEEE 754 標準的一般格式

現(xiàn)在,我們已經(jīng)知道浮點數(shù)劃分的三個區(qū)域,現(xiàn)在我們來看這三個區(qū)域是如何求值的:

  • 符號位:0表示正,1表示負
  • 指數(shù)區(qū)域:移碼
    • 指數(shù)區(qū)域采用移碼表示:E = 機器數(shù) - bias,偏移值bias=2^{位長-1}-1
      例如位長為8時,bias=127,位長為11時,bias=1023
    • 注意:指數(shù)域全0和全1為特殊值
  • 尾數(shù)區(qū)域:隱藏整數(shù)位的原碼
    尾數(shù)區(qū)域采用原碼表示:1.f = 1 + 機器數(shù)

舉個例子,十進制數(shù)100_{(10)}轉(zhuǎn)換為二進制為1.100100*2^6_{(2)}。這里推薦一個站點:浮點數(shù)轉(zhuǎn)換器,它可以很方便地對比實數(shù)的真值與機器數(shù)表示,如下圖所示:

3.2 兩種常用格式

前面講的是IEEE 754 浮點數(shù)的一般格式,其中最常用的是32位單精度浮點數(shù)64位雙精度浮點數(shù),在高級語言中通常代表floatdouble兩種數(shù)據(jù)類型(例如C/C++、Java),在有些語言中只有一種數(shù)字格式number(例如JavaScript/TypeScript)。

  • 單精度
    單精度浮點數(shù)有8位指數(shù),23位尾數(shù),再加上隱藏的整數(shù)1,總共有24位二進制精度
  • 雙精度
    雙精度浮點數(shù)有11位指數(shù),52位尾數(shù),再加上隱藏的整數(shù)1,總共有53位二進制精度,具體如下:

3.3 特殊值

在 IEEE 754 標準規(guī)定指數(shù)區(qū)域全0 或 全1為特殊值,具體如下:

  • 非規(guī)范化數(shù)(Denormalized Number)

    • 定義:指數(shù)域全0,尾數(shù)域不為0(去掉隱含整數(shù)域為1的約定)
    • 意義:可以保存絕對值更小的數(shù),所有可表示的浮點數(shù)的差值都可以表示
  • +0/-0

    • 定義:指數(shù)域全0,尾數(shù)域全0(去掉隱含整數(shù)域為1的約定)。IEEE 754 未要求具體的尾數(shù)域,意味著NaN不是一個而是一族。
    • 意義:符號位為0是+0,符號位為1是-0,在涉及無窮的運算中避免丟失符號信息,例如\frac{1}{1/x} = x,如果0不區(qū)分正負,在x=\pm\infty時不成立
  • 正負無窮(Infinity)

    • 定義:指數(shù)域全1,尾數(shù)全0
    • 意義:用于表達計算中產(chǎn)生的上溢(overflow),使得計算中出現(xiàn)上溢不至于終止計算
    • 產(chǎn)生:除了NaN外的非零值除以0,其結(jié)果為正負無窮
  • NaN(Not a Number)

    • 定義:指數(shù)域全1,尾數(shù)域不為0
    • 意義:表示計算中的錯誤情況,例如\frac{0.0}{0.0}\sqrt2,使得計算中出現(xiàn)錯誤不至于終止計算
    • 特點:NaN是無序的,比較操作符在任一操作數(shù)為NaN是為false!=在任一操作數(shù)為NaN時為true,這意味著NaN != NaN

參考資料

  • 《編碼·隱匿在計算機軟硬件背后的語言》(第23章) —— [美] Charles Petzold 著
  • 《Java編程思想》(第2章) —— [美] Bruce Eckel 著
  • 《深入理解Java虛擬機》(第6.4節(jié)) —— 周志明 著
  • 《JavaScript權(quán)威指南》(第3章) —— [美] David Flanagan 著
  • 《計算機組成原理考研復(fù)習(xí)指導(dǎo)》(第2章) —— 王道論壇 組編
  • 《代碼之謎》 (第4、5章)—— justjavac(迷渡)的博客文章

創(chuàng)作不易,你的「三連」是丑丑最大的動力,我們下次見!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,646評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,595評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,560評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,035評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,814評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,224評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,301評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,444評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,988評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,804評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,998評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,544評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,237評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,665評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,927評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,706評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,993評論 2 374