Python Decimal类型除以3的精度问题及取整合理性疑问
Decimal处理无限小数与取整的问题分析
问题与实验结果
我好奇decimal包如何处理无限小数,做了如下实验:
Python 3.7.3 (default, Oct 31 2022, 14:04:00) [GCC 8.3.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> >>> from decimal import * >>> import math >>> Decimal(1) / 3 * 3 Decimal('0.9999999999999999999999999999') >>> 1/3*3 1.0 >>> Decimal(1) / 3 * 3 == Decimal(1) False >>> math.floor(Decimal(1) / 3 * 3) 0 >>> math.floor(1 / 3 * 3) 1
我大概能猜到原因,但想确认math.floor(Decimal(1) / 3 * 3)的结果是否符合预期,是否应该接受?
补充实验:
>>> Decimal(1) / 3 * 3 * 6 Decimal('5.999999999999999999999999999') >>> math.floor(Decimal(1) / 3 * 3 * 6) 5 >>> math.floor(float(Decimal(1) / 3 * 3 * 6)) 6 >>> sum([Decimal(1) / 3 * 3] * 6) Decimal('6.000000000000000000000000000') >>> math.floor(sum([Decimal(1) / 3 * 3] * 6)) 6
我想知道是否在使用floor/ceil取整时需要格外谨慎?
背景:原本好奇IEEE754双精度浮点数中有名的0.1+0.2问题,想到取整操作涉及进制转换,无限小数的情况在Decimal类型中也可能存在,因此想了解Decimal如何处理这类取整,以及是否存在安全的取整方法(当1为有效位时)。
核心原因与结论
1. math.floor(Decimal(1)/3*3)的结果符合预期
Decimal默认采用28位精度,1/3是十进制无限循环小数,存储时会被截断为0.3333333333333333333333333333,乘以3后得到0.9999999999999999999999999999——这个值确实小于1,所以math.floor返回0是完全符合逻辑的,属于Decimal精确性的体现(不像普通float会因IEEE754的精度特性自动近似为1.0)。
2. 取整操作确实需要谨慎
- 直接用
math.floor/math.ceil处理Decimal近似值时,结果完全基于其实际存储的数值,当数值是接近整数的近似值时,结果可能不符合直觉(比如5.999...的floor是5)。 - 对比sum操作的差异:多次累加
0.999...时,Decimal的精度规则会处理累积误差,最终得到精确的6.0,这是因为累加后的数值5.9999999999999999999999999994(6次0.999...相加)会被Decimal的舍入规则修正为6.0。
3. 安全的取整方法
如果需要对接近整数的Decimal数值取整,推荐使用Decimal自身的方法,而非直接调用math模块的函数:
- 使用
to_integral_value()配合指定舍入模式:from decimal import Decimal, ROUND_HALF_UP num = Decimal(1)/3*3 print(num.to_integral_value(rounding=ROUND_HALF_UP)) # 输出1 - 先通过
quantize()指定精度后取整:num = Decimal(1)/3*3 print(num.quantize(Decimal('1'), rounding=ROUND_HALF_UP)) # 输出1 - 如果需要向下取整但希望修正近似误差,可以先判断数值是否接近整数,再调整:
num = Decimal(1)/3*3 if abs(num - num.to_integral_value()) < Decimal('1e-20'): print(num.to_integral_value()) else: print(math.floor(num))
内容的提问来源于stack exchange,提问作者Kennyhyun
相关产品推荐
相关产品推荐

