为何从float构造Decimal时shift方法结果异常?
Decimal从float构造后shift方法结果异常的原因
问题重现
用字符串构造Decimal(正常情况)
代码:
from decimal import Decimal d = Decimal("1.23") print(f"{d = }, {d.shift(1) = }")
执行输出:
d = Decimal('1.23'), d.shift(1) = Decimal('12.30')
此结果符合预期:看起来shift(1)实现了十进制左移1位的效果。
用float构造Decimal(异常情况)
代码:
from decimal import Decimal d = Decimal(1.23) print(f"{d = }, {d.shift(1) = }")
执行输出:
d = Decimal('1.229999999999999982236431605997495353221893310546875'), d.shift(1) = Decimal('6.059974953532218933105468750E-24')
这里float转Decimal的精度问题符合预期,但shift(1)的结果并非近似12.3的数值,而是一个极小值,这是核心疑问。
原因解析
Decimal的shift()方法并非实现十进制的位移动,它的实际作用是调整Decimal内部的二进制指数:
shift(n)会将Decimal的二进制指数增加n,本质是对数值执行d * (2 ** n)的操作。
两种构造方式的差异
- 字符串构造的Decimal:
Decimal("1.23")的内部表示基于十进制规则,其二进制指数刚好和十进制位移动的效果对齐,所以shift(1)看起来像是十进制左移1位,但这只是巧合。 - float构造的Decimal:float本身是二进制浮点数,
1.23无法精确表示,实际存储的二进制值对应的Decimal内部有一个非常大的负二进制指数。此时调用shift(1),只是将这个二进制指数加1,导致数值量级发生非预期变化,最终得到极小值。
正确实现十进制位移动的方式
如果需要实现十进制的左移/右移,应该直接使用十进制的乘除操作:
- 左移n位:
d * Decimal(10) ** n - 右移n位:
d / Decimal(10) ** n
示例代码:
from decimal import Decimal d = Decimal(1.23) print(f"{d * Decimal(10) = }") # 十进制左移1位
输出:
d * Decimal(10) = Decimal('12.29999999999999982236431606')
这才是预期的近似12.3的结果。
内容的提问来源于stack exchange,提问作者HaroldFinch
相关产品推荐
相关产品推荐

