Python進(jìn)階之路(三)之float奇舍偶入?五成雙?

引言,

默認(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,歡迎自取)'
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末秸滴,一起剝皮案震驚了整個(gè)濱河市比搭,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌合是,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,843評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件尿背,死亡現(xiàn)場(chǎng)離奇詭異端仰,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)田藐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門荔烧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人汽久,你說我怎么就攤上這事鹤竭。” “怎么了景醇?”我有些...
    開封第一講書人閱讀 163,187評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵臀稚,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我三痰,道長(zhǎng)吧寺,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,264評(píng)論 1 292
  • 正文 為了忘掉前任散劫,我火速辦了婚禮稚机,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘获搏。我一直安慰自己赖条,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,289評(píng)論 6 390
  • 文/花漫 我一把揭開白布常熙。 她就那樣靜靜地躺著纬乍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪裸卫。 梳的紋絲不亂的頭發(fā)上仿贬,一...
    開封第一講書人閱讀 51,231評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音墓贿,去河邊找鬼诅蝶。 笑死,一個(gè)胖子當(dāng)著我的面吹牛募壕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播语盈,決...
    沈念sama閱讀 40,116評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼舱馅,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了刀荒?” 一聲冷哼從身側(cè)響起代嗤,我...
    開封第一講書人閱讀 38,945評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤棘钞,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后干毅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宜猜,經(jīng)...
    沈念sama閱讀 45,367評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,581評(píng)論 2 333
  • 正文 我和宋清朗相戀三年硝逢,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了姨拥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,754評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡渠鸽,死狀恐怖叫乌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情徽缚,我是刑警寧澤憨奸,帶...
    沈念sama閱讀 35,458評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站凿试,受9級(jí)特大地震影響排宰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜那婉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,068評(píng)論 3 327
  • 文/蒙蒙 一板甘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧吧恃,春花似錦虾啦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至呻率,卻和暖如春硬毕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背礼仗。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工吐咳, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人元践。 一個(gè)月前我還...
    沈念sama閱讀 47,797評(píng)論 2 369
  • 正文 我出身青樓韭脊,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親单旁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子沪羔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,654評(píng)論 2 354