C語(yǔ)言之浮點(diǎn)數(shù)的表示與儲(chǔ)存方式
1. 二進(jìn)制小數(shù)
1.1 十進(jìn)制小數(shù)的表示方法
理解浮點(diǎn)數(shù)的第一步是考慮含有小數(shù)值的十進(jìn)制數(shù)字
先來(lái)看一下十進(jìn)制數(shù)字的表示法:

其中每個(gè)十進(jìn)制數(shù)
的取值范圍是0~9。這個(gè)表達(dá)描述的數(shù)值
的定義如下:

數(shù)字權(quán)的定義與十進(jìn)制小數(shù)點(diǎn)符號(hào)( ‘.’ ) 相關(guān),這意味著小數(shù)點(diǎn)左邊的數(shù)字的權(quán)是10的正冪,得到整數(shù)值,而小數(shù)點(diǎn)右邊的數(shù)字的權(quán)是10的負(fù)冪,得到小數(shù)值。例如十進(jìn)制數(shù)12. 34表示數(shù)字

1.2 二進(jìn)制小數(shù)的表示方法
類(lèi)比十進(jìn)制數(shù),二進(jìn)制表示小數(shù)可以用如下表示法表示,

以圖片表示為:

其中
的取值范圍是0和1。這個(gè)表達(dá)描述的數(shù)值
的定義如下:

符號(hào) ‘ . ’ 現(xiàn)在變?yōu)榱硕M(jìn)制的點(diǎn),點(diǎn)左邊的位的權(quán)是2的正冪,點(diǎn)右邊的位的權(quán)是2的負(fù)冪。例如,二進(jìn)制數(shù)101.11表示數(shù)字

2. IEEE浮點(diǎn)表示
2.1 IEEE浮點(diǎn)標(biāo)準(zhǔn)
上一節(jié)提到的定點(diǎn)表示法并不能很有效地表示非常大的數(shù)字。IEEE(電氣和電子工程師協(xié)會(huì))浮點(diǎn)標(biāo)準(zhǔn)用如下的形式來(lái)表示一個(gè)數(shù):

在這個(gè)式子中設(shè)計(jì)三個(gè)變量,s, M以及E
符號(hào) (sign) : s決定這數(shù)是負(fù)數(shù) (s = 1) 還是正數(shù) (s = 0)尾數(shù) (ignificand) : M是一個(gè)二進(jìn)制小數(shù)階碼 (exponent): E的作用是對(duì)浮點(diǎn)數(shù)加權(quán),這個(gè)權(quán)重是2的E次冪(可能是負(fù)數(shù))
例如:
5.0 化為二進(jìn)制小數(shù)
,由于 5.0 為正數(shù),因此 s = 0,M = 1.01,E = 2

這種標(biāo)準(zhǔn)將浮點(diǎn)數(shù)封裝成一種似乎很難理解的形式來(lái)存儲(chǔ),但其實(shí)是相當(dāng)優(yōu)雅的。
2.2 單精度和雙精度浮點(diǎn)數(shù)的封裝形式
在C語(yǔ)言中,浮點(diǎn)數(shù)分為單精度浮點(diǎn)數(shù) float 和雙精度浮點(diǎn)數(shù) double,其中float在內(nèi)存中占4個(gè)字節(jié),32個(gè)比特位;double在內(nèi)存中占8個(gè)字節(jié),64個(gè)比特位。

不管是 float 還是 double ,它們都被分為三個(gè)字段,分別用來(lái)表示符號(hào)位s,階碼字段exp和編碼尾數(shù)frac。這三個(gè)字段與上述的符號(hào)s,尾數(shù)M,階碼E一一對(duì)應(yīng),但并非將s,M,E直接存入內(nèi)存,而是根據(jù)浮點(diǎn)數(shù)的不同數(shù)值類(lèi)型按照不同的規(guī)則進(jìn)行編碼。
2.3 浮點(diǎn)數(shù)的數(shù)值分類(lèi)
根據(jù)階碼的不同,浮點(diǎn)數(shù)的數(shù)值可以分為三類(lèi):
- 規(guī)格化的值 (Normalized Values)
- 非規(guī)格化的值 (Denormalized Values)
- 特殊值 (Special Values)
exp的值決定了這個(gè)數(shù)屬于上面類(lèi)型中的哪一種,以float類(lèi)型為例:
2.3.1 規(guī)格化的值 (Normalized Values)
當(dāng)階碼域不全為0并且不全為1時(shí),表示該數(shù)值為規(guī)格化的值

