最後更新日期:2021 年 01 月 3 日

簡介

一個 python 的 float 是二進位的浮點數(binary floation-point number),但是我們在生活中,實際中運用到的是十進位(decimal)的浮點數;兩者之前有一些不同的地方,如果有興趣,可以參考 IEEE754 及 IEEE854。

decimal 模組提供了一個可以控制的算術情境(arithmetic context)和相關的類別和方法,來幫助我們把電腦的小數點運算,和真實生活中的小數點運算結合起來。

內容

算術情境

在 decimal 模組中,可以控制的算術情境有:精確度(precision)、約整(rounding,或稱「捨入」)方式…等。

可以在 python 互動式介面輸入,就可以看到目前的算術語境。

from decimal import *
getcontext()

你可以看到以下的輸出

Context(
    prec=28, 
    rounding=ROUND_HALF_EVEN, 
    Emin=-999999, 
    Emax=999999, 
    capitals=1, 
    clamp=0, 
    flags=[], 
    traps=[InvalidOperation, DivisionByZero, Overflow]
    )

比較常用到的是:
prec:精確度 (precision),預設為 28 位數
rounding:約整方式,預設為「ROUND_HALF_EVEN」

其他的請參考 decimal 模組文件

Decimal 類別

decimal 模組提供了一個 Decimal 類別來代表一個十進位的數字,建立一個 Decimal 類別的實體需要提供一個引數,這個引數可以是一個整數、浮點數、字串、或元組。

from decimal import Decimal
a = 1.115
Decimal(a)
# Decimal(a) 的值和想像中的不同
Decimal('1.1149999999999999911182158029987476766109466552734375')
b = 1.125
Decimal(b)
# Decimal(b) 的值則和想像中一樣
Decimal('1.125')

會出現這樣的現象,是因為把浮點數 1.115 轉換成電腦內部的二進位儲存格式時,所產生的變化,如果有興趣,可以看一下參考資料中的「What Every Computer Scientist Should Know About Floating-Point Arithmetic」。

比較完善的做法是傳入字串型態來避免這個問題

from decimal import Decimal
a = 1.115
Decimal(str(a))
# Decimal(a) 的值和想像中一樣了
Decimal('1.115')
b = 1.125
Decimal(str(b)
# Decimal(b) 的值則和想像中一樣
Decimal('1.125')

quantize 方法

quantize 方法可以將一個浮點數,依指示的有效位數(number of significant digits) 進行約整(rounding,或稱捨入)

範例 1:將 1.115 和 1.125 依四捨五入的方法,約整到小數第二位。

使用 python 內建的 round 函數,無法達到我們的目標。

round(1.115, 2)
# 會得到 1.11
# 這是因為在電腦內部,1.115 真正的值是 1.1149999999999999911182158029987476766109466552734375
1.11
round(1.125, 2)
# 會得到 1.12
# 雖然在電腦內部,1.125 真正的值也是 1.125
# 但是 python 的 round 函數,約整的方法並不是四捨五入
# round 函數遇到要約整的數是 5 時,其進位會選擇最近的偶數
# 也就是 decimal 模組中,內建的約整方法 ROUND_HALF_EVEN,這個約整方法無法變更 
1.12

使用 Decimal 的 quantize 方法

from decimal import Decimal, ROUND_HALF_UP

# 正確的使用方式,使用「字串」型態建立 Decimal

# 對 1.115 四捨五入取到小數第二位
Decimal('1.115').quantize(Decimal('.00'), ROUND_HALF_UP)
## 得到 Decimal('1.12'),結果與我們想像中的相同
1.12,可以直接用 print 輸出

# 對 1.125 四捨五入取到小數第二位
Decimal('1.125').quantize(Decimal('.00'), ROUND_HALF_UP)
## 得到 Decimal('1.13'),結果與我們想像中的相同,可以直接用 print 輸出
Decimal('1.13')

## 錯誤的使用方式,使用「浮點數」型態建立 Decimal
Decimal(1.115).quantize(Decimal('.00'), ROUND_HALF_UP)
## 錯誤,原因與之前使用 round 函數時相同
Decimal('1.11')

# 但是 1.125 的小數第2位四捨五入則是正確的
Decimal(1.125).quantize(Decimal('.00'), ROUND_HALF_UP)
# 正確,這是因為其內部儲存方法未經轉換,而且我們指定了約整方法為四捨五入(ROUND_HALF_UP)
Decimal('1.13')
範例 2:將 1.5 和 2.5 依四捨五入的方法,取到整數位。

使用 python 內建的 round 函數,無法達到我們的目標。

round(1.5)
# 會輸出 2
# 跟我們想像的一樣
2
round(2.5)
# 也會輸出 2
# 跟我們想像的不同!!
# 因為 python 的 round 函數,約整的方法並不是四捨五入
# round 函數遇到要約整的數是 5 時,其進位會選擇最近的偶數
# 也就是 decimal 模組中,內建的約整方法 ROUND_HALF_EVEN,這個約整方法無法變更
2

使用 Decimal 的 quantize 方法

from decimal import Decimal, ROUND_HALF_UP

# 正確的使用方式,使用「字串」型態建立 Decimal
Decimal('1.5').quantize(Decimal('0'), ROUND_HALF_UP)
## 得到 Decimal('2'),結果與我們想像中的相同,可以直接用 print 輸出
Decimal('2')

Decimal('2.5').quantize(Decimal('0'), ROUND_HALF_UP)
## Decimal('3'),結果與我們想像中的相同,可以直接用 print 輸出
Decimal('3')

# 也可以用以下的方法四捨五入取整數
Decimal('2.5').quantize(0, ROUND_HALF_UP)

參考資料

1、
What Every Computer Scientist Should Know About Floating-Point Arithmetic

2、decimal — Decimal fixed point and floating point arithmetic
https://docs.python.org/3/library/decimal.html?highlight=decimal#module-decimal

Last modified: 2021-01-03

Author

Comments

Write a Reply or Comment

Your email address will not be published.