引言,
默認(rèn)float 類型儲(chǔ)存雙精度(double) 浮點(diǎn)數(shù),可表達(dá)16到17個(gè)小數(shù)點(diǎn).
從實(shí)現(xiàn)方式看,浮點(diǎn)數(shù)以二進(jìn)制儲(chǔ)存十進(jìn)制的近似值.這可能導(dǎo)致執(zhí)行結(jié)果和編碼的預(yù)期效果不符合,造成一定量的缺陷,所以對(duì)精度有嚴(yán)格要求的場(chǎng)合,應(yīng)該選擇固定精度類型.
1.關(guān)于精度問題
一般可以通過float.hex 方法輸入實(shí)際儲(chǔ)存值的十六進(jìn)制格式字符串,以檢查執(zhí)行結(jié)果為什么不同.
還可以使用該方式實(shí)現(xiàn)浮點(diǎn)值的精確傳遞,避免精度丟失
- bin() 轉(zhuǎn)二進(jìn)制
- int() 轉(zhuǎn)10進(jìn)制
- oct() 轉(zhuǎn)8進(jìn)制
- hex() 轉(zhuǎn)16進(jìn)制
In [1]: 0.1 * 3
Out[1]: 0.30000000000000004
In [2]: (0.1 * 3).hex() # 顯然兩個(gè)儲(chǔ)存的不同
Out[2]: '0x1.3333333333334p-2'
In [3]: (0.3).hex() # 轉(zhuǎn)換為16進(jìn)制
Out[3]: '0x1.3333333333333p-2'
In [4]: s = (1/3).hex()
In [5]: s
Out[5]: '0x1.5555555555555p-2'
In [6]: float.fromhex(s) # 返回浮點(diǎn)數(shù)
Out[6]: 0.3333333333333333
對(duì)于簡(jiǎn)單的比較操作,可以嘗試將浮點(diǎn)數(shù)限制在有效的固定精度內(nèi),但是考慮到round算法實(shí)現(xiàn)問題,更準(zhǔn)確的做法是使用decimal.Decimal類型
In [13]: round(0.1 * 3, 2) == round(0.3, 2) # 避免不確定行,左右都是使用了固定精度
Out[13]: True
In [14]: round(0.1, 2) * 3 == round(0.3, 2) # 將round 返回值作為操作數(shù),導(dǎo)致精度再度丟失
Out[14]: False
不同類型的數(shù)字之間,可以直接進(jìn)行加減和比較運(yùn)算的.
In [15]: 1.1 + 2
Out[15]: 3.1
In [16]: 1.1 < 2
Out[16]: True
In [17]: 1.1 == 1.100
Out[17]: True
2.轉(zhuǎn)換
將整數(shù)或者字符串轉(zhuǎn)換為浮點(diǎn)數(shù)很簡(jiǎn)單,且能自動(dòng)處理字符串內(nèi)的符號(hào)以及空白符問題,只超過有效精確度的時(shí)候,結(jié)果和字符串內(nèi)容存在差異.
In [18]: float(100) # 正常
Out[18]: 100.0
In [19]: float("-100.123") #符號(hào)
Out[19]: -100.123
In [20]: float(" \t 100.213123 \n") # 空白符
Out[20]: 100.213123
In [21]: float("1.1234e2") # 科學(xué)計(jì)算法
Out[21]: 112.34
差異部分
In [22]: float("0.1234567890123456789")
Out[22]: 0.12345678901234568 # 顯示不完全
返回來,將浮點(diǎn)數(shù)轉(zhuǎn)換為整數(shù)的時(shí)候,有多重不同的方案可以供我們選擇.可以直接減掉小數(shù)部分,或者分別往大小兩個(gè)方向取整數(shù).
In [23]: int (2.6)
Out[23]: 2
In [24]: from math import trunc,floor,ceil
In [25]: trunc(3.2),trunc(-3.2) # 截?cái)嘈?shù)部分
Out[25]: (3, -3)
In [26]: floor(3.2),floor(-3.2) # 向小數(shù)方向取最近整數(shù)
Out[26]: (3, -4)
In [27]: ceil(3.2),ceil(-3.2) # 往大數(shù)字方向取整數(shù)
Out[27]: (4, -3)
3.十進(jìn)制浮點(diǎn)數(shù)
與float 基于硬件的二進(jìn)制浮點(diǎn)數(shù)類型相比,decimal.Decimal 是十進(jìn)制實(shí)現(xiàn),最高可以提供28位有效精度,其準(zhǔn)確表達(dá)十進(jìn)制數(shù)和運(yùn)算,不存在二進(jìn)制近似值的問題.
In [1]: 1.1+2.2
Out[1]: 3.3000000000000003 # 結(jié)果只是與3.3相似
In [2]: (0.1 + 0.1 + 0.1 - 0.3) == 0 # 與預(yù)期結(jié)果不符
Out[2]: False
In [3]: from decimal import Decimal
# 使用Decimal之后
In [4]: Decimal("1.1") + Decimal("2.2")
Out[4]: Decimal('3.3')
In [5]: Decimal("0.1") + Decimal("0.1") + Decimal("0.1") - Decimal("0.3") == 0
Out[5]: True
在創(chuàng)建Decimal 實(shí)例時(shí),應(yīng)該傳入一個(gè)準(zhǔn)確數(shù)值,比如整數(shù)或者字符串等,如果是float類型的話,那么再構(gòu)建之前,其中精度就已經(jīng)丟失了.
In [6]: Decimal(0.1)
Out[6]: Decimal('0.1000000000000000055511151231257827021181583404541015625') # 精度已經(jīng)丟失
In [7]: Decimal("0.1") # 只有在傳入字符串或者整數(shù)的時(shí)候,精度才不會(huì)丟失.
Out[7]: Decimal('0.1')
需要的時(shí)候,可以通過上下文修改Decimal默認(rèn)的28位精度
In [8]: from decimal import Decimal,getcontext
In [9]: getcontext()
Out[9]: Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[FloatOperation], traps=[InvalidOperation, DivisionByZero, Overflow])
In [10]: getcontext().prec = 2
In [11]: Decimal(1) / Decimal(7)
Out[11]: Decimal('0.14')
In [12]: Decimal(1) / Decimal(3)
Out[12]: Decimal('0.33')
更高階一點(diǎn)的 用localcontext 限制一定的區(qū)域中的精度
In [14]: from decimal import localcontext
In [15]: with localcontext() as ctx:
...: ctx.prec = 2
...: print(getcontext().prec)
...: print(Decimal(1) / Decimal(7))
...:
2
0.14
除非有特別的需求,不然不要使用Decimal代替float,要知道其運(yùn)算速度也會(huì)慢很多
4.奇舍偶入(并不是)
同樣因?yàn)榻浦岛途葐栴},造成float運(yùn)行'四舍五入' (round) 的時(shí)候操作存在不確定性,其結(jié)果會(huì)導(dǎo)致一些不易察覺的陷阱
In [18]: round(0.3)
Out[18]: 0
In [19]: round(0.5) # 這里應(yīng)該是1才是正確的
Out[19]: 0
In [20]: round(1.5)
Out[20]: 2
按照round算法規(guī)則,按照林近數(shù)字距離遠(yuǎn)近來考慮是否進(jìn)位,因此,四舍六入就是確定的,相關(guān)問題都一種在兩邊都是5的時(shí)候是否進(jìn)位
按照以0.4為例子,其舍入后的相鄰數(shù)字是0和1,從距離上看0自然是距離0.4更近一些
對(duì)于5 還要考慮后面是否還有小數(shù)位,如果有,那么左右距離就不可能是相等的,這自然需要進(jìn)位
In [21]: round(0.5) # 與0,1距離相等,暫時(shí)不確定
Out[21]: 0
In [22]: round(0.5000001) # 哪怕是0.5后面的小數(shù)部分再小,那么他也是接近1的
Out[22]: 1
In [23]: round(1.25,1)
Out[23]: 1.2
In [24]: round(1.2500000001,1)
Out[24]: 1.3
剩下的,要看返回整數(shù)還是浮點(diǎn)數(shù).如果是整數(shù),就去鄰近的偶數(shù).
In [25]: round(0.5) # 0 --> 0.5 --> 1
Out[25]: 0
In [26]: round(1.5) # 1 --> 1.5 --> 2
Out[26]: 2
In [27]: round(2.5) # 2 --> 2.5 --> 3
Out[27]: 2
不同版本,規(guī)則存在差異,比如2.7中,round(2.5)返回值是3.0
從這點(diǎn)來看,我們應(yīng)該謹(jǐn)慎對(duì)待此類行為差異,并且嚴(yán)格測(cè)試其造成的影響
如果依舊返回浮點(diǎn)數(shù),事情就變得有點(diǎn)莫名其妙了,有些文章宣城 "奇舍偶入",或者"五成雙",也就是看5前一位小數(shù)的奇偶性來判斷是否進(jìn)位,但是事情并非如此
In [28]: round(1.25,1) # 偶舍
Out[28]: 1.2
In [29]: round(1.245,2) # 偶入
Out[29]: 1.25
In [30]: round(2.675,2) # 奇 舍
Out[30]: 2.67
In [31]: round(2.375,2) # 奇 入
Out[31]: 2.38
對(duì)此官方文檔宣城這并不是錯(cuò)誤,而是屬于事出有因,對(duì)此我們可以改用Decimal,按照需求選取可控的進(jìn)位方案.
In [35]: from decimal import Decimal,ROUND_HALF_UP
In [35]: def roundx(x,n):
...: return Decimal(x).quantize(Decimal(n), ROUND_HALF_UP) # 嚴(yán)格按照四舍五入進(jìn)行
In [36]: roundx("1.24",".1")
Out[36]: Decimal('1.2')
In [37]: roundx("1.26",".1")
Out[37]: Decimal('1.3')
In [38]: roundx("1.245",".1")
Out[38]: Decimal('1.2')
In [39]: roundx("1.675",".1")
Out[39]: Decimal('1.7')
In [40]: roundx("1.375",".1")
Out[40]: Decimal('1.4')
最后嘛 :
小編整理的python程序員資料裙echo'(895797751,歡迎自取)'