python 數字的四捨五入
python 數字的四捨五入
問題
你想對浮點數執行指定精度的舍入運算。
解決方案
對於簡單的舍入運算,使用內建的 round(value, ndigits) 函式即可。比如:
當一個值剛好在兩個邊界的中間的時候, round 函式返回離它最近的偶數。 也就是說,對1.5或者2.5的舍入運算都會得到2。
傳給 round() 函式的 ndigits 引數可以是負數,這種情況下, 舍入運算會作用在十位、百位、千位等上面。比如:
討論
不要將舍入和格式化輸出搞混淆了。 如果你的目的只是簡單的輸出一定寬度的數,你不需要使用 round() 函式。 而僅僅只需要在格式化的時候指定精度即可。比如:
同樣,不要試著去舍入浮點值來”修正”表面上看起來正確的問題。比如,你可能傾向於這樣做:
對於大多數使用到浮點的程式,沒有必要也不推薦這樣做。 儘管在計算的時候會有一點點小的誤差,但是這些小的誤差是能被理解與容忍的。 如果不能允許這樣的小誤差(比如涉及到金融領域),那麼就得考慮使用 decimal 模組了,下一節我們會詳細討論。
3.2 執行精確的浮點數運算
問題
你需要對浮點數執行精確的計算操作,並且不希望有任何小誤差的出現。
解決方案
浮點數的一個普遍問題是它們並不能精確的表示十進位制數。 並且,即使是最簡單的數學運算也會產生小的誤差,比如:
這些錯誤是由底層CPU和IEEE 754標準通過自己的浮點單位去執行算術時的特徵。 由於Python的浮點資料型別使用底層表示儲存資料,因此你沒辦法去避免這樣的誤差。
如果你想更加精確(並能容忍一定的效能損耗),你可以使用 decimal 模組:
初看起來,上面的程式碼好像有點奇怪,比如我們用字串來表示數字。 然而, Decimal 物件會像普通浮點數一樣的工作(支援所有的常用數學運算)。 如果你列印它們或者在字串格式化函式中使用它們,看起來跟普通數字沒什麼兩樣。
decimal 模組的一個主要特徵是允許你控制計算的每一方面,包括數字位數和四捨五入運算。 為了這樣做,你先得建立一個本地上下文並更改它的設定,比如:
討論
decimal 模組實現了IBM的”通用小數運算規範”。不用說,有很多的配置選項這本書沒有提到。
Python新手會傾向於使用 decimal 模組來處理浮點數的精確運算。 然而,先理解你的應用程式目的是非常重要的。 如果你是在做科學計算或工程領域的計算、電腦繪圖,或者是科學領域的大多數運算, 那麼使用普通的浮點型別是比較普遍的做法。 其中一個原因是,在真實世界中很少會要求精確到普通浮點數能提供的17位精度。 因此,計算過程中的那麼一點點的誤差是被允許的。 第二點就是,原生的浮點數計算要快的多-有時候你在執行大量運算的時候速度也是非常重要的。
即便如此,你卻不能完全忽略誤差。數學家花了大量時間去研究各類演算法,有些處理誤差會比其他方法更好。 你也得注意下減法刪除以及大數和小數的加分運算所帶來的影響。比如
上面的錯誤可以利用 math.fsum() 所提供的更精確計算能力來解決:
然而,對於其他的演算法,你應該仔細研究它並理解它的誤差產生來源。
總的來說, decimal 模組主要用在涉及到金融的領域。 在這類程式中,哪怕是一點小小的誤差在計算過程中蔓延都是不允許的。 因此, decimal 模組為解決這類問題提供了方法。 當Python和資料庫打交道的時候也通常會遇到 Decimal 物件,並且,通常也是在處理金融資料的時候。
3.3 數字的格式化輸出
問題
你需要將數字格式化後輸出,並控制數字的位數、對齊、千位分隔符和其他的細節。
解決方案
格式化輸出單個數字的時候,可以使用內建的 format() 函式,比如:
如果你想使用指數記法,將f改成e或者E(取決於指數輸出的大小寫形式)。比如:
同時指定寬度和精度的一般形式是 ‘[<>^]?width[,]?(.digits)?’ , 其中 width 和 digits 為整數,?代表可選部分。 同樣的格式也被用在字串的 format() 方法中。比如:
討論
數字格式化輸出通常是比較簡單的。上面演示的技術同時適用於浮點數和 decimal 模組中的 Decimal 數字物件。
當指定數字的位數後,結果值會根據 round() 函式同樣的規則進行四捨五入後返回。比如:
包含千位符的格式化跟本地化沒有關係。 如果你需要根據地區來顯示千位符,你需要自己去調查下 locale 模組中的函數了。 你同樣也可以使用字串的 translate() 方法來交換千位符。比如:
在很多Python程式碼中會看到使用%來格式化數字的,比如:
這種格式化方法也是可行的,不過比更加先進的 format() 要差一點。 比如,在使用%操作符格式化數字的時候,一些特性(新增千位符)並不能被支援。
3.4 二八十六進位制整數
問題
你需要轉換或者輸出使用二進位制,八進位制或十六進位制表示的整數。
解決方案
為了將整數轉換為二進位制、八進位制或十六進位制的文字串, 可以分別使用 bin() , oct() 或 hex() 函式:
另外,如果你不想輸出 0b , 0o 或者 0x 的字首的話,可以使用 format() 函式。比如:
整數是有符號的,所以如果你在處理負數的話,輸出結果會包含一個負號。比如:
如果你想產生一個無符號值,你需要增加一個指示最大位長度的值。比如為了顯示32位的值,可以像下面這樣寫:
為了以不同的進位制轉換整數字符串,簡單的使用帶有進位制的 int() 函式即可:
討論
大多數情況下處理二進位制、八進位制和十六進位制整數是很簡單的。 只要記住這些轉換屬於整數和其對應的文字表示之間的轉換即可。永遠只有一種整數型別。
最後,使用八進位制的程式設計師有一點需要注意下。 Python指定八進位制數的語法跟其他語言稍有不同。比如,如果你像下面這樣指定八進位制,會出現語法錯誤:
3.5 位元組到大整數的打包與解包
問題
你有一個位元組字串並想將它解壓成一個整數。或者,你需要將一個大整數轉換為一個位元組字串。
解決方案
假設你的程式需要處理一個擁有128位長的16個元素的位元組字串。比如:
data = b’\x00\x124V\x00x\x90\xab\x00\xcd\xef\x01\x00#\x004’
為了將bytes解析為整數,使用 int.from_bytes() 方法,並像下面這樣指定位元組順序:
為了將一個大整數轉換為一個位元組字串,使用 int.to_bytes() 方法,並像下面這樣指定位元組數和位元組順序:
討論
大整數和位元組字串之間的轉換操作並不常見。 然而,在一些應用領域有時候也會出現,比如密碼學或者網路。 例如,IPv6網路地址使用一個128位的整數表示。 如果你要從一個數據記錄中提取這樣的值的時候,你就會面對這樣的問題。
作為一種替代方案,你可能想使用6.11小節中所介紹的 struct 模組來解壓位元組。 這樣也行得通,不過利用 struct 模組來解壓對於整數的大小是有限制的。 因此,你可能想解壓多個位元組串並將結果合併為最終的結果,就像下面這樣:
位元組順序規則(little或big)僅僅指定了構建整數時的位元組的低位高位排列方式。 我們從下面精心構造的16進位制數的表示中可以很容易的看出來:
如果你試著將一個整數打包為位元組字串,那麼它就不合適了,你會得到一個錯誤。 如果需要的話,你可以使用 int.bit_length() 方法來決定需要多少位元組位來儲存這個值。