以下是一些用作工具函數的例程,它們演示了如何使用 Decimal
類別:
moneyfmt
將 Decimal
轉換為貨幣格式的字串。
def moneyfmt(value, places=2, curr='', sep=',', dp='.',
pos='', neg='-', trailneg=''):
"""Convert Decimal to a money formatted string."""
q = Decimal(10) ** -places # 2 places --> '0.01'
sign, digits, exp = value.quantize(q).as_tuple()
result = []
digits = list(map(str, digits))
build, next = result.append, digits.pop
if sign:
build(trailneg)
for i in range(places):
build(next() if digits else '0')
if places:
build(dp)
if not digits:
build('0')
i = 0
while digits:
build(next())
i += 1
if i == 3 and digits:
i = 0
build(sep)
build(curr)
build(neg if sign else pos)
return ''.join(reversed(result))
pi
計算 Pi
到當前精度。
def pi():
"""Compute Pi to the current precision."""
getcontext().prec += 2 # extra digits for intermediate steps
three = Decimal(3) # substitute "three=3.0" for regular floats
lasts, t, s, n, na, d, da = 0, three, 3, 1, 0, 0, 24
while s != lasts:
lasts = s
n, na = n + na, na + 8
d, da = d + da, da + 32
t = (t * n) / d
s += t
getcontext().prec -= 2
return +s # unary plus applies the new precision
exp
返回 e
的 x
次方。結果類型與輸入類型匹配。
def exp(x):
"""Return e raised to the power of x."""
getcontext().prec += 2
i, lasts, s, fact, num = 0, 0, 1, 1, 1
while s != lasts:
lasts = s
i += 1
fact *= i
num *= x
s += num / fact
getcontext().prec -= 2
return +s
cos
返回 x
的餘弦值(以弧度為單位)。
def cos(x):
"""Return the cosine of x as measured in radians."""
getcontext().prec += 2
i, lasts, s, fact, num, sign = 0, 0, 1, 1, 1, 1
while s != lasts:
lasts = s
i += 2
fact *= i * (i - 1)
num *= x * x
sign *= -1
s += num / fact * sign
getcontext().prec -= 2
return +s
sin
返回 x
的正弦值(以弧度為單位)。
def sin(x):
"""Return the sine of x as measured in radians."""
getcontext().prec += 2
i, lasts, s, fact, num, sign = 1, 0, x, 1, x, 1
while s != lasts:
lasts = s
i += 2
fact *= i * (i - 1)
num *= x * x
sign *= -1
s += num / fact * sign
getcontext().prec -= 2
return +s
decimal.Decimal('1234.5')
是否過於笨拙?使用互動解釋器時有沒有最小化輸入量的方式?A. 有些使用者會將 Decimal
建構器簡寫為一個字母:
>>> import decimal
>>> D = decimal.Decimal
>>> D('1.23') + D('3.45')
Decimal('4.68')
這樣可以減少輸入量並提高代碼的可讀性。
A. 使用 quantize()
方法將數字捨入到固定的小數位數。如果設定了 Inexact
陷阱,它也適用於驗證有效性:
from decimal import Decimal, Context, Inexact
TWOPLACES = Decimal(10) ** -2 # same as Decimal('0.01')
# Round to two places
Decimal('3.214').quantize(TWOPLACES)
Decimal('3.21')
# Validate that a number does not exceed two places
Decimal('3.21').quantize(TWOPLACES, context=Context(traps=[Inexact]))
Decimal('3.21')
Decimal('3.214').quantize(TWOPLACES, context=Context(traps=[Inexact]))
Traceback (most recent call last):
...
Inexact: None
A. 某些運算如與整數相加、相減和相乘會自動保留固定的小數位數。其他運算如相除和非整數相乘則需要額外的 quantize()
處理步驟:
a = Decimal('102.72') # Initial fixed-point values
b = Decimal('3.17')
a + b # Addition preserves fixed-point
Decimal('105.89')
a - b
Decimal('99.55')
a * 42 # So does integer multiplication
Decimal('4314.24')
(a * b).quantize(TWOPLACES) # Must quantize non-integer multiplication
Decimal('325.62')
(b / a).quantize(TWOPLACES) # And quantize division
Decimal('0.03')
為了方便,可以定義處理 quantize()
步驟的函數:
def mul(x, y, fp=TWOPLACES):
return (x * y).quantize(fp)
def div(x, y, fp=TWOPLACES):
return (x / y).quantize(fp)
mul(a, b) # Automatically preserve fixed-point
Decimal('325.62')
div(b, a)
Decimal('0.03')
200
, 200.000
, 2E2
和 .02E+4
都具有相同的值但其精度不同。是否有辦法將它們轉換為一個可識別的規範值?A. normalize()
方法可以將所有相等的值轉換為單一表示形式:
values = map(Decimal, '200 200.000 2E2 .02E+4'.split())
[v.normalize() for v in values]
[Decimal('2E+2'), Decimal('2E+2'), Decimal('2E+2'), Decimal('2E+2')]
A. 捨入是在計算之後發生的。decimal
模組設計中,數字被視為精確的,並且不依賴於當前上下文來創建。它們可以具有比當前上下文更高的精確度。計算過程會使用精確的輸入,然後對計算結果應用捨入(或其他上下文操作):
from decimal import Decimal, getcontext
getcontext().prec = 5
pi = Decimal('3.1415926535') # More than 5 digits
pi # All digits are retained
Decimal('3.1415926535')
pi + 0 # Rounded after an addition
Decimal('3.1416')
pi - Decimal('0.00005') # Subtract unrounded numbers, then round
Decimal('3.1415')
pi + 0 - Decimal('0.00005') # Intermediate values are rounded
Decimal('3.1416')
A. 對於某些數值,指數表示法是唯一的表示方式。若不必關心有效位數,則可以移除指數和末尾的零,但這樣會丟失有效位:
def remove_exponent(d):
return d.quantize(Decimal(1)) if d == d.to_integral() else d.normalize()
remove_exponent(Decimal('5E+3'))
Decimal('5000')
Decimal
?A. 是的,任何二元浮點數都可以精確地表示為 Decimal
值,但完全精確的轉換可能需要比預期更高的精度:
import math
Decimal(math.pi)
Decimal('3.141592653589793115997963468544185161590576171875')
A. 使用 decimal
模組可以輕鬆檢測結果。最佳實踐是使用更高的精度和不同的捨入模式重新計算。明顯不同的結果顯示存在精度不足、捨入模式問題、不符合條件的輸入或是結果不穩定的演算法。
A. 是的,所有值都被視為精確的,只有結果會被捨入。對於輸入而言,這意味著「所輸入即所得」。但如果忘記了輸入沒有被捨入,結果可能會看起來很奇怪:
getcontext().prec = 3
Decimal('3.104') + Decimal('2.104')
Decimal('5.21')
Decimal('3.104') + Decimal('0.000') + Decimal('2.104')
Decimal('5.20')
解決方案是提高精度或使用單目加法運算對輸入執行強制捨入:
getcontext().prec = 3
+Decimal('1.23456789') # unary plus triggers rounding
Decimal('1.23')
# 使用 create_decimal() 方法在建立輸入時執行捨入
Context(prec=5, rounding=ROUND_DOWN).create_decimal('1.2345678')
Decimal('1.2345')
A. 是的。在 CPython 和 PyPy3 實作中,decimal
模組的 C/CFFI 版本集成了高速 libmpdec
函式庫來實現任意精度正確捨入的十進制浮點算術。libmpdec
會對中等大小的數字使用 Karatsuba 乘法,而對非常巨大的數字使用數字原理變換。
對於大數字算術,最便捷的方法是使用 prec
的最大值:
from decimal import Decimal, Context, MAX_PREC, MAX_EMAX, MIN_EMIN, setcontext
setcontext(Context(prec=MAX_PREC, Emax=MAX_EMAX, Emin=MIN_EMIN))
x = Decimal(2) ** 256
x / 128
Decimal('904625697166532776746648320380374280103671755200316906558262375061821325312')
對於精度不足的結果,在 64 位元平台上 MAX_PREC
的值可能過大,會導致記憶體不足:
Decimal(1) / 3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
MemoryError
在具有超量分配的系統上,可以根據可用的 RAM 大小來調整 prec
。假設有
8GB 的 RAM 並期望同時有 10 個操作數,每個最多使用 500MB:
import sys
from decimal import Context, Decimal, Inexact
# Maximum number of digits for a single operand using 500MB in 8-byte words
# with 19 digits per word (4-byte and 9 digits for the 32-bit build):
maxdigits = 19 * ((500 * 1024**2) // 8)
# Check that this works:
c = Context(prec=maxdigits, Emax=MAX_EMAX, Emin=MIN_EMIN)
c.traps[Inexact] = True
setcontext(c)
# Fill the available precision with nines:
x = Decimal(0).logical_invert() * 9
sys.getsizeof(x)
524288112
x + 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
decimal.Inexact: [<class 'decimal.Inexact'>]
decimal
模組提供了靈活且高精度的十進制算術,尤其適合對精度要求極高的金融、科學及工程應用。透過合理設定上下文精度、捨入模式和陷阱,使用者可以有效避免精度不足或異常結果。結合一系列工具函數,decimal
不僅提升了計算的精確性,也讓複雜運算變得更具可讀性與可靠性。
這30天介紹了許多的有關於數學模組的介紹與應用,讓我又更知道程式函數的多樣,也更熟悉了Python這個語言。