mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
397 字
1 分钟
为什么十进制计算也需要精确
2023-08-07

为什么浮点数计算不精确 中,我们了解到 0.1 + 0.2 = 0.300000004。但如果是在金融场景,即使是 0.00000001 的误差也是不可接受的。

一、金融计算的特殊性#

1.1 浮点数的误差在金融场景的后果#

# 银行利息计算错误示例
balance = 1000.00
interest_rate = 0.03 # 3% 年利率
# 错误的浮点数计算
calculated_interest = balance * interest_rate
print(f"利息: {calculated_interest}") # 可能输出 30.000000000004
# 错误累计
total = 0.0
for _ in range(1000000):
total += 0.0000001
print(f"总计: {total}") # 不是 0.1!

1.2 金融计算的要求#

场景精度要求
银行利息精确到分(0.01)
外汇交易精确到小数点后 8 位
加密货币精确到聪(satoshi)
股票交易精确到 0.001 元

二、Decimal 的工作原理#

2.1 Decimal vs Float#

from decimal import Decimal, getcontext
# Decimal 使用字符串初始化
a = Decimal('0.1')
b = Decimal('0.2')
print(a + b) # 0.3
# 浮点数初始化 Decimal(不推荐)
c = Decimal(0.1) # 会带入浮点误差
print(c) # 0.1000000000000000055511151231257827021181583404541015625

2.2 Decimal 的表示#

Decimal = 符号 × 系数 × 10^指数
例如:Decimal('3.14') = (+1) × 314 × 10^(-2)

2.3 可配置的精度#

from decimal import Decimal, getcontext
# 设置精度(默认 28 位)
getcontext().prec = 50
a = Decimal('1') / Decimal('3')
print(a) # 0.33333333333333333333333333333333333333333333333333

三、金融计算的最佳实践#

3.1 用字符串创建 Decimal#

from decimal import Decimal, ROUND_HALF_UP
# 始终用字符串创建 Decimal
price = Decimal('19.99')
tax_rate = Decimal('0.08')
quantity = Decimal('3')
# 计算
subtotal = price * quantity
tax = subtotal * tax_rate
total = subtotal + tax
# 四舍五入到分
total = total.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
print(f"总计: {total}") # 64.77

3.2 避免浮点数混合#

# 错误:混合使用
result = Decimal('0.1') + 0.2 # 0.2 是浮点数!
# 正确:全部使用 Decimal
result = Decimal('0.1') + Decimal('0.2')
# 如果必须从浮点数转换
from decimal import Decimal
result = Decimal(str(0.2)) + Decimal('0.1')

3.3 货币计算示例#

from decimal import Decimal, ROUND_HALF_UP
class Money:
def __init__(self, amount, currency='CNY'):
self.amount = Decimal(str(amount))
self.currency = currency
def __add__(self, other):
if self.currency != other.currency:
raise ValueError("货币类型不匹配")
return Money(self.amount + other.amount, self.currency)
def __mul__(self, multiplier):
return Money(self.amount * Decimal(str(multiplier)), self.currency)
def round(self, places=2):
return Money(
self.amount.quantize(
Decimal(10) ** -places,
rounding=ROUND_HALF_UP
),
self.currency
)
# 使用
price = Money('19.99')
tax = price * Decimal('0.08')
total = (price + tax).round()
print(f"总价: {total.amount} {total.currency}") # 21.59 CNY

四、Python 中的 Decimal 性能#

4.1 性能对比#

# Decimal 比 float 慢约 10-100 倍
# 因为是软件模拟,而非硬件支持
# benchmark
import timeit
float_time = timeit.timeit('0.1 + 0.2', number=100000)
decimal_time = timeit.timeit(
"Decimal('0.1') + Decimal('0.2')",
setup="from decimal import Decimal",
number=100000
)
print(f"Float: {float_time:.4f}s")
print(f"Decimal: {decimal_time:.4f}s")
# Float: 0.0023s
# Decimal: 0.1847s

4.2 何时使用 Decimal#

场景推荐
金融计算必须使用 Decimal
科学计算Float 足够
实时交易需要考虑性能
报表生成Decimal 更安全

五、总结#

5.1 为什么金融计算需要 Decimal?#

原因说明
精度要求金融不允许任何舍入误差
法规要求金融审计要求精确
累积误差多次计算后误差放大

5.2 Decimal vs Float#

特性DecimalFloat
精度任意精度固定精度
速度
内存
用途金融计算科学计算

核心原则:在涉及金钱的场景,永远使用 Decimal 或整数运算,不要使用浮点数。

参考资料#

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

为什么十进制计算也需要精确
https://blog.souloss.com/posts/why-the-design/why-decimal-calculation-needs-precision/
作者
Souloss
发布于
2023-08-07
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时