淺析Golang中float64的精度問(wèn)題
現(xiàn)象
func main() { x := uint64(1) for i := 0; i < 53; i++ { x = x * 2 } fmt.Println("2^53 =", x) xStr := strconv.FormatUint(x, 10) fmt.Println("len(2^53) =", len(xStr)) xAdded1 := x + 1 fmt.Println("2^53 + 1 =", xAdded1) // 9007199254740993 fmt.Println("float64(2^53 + 1) =", float64(xAdded1)) // 9.007199254740992e+15,出現(xiàn)了精度問(wèn)題 xAdded2 := x + 2 fmt.Println("2^53 + 2 =", xAdded2) // 9007199254740994 fmt.Println("float64(2^53 + 2) =", float64(xAdded2)) // 9.007199254740994e+15,沒(méi)有出現(xiàn)精度問(wèn)題 fmt.Println("math.MaxInt64 =", int64(math.MaxInt64)) fmt.Println("float64(math.MaxInt64) =", float64(math.MaxInt64)) // 精度問(wèn)題 fmt.Println("math.MaxUint64 =", uint64(math.MaxUint64)) }
運(yùn)行結(jié)果:
2^53 = 9007199254740992
len(2^53) = 16
2^53 + 1 = 9007199254740993
float64(2^53 + 1) = 9.007199254740992e+15
2^53 + 2 = 9007199254740994
float64(2^53 + 2) = 9.007199254740994e+15
math.MaxInt64 = 9223372036854775807
float64(math.MaxInt64) = 9.223372036854776e+18
math.MaxUint64 = 18446744073709551615
分析
可以看到float64
無(wú)法精確存儲(chǔ)2^53 + 1
,但能精確存儲(chǔ)2^53 + 2
,為什么?
首先,float64
的尾數(shù)位有52位,尾數(shù)的最大長(zhǎng)度只能是52+1=53
位,尾數(shù)長(zhǎng)度超過(guò)53就無(wú)法精確存儲(chǔ),會(huì)存在精度問(wèn)題。
無(wú)法精確存儲(chǔ)2^53+1
:
2^53+1
的二進(jìn)制是10000000....0001(中間有52個(gè)0)
,根據(jù)IEEE標(biāo)準(zhǔn)則是(-1)^0 * 1.000000000...01(中間有52個(gè)0) * 2^53
,尾數(shù)長(zhǎng)度是54,超過(guò)了53,因此float64
無(wú)法存儲(chǔ)第54位,只能舍去最后的1,所以存在精度問(wèn)題。
能精確存儲(chǔ)2^53+2
:
2^53+2
的二進(jìn)制是1000000...010(中間51個(gè)0)
,根據(jù)IEEE標(biāo)準(zhǔn)則是(-1)^0 * 1.00000000...01(中間是51個(gè)0)* 2^53
,尾數(shù)長(zhǎng)度是53,float64
的尾數(shù)位可以精確存儲(chǔ),因此沒(méi)有精度問(wèn)題。
知識(shí)補(bǔ)充
計(jì)算機(jī)的浮點(diǎn)數(shù)表示
科學(xué)計(jì)數(shù)法和IEEE標(biāo)準(zhǔn)
在計(jì)算機(jī)中,是用二進(jìn)制的科學(xué)計(jì)數(shù)法來(lái)表示和存儲(chǔ)浮點(diǎn)數(shù)的。因?yàn)榭茖W(xué)計(jì)數(shù)法可以唯一地表示任何一個(gè)數(shù),且所占用的存儲(chǔ)空間會(huì)更少。
比如:對(duì)于一個(gè)二進(jìn)制數(shù)100000...000(共127個(gè)0)
,如果不用科學(xué)計(jì)數(shù)法,需要16個(gè)字節(jié)來(lái)存儲(chǔ)。如果用科學(xué)計(jì)數(shù)法:1*2^127,只需要用二進(jìn)制表示出:有效數(shù)字和指數(shù)即可,壓根不需要16個(gè)字節(jié)。
IEEE浮點(diǎn)標(biāo)準(zhǔn)用V=(-1)^s * M * 2^E
的形式來(lái)表示一個(gè)數(shù):
- 符號(hào):s決定該數(shù)是正數(shù)還是負(fù)數(shù)(0正1負(fù)),而對(duì)于數(shù)值0的符號(hào)位解釋作為特殊情況處理。
- 尾數(shù)(相當(dāng)于有效數(shù)字):M是一個(gè)二進(jìn)制小數(shù),對(duì)于規(guī)格化表示:1<=M<2
- 階碼(相當(dāng)于指數(shù)):階碼E決定了二進(jìn)制小數(shù)的小數(shù)點(diǎn)位置。(可為負(fù)數(shù))
將一個(gè)浮點(diǎn)數(shù)的表示轉(zhuǎn)成如上形式,然后分別對(duì)符號(hào)、尾數(shù)和階碼進(jìn)行編碼就能得到浮點(diǎn)數(shù)的機(jī)器表示。
如下,IEEE標(biāo)準(zhǔn)規(guī)定:
- 對(duì)于單精度浮點(diǎn)數(shù),1位符號(hào)位+8位階碼位+23位尾數(shù)位。
- 對(duì)于雙精度浮點(diǎn)數(shù),1位符號(hào)位+11位階碼位+52位尾數(shù)位。
IEEE標(biāo)準(zhǔn)規(guī)定:階碼位表示的是無(wú)符號(hào)數(shù)e,階碼E
和無(wú)符號(hào)數(shù)e
的關(guān)系是:E = e - (2^(n-1) - 1)
。
比如,對(duì)于單精度浮點(diǎn)數(shù)(8位階碼位),無(wú)符號(hào)數(shù)e的范圍是[0, 255],因此E = e - (2^7 - 1) = e - 127
,所以階碼E的范圍是[-127, 128],即指數(shù)的范圍是[-127, 128]。
尾數(shù)的規(guī)格化表示: 尾數(shù)M必須1<=M<2
。
為什么要規(guī)格化?保證浮點(diǎn)數(shù)有唯一的表示。若不對(duì)浮點(diǎn)數(shù)的表示作出明確規(guī)定,同一個(gè)浮點(diǎn)數(shù)的表示就不是唯一的,比如對(duì)于十進(jìn)制數(shù)1.75表示可能有1.11*2^0、0.111*2^1、0.0111*2^2等。
規(guī)格化表示后,尾數(shù)一定是1.xxxx
,由于第一位一定是1,所以不需要顯式地表示它,因此尾數(shù)位全部用來(lái)表示尾數(shù)1.xxxx
之后的xxxx
部分,也就是說(shuō)【尾數(shù)的長(zhǎng)度(去除小數(shù)點(diǎn))】最多是【尾數(shù)位+1位】,尾數(shù)超過(guò)這個(gè)長(zhǎng)度之后的數(shù)字位都會(huì)被舍棄,從而出現(xiàn)精度問(wèn)題。
示例
將如下十進(jìn)制數(shù)轉(zhuǎn)成單精度浮點(diǎn)數(shù)表示:
- 1.5
- -12.5
對(duì)于1.5,其二進(jìn)制小數(shù)是1.1
,按照IEEE標(biāo)準(zhǔn)轉(zhuǎn)成V=(-1)^s * M * 2^E
的形式,即(-1)^0 * 1.1 * 2^0
,所以:s=0;M=1.1;E=0。然后分別用1位符號(hào)位、8位階碼位和23位尾數(shù)位進(jìn)行編碼:
- 因?yàn)槭钦龜?shù),所以單精度浮點(diǎn)數(shù)的1位符號(hào)位為
0
- 尾數(shù)是
1.1
,尾數(shù)位只需存儲(chǔ)1.1
之后的1
,因此單精度浮點(diǎn)數(shù)的23位尾數(shù)位就是10000000000000000000000
- 階碼E=0,則
e=E+127=127
,因此階碼位對(duì)應(yīng)的無(wú)符號(hào)數(shù)e是127
,所以單精度浮點(diǎn)數(shù)的8位階碼位是01111111
最終,二進(jìn)制表示為00111111110000000000000000000000
同理,對(duì)于-12.5,其二進(jìn)制小數(shù)是-1100.1
,即(-1)^1 * 1.1001 * 2^3
,所以:s=0;M=1.1001;E=3。然后分別用1位符號(hào)位、8位階碼位和23位尾數(shù)位進(jìn)行編碼:
- 因?yàn)槭秦?fù)數(shù),所以符號(hào)位是
1
- 尾數(shù)是
1.1001
,尾數(shù)位只需存儲(chǔ)1.1001
之后的1001
,所以尾數(shù)位是10010000000000000000000
- 階碼E=3,則
e=E+127=130
,因此階碼位對(duì)應(yīng)的無(wú)符號(hào)數(shù)e是130,則階碼位是10000010
最終,二進(jìn)制表示為11000001010010000000000000000000
精度問(wèn)題
單精度浮點(diǎn)數(shù)有23位尾數(shù)位,最多只能表示23+1=24
位長(zhǎng)度的尾數(shù);雙精度浮點(diǎn)數(shù)有52位尾數(shù)位,最多只能表示52+1=53
位長(zhǎng)度的尾數(shù)。 也就是說(shuō):
- 對(duì)于單精度浮點(diǎn)數(shù),若尾數(shù)的長(zhǎng)度超過(guò)24位,24位之后的數(shù)字就無(wú)法存儲(chǔ),會(huì)出現(xiàn)精度問(wèn)題
- 對(duì)于雙精度浮點(diǎn)數(shù),若尾數(shù)的長(zhǎng)度超過(guò)53位,53位之后的數(shù)字就無(wú)法存儲(chǔ),會(huì)出現(xiàn)精度問(wèn)題
到此這篇關(guān)于淺析Golang中float64的精度問(wèn)題的文章就介紹到這了,更多相關(guān)Go float64精度內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go語(yǔ)言使用templ實(shí)現(xiàn)編寫HTML用戶界面
templ是一個(gè)在 Go 中編寫 HTML 用戶界面的語(yǔ)言,使用 templ,我們可以創(chuàng)建可呈現(xiàn) HTML 片段的組件,下面就跟隨小編一起了解一下具體的實(shí)現(xiàn)方法吧2023-12-12使用go實(shí)現(xiàn)一個(gè)超級(jí)mini的消息隊(duì)列的示例代碼
本文主要介紹了使用go實(shí)現(xiàn)一個(gè)超級(jí)mini的消息隊(duì)列的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12在Visual Studio Code中配置GO開發(fā)環(huán)境的詳細(xì)教程
這篇文章主要介紹了在Visual Studio Code中配置GO開發(fā)環(huán)境的詳細(xì)教程,需要的朋友可以參考下2017-02-02詳解Golang如何優(yōu)雅判斷interface是否為nil
這篇文章主要為大家詳細(xì)介紹了Golang如何優(yōu)雅判斷interface是否為nil的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起了解下2024-01-01Golang 空map和未初始化map的注意事項(xiàng)說(shuō)明
這篇文章主要介紹了Golang 空map和未初始化map的注意事項(xiàng)說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-04-04完美解決golang go get私有倉(cāng)庫(kù)的問(wèn)題
這篇文章主要介紹了完美解決golang go get私有倉(cāng)庫(kù)的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-05-05