當(dāng)階碼字段不全為0時(shí),表示該數(shù)值為非規(guī)格化的值。這是一種最普遍的情況,大多數(shù)浮點(diǎn)數(shù)都屬于這類(lèi)。比如上一小節(jié)的5.0
對(duì)于規(guī)格化的值,在得到s, E, M之后還需要進(jìn)行一些處理才能放進(jìn)內(nèi)存:
規(guī)則1:
- 前面已經(jīng)提到,E是階碼并且可以表示負(fù)值,存放在exp字段,但是E在標(biāo)準(zhǔn)中為無(wú)符號(hào)數(shù),這說(shuō)明它不能表示負(fù)數(shù)且可表示的范圍為0~255。那么對(duì)于E為負(fù)值的情況如何處理呢?
- 這里引入偏置 (Bias) 的概念。
- IEEE 754規(guī)定,存入內(nèi)存時(shí)E的真實(shí)值必須再加上一個(gè)中間數(shù),對(duì)于8位的E,這個(gè)中間數(shù)是127;對(duì)于11位的E,這個(gè)中間數(shù)是1023。這里的127和1023就是Bias。也就是說(shuō),階碼的值是 E= exp - Bias
- 例如:2^10的E是10,所以保存成32位浮點(diǎn)數(shù)時(shí),必須保存成10+127=137,即10001001。此時(shí)內(nèi)存中的exp中存的是10001001。
規(guī)則2
- 同樣的,M存放在小數(shù)字段 f 中,我們知道,當(dāng)一個(gè)小數(shù)化為二進(jìn)制小數(shù)后所得到的M總是一個(gè)介于1~2之間的值,形式為1. xxxxx
- 因此在存數(shù)據(jù)時(shí),考慮省略小數(shù)點(diǎn)前面的1(取數(shù)據(jù)的時(shí)候可以直接在前面添上),可以節(jié)省一位的存儲(chǔ)空間,能讓小數(shù)點(diǎn)后的數(shù)據(jù)多保存一位,提高數(shù)據(jù)的精度。
- 例如,M=1.01101,存到 f 中去的數(shù)據(jù)為01101,M = 1 + f
到這里我們可以解決 5.0 這樣一個(gè)規(guī)格化的值的存放問(wèn)題了:
我們知道,對(duì)于5.0來(lái)說(shuō),s = 0,M = 1.01,E = 2,
根據(jù)以上規(guī)則,在內(nèi)存中,符號(hào)位字段s = 0,階碼字段exp = E + 127 = 129 = 10000001,編碼尾數(shù)f = 01000000000000000000000(不夠23位要在后面補(bǔ)0)
結(jié)合來(lái)看:
0 10000001 01000000000000000000000,化為十六進(jìn)制為40 a0 00 00
在小端機(jī)器上的結(jié)果為:

2.3.2 非規(guī)格化的值 (Denormalized Values)
當(dāng)階碼域?yàn)槿?時(shí), 所表示的數(shù)是非規(guī)格化的值

在這種情況下,規(guī)則又有所不同:
規(guī)則1
- 當(dāng)exp為全0時(shí),此時(shí)的E = 1- Bias,也就是說(shuō)1-127(或者1-1023)即為真實(shí)值
- 補(bǔ)充:使階碼值為 1-Bias 仍而不是簡(jiǎn)單的 -Bias 似乎是違反直覺(jué)的。在后面我們會(huì)知道,這種方式提供了一種從非規(guī)格化值平滑轉(zhuǎn)換到規(guī)格化值的方法
規(guī)則2
- 有效數(shù)字M不再加上第一位的1,而是還原為0.xxxxxx的小數(shù),即M = f ,也就是小數(shù)字段的值, 不包含隱含的開(kāi)頭的1
- 實(shí)際上,如M = 1.01101,在exp = 00000000時(shí),存進(jìn)去的是101101而不是01101
非規(guī)格化數(shù)有兩個(gè)用途:
- 首先,它們提供了一種表示數(shù)值0 的方法,因?yàn)槭褂靡?guī)格化數(shù),我們必須總是M>= 1,因此我們就不能表示0.0
- 其次,非規(guī)格化數(shù)的另外一個(gè)功能是表示那些非常接近于 0.0 的數(shù)。它們提供了一種屬性,稱(chēng)為逐漸溢出(gradualunder flow) ,其中,可能的數(shù)值分布均勻地接近于0.0
2.3.3 特殊值 (Special Values)
當(dāng)階碼域?yàn)槿?時(shí), 所表示的數(shù)是特殊值
特殊值可分為兩種:
1. 當(dāng)小數(shù)域全為0 時(shí),得到的值表示無(wú)窮,當(dāng) s = 0 時(shí),是+∞, 或者當(dāng) s = 1時(shí),是 -∞

2.當(dāng)小數(shù)域?yàn)榉橇銜r(shí), 結(jié)果值被稱(chēng)為 “NaN”(Not a Number)。一些運(yùn)算的結(jié)果不能是實(shí)數(shù)或無(wú)窮, 就會(huì)返回這樣的NaN值

3. 數(shù)字示例
為了更加直觀(guān)地理解,我們用一個(gè)8位浮點(diǎn)數(shù)的例子,假定符號(hào)位 s 的長(zhǎng)度為1,階碼字段的長(zhǎng)度為4,小數(shù)字段的長(zhǎng)度為3:
對(duì)于非規(guī)格數(shù):

對(duì)于規(guī)格數(shù)

可以看到,從最大非規(guī)格數(shù)0 0000 111到最小規(guī)格數(shù)0 0001 000這樣的過(guò)渡是很自然的,體現(xiàn)出了上述IEEE標(biāo)準(zhǔn)的邏輯性與和諧的美感
4. 舍入
因?yàn)楸硎痉椒ㄏ拗屏烁↑c(diǎn)數(shù)的范圍和精度, 所以浮點(diǎn)運(yùn)算只能近似地表示實(shí)數(shù)運(yùn)算
我們企圖找到一個(gè)與值
最相近的值匹配值
來(lái)作為儲(chǔ)存的值
例如:
如果由于表示方法的限制,1.5這樣一個(gè)值無(wú)法完全放在內(nèi)存中,需要舍掉小數(shù)點(diǎn)后的值,那么舍入結(jié)果是1還是2呢?
IEEE定義了四種舍入方式:
- 向偶數(shù)舍入
- 向零舍入
- 向上舍入
- 向下舍入
其中,向偶數(shù)舍入(round - to - even)又被稱(chēng)為向最接近的值舍入(round - to - nearest),是默認(rèn)的方式,試圖找到一個(gè)最接近的匹配值
向上和向下舍入很好理解,一個(gè)介于1~2之間的數(shù)如 1.5 向上舍入是2,向下舍入是1
向零舍入是指在在數(shù)軸上的數(shù)向 0 的方向進(jìn)行舍入,比如 1.50 向零舍入會(huì)找到 1 和 2 之間更靠近0 的數(shù) 1 ,-1.50 向零舍入會(huì)找到 -1 和 -2 之間更靠近 0 的數(shù) -1
向偶數(shù)舍入,指當(dāng)一個(gè)數(shù)是兩個(gè)可能結(jié)果的中間數(shù)時(shí),它將數(shù)字向上或者向下舍入,使得結(jié)果的最低有效數(shù)字是偶數(shù)
比如1.50可以向1舍入,也可以向2舍入,并且正好是1和2的中間值,這時(shí)會(huì)默認(rèn)向2(偶數(shù))舍入;比如2.50可以向2舍入,也可以向3舍入,并且正好是2和3的中間值,這時(shí)同樣會(huì)向2(偶數(shù))舍入。
值得注意的是:
向偶數(shù)舍入只針對(duì)那些“中間值”。當(dāng)值為1.49或1.51時(shí),依舊舍入為 1 和 2
| 方式 | 1.40 | 1.60 | 1.50 | 2.50 | -1.50 |
| 向偶數(shù)舍入 | 1 | 2 | 2 | 2 | -2 |
| 向零舍入 | 1 | 1 | 1 | 2 | -1 |
| 向上舍入 | 1 | 1 | 1 | 2 | -2 |
| 向下舍入 | 2 | 2 | 2 | 3 | -1 |
為什么要使用向偶數(shù)舍入呢?
使用其他三種舍入方法,在一組數(shù)據(jù)中很容易引入平均值的統(tǒng)計(jì)偏差 ,當(dāng)1.50 1.60 1.70這樣一組數(shù)據(jù)都使用向上舍入,結(jié)果是2 2 2,平均值會(huì)偏大
向偶數(shù)舍入在大多數(shù)現(xiàn)實(shí)情況中避免了這種統(tǒng)計(jì)偏差。在50%的時(shí)間里,它將向上舍入,而在50%的時(shí)間里,它將向下舍入
對(duì)二進(jìn)制的浮點(diǎn)數(shù)舍入同樣遵循向偶數(shù)舍入的原則,并將 0 視為偶數(shù),1 視為奇數(shù)
例如:
10.11100 ,當(dāng)舍入需要精確到小數(shù)點(diǎn)后兩位時(shí), 后三位100代表
,正好是中間值,因此向偶數(shù)舍入為11.00
5. 浮點(diǎn)運(yùn)算
考慮下面幾個(gè)式子:
- (1) (3.14 + 1e10) - 1e10 = 0.0
- (2) 3.14 + (1e10 - 1e10) = 3.14
- (3) (1e20 * 1e20) * 1e-20 = +∞
- (4) 1e20 * (1e20 * 1e-20) = 1e20
- (5) 1e20 * (1e20 - 1e20) = 0.0
- (6) 1e20*1e20 - 1e20*1e20 = NaN
- (1)(2)中,3.14 + 1e10對(duì)結(jié)果進(jìn)行了舍入,值3.14會(huì)丟失,因此對(duì)于浮點(diǎn)數(shù)的加法不具有結(jié)合性
- (3)(4)中,由于計(jì)算結(jié)果可能溢出或舍入,因此浮點(diǎn)數(shù)的乘法也不具有結(jié)合性
- (5)(6)中,在單精度浮點(diǎn)數(shù)時(shí)結(jié)果不同,說(shuō)明浮點(diǎn)數(shù)乘法不具有分配性
6. C語(yǔ)言中的浮點(diǎn)數(shù)
所有的C語(yǔ)言版本提供了兩種不同的浮點(diǎn)數(shù)據(jù)類(lèi)型: float 和 double
當(dāng)int ,float,double不同數(shù)據(jù)類(lèi)型之間進(jìn)行強(qiáng)制類(lèi)型轉(zhuǎn)換時(shí),得到的結(jié)果可能會(huì)超出我們的預(yù)期,程序改變數(shù)值和位模式的原則如下( 假設(shè)int是32位的) :
- 從 int 轉(zhuǎn)換成 float,數(shù)字不會(huì)溢出,但是可能被舍入
- 從 int 或 float 轉(zhuǎn)換成 double,因?yàn)閐ouble有更高的精度,所以能夠保留精確的數(shù)值
- 從 double 轉(zhuǎn)換成 float ,因?yàn)榉秶∫恍?,所以值可能溢出?+∞ 或 -∞。另外,由于精確度較小,它還可能被舍入
- 從 float 或者 double 轉(zhuǎn)換成int,值將會(huì)向零舍入。例如,1.999將被轉(zhuǎn)換成1,而-1.999將被轉(zhuǎn)換成 -1。進(jìn)一步來(lái)說(shuō),值可能會(huì)溢出
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
關(guān)于"引用"的幾點(diǎn)說(shuō)明介紹
引用聲明完畢后,相當(dāng)于目標(biāo)變量名有兩個(gè)名稱(chēng),即該目標(biāo)原名稱(chēng)和引用名,且不能再把該引用名作為其他變量名的別名2013-09-09
C++ 使用CRC32檢測(cè)內(nèi)存映像完整性的實(shí)現(xiàn)步驟
當(dāng)我們使用動(dòng)態(tài)補(bǔ)丁的時(shí)候,那么內(nèi)存中同樣不存在校驗(yàn)效果,也就無(wú)法抵御對(duì)方動(dòng)態(tài)修改機(jī)器碼了,為了防止解密者直接對(duì)內(nèi)存打補(bǔ)丁,我們需要在硬盤(pán)校驗(yàn)的基礎(chǔ)上,增加內(nèi)存校驗(yàn),防止動(dòng)態(tài)補(bǔ)丁的運(yùn)用。2021-06-06
C++?超詳細(xì)分析多態(tài)的原理與實(shí)現(xiàn)
這篇文章主要介紹了C++多態(tài)的原理與實(shí)現(xiàn),多態(tài)是一種面向?qū)ο蟮脑O(shè)計(jì)思路,本身和C++不是強(qiáng)綁定的,其他語(yǔ)言當(dāng)中一樣有多態(tài),只不過(guò)實(shí)現(xiàn)的方式可能有所不同。下面來(lái)一起了解更多詳細(xì)內(nèi)容吧2022-03-03
C語(yǔ)言實(shí)現(xiàn)用?*?打印X形圖案
這篇文章主要介紹了C語(yǔ)言實(shí)現(xiàn)用?*?打印X形圖案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11
關(guān)于Qt?C++中connect的幾種寫(xiě)法代碼示例
這篇文章介紹了Qt中connect函數(shù)的不同編寫(xiě)方式,包括傳統(tǒng)的槽函數(shù)寫(xiě)法、使用函數(shù)指針的寫(xiě)法、Lambda表達(dá)式以及使用QOverload選擇重載信號(hào)的寫(xiě)法,每種寫(xiě)法都有其特點(diǎn)和適用場(chǎng)景,程序員應(yīng)根據(jù)具體需求選擇最合適的方式,需要的朋友可以參考下2024-11-11
C語(yǔ)言Easyx實(shí)現(xiàn)貪吃蛇詳解
這篇文章主要為大家詳細(xì)介紹了基于easyx的C++實(shí)現(xiàn)貪吃蛇,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-10-10
C語(yǔ)言編程技巧 關(guān)于const和#define的區(qū)別心得
盡量用const和inline而不用#define 這個(gè)條款最好稱(chēng)為:“盡量用編譯器而不用預(yù)處理”,因?yàn)?define經(jīng)常被認(rèn)為好象不是語(yǔ)言本身的一部分。這是問(wèn)題之一。再看下面的語(yǔ)句:2013-02-02

