考虑以下 Python 代码:
from decimal import Decimal
d = Decimal("1.23")
print(f"{d = }, {d.shift(1) = }")
当我执行它时(使用 Python 3.12.4),我得到以下输出:
d = Decimal('1.23'), d.shift(1) = Decimal('12.30')
这正是我期望的输出:移位操作,给定正 1 的 arg,左移 1.23 1 个小数点,产生 12.30
现在执行这段代码:
from decimal import Decimal
d = Decimal(1.23)
print(f"{d = }, {d.shift(1) = }")
唯一的区别是我们使用 float 而不是 str 构造 d。
当我执行它时,我得到以下输出:
d = Decimal('1.229999999999999982236431605997495353221893310546875'), d.shift(1) = Decimal('6.059974953532218933105468750E-24')
该输出的第一部分正是我所期望的:1.23 的浮点数不能完美地表示 10 进制数字;这是预期的浮点错误。
但是 shift(1) 输出的第二部分让我大吃一惊,以一种糟糕的方式:我期望它类似于 Decimal('12.29999999999999982236431605997495353221893310546875'),即一个相当接近 12.30 的值。
它到底是如何产生像 Decimal('6.059974953532218933105468750E-24') 这样看似完全不相关的结果?
我知道如果我执行 Decimal(str(1.23)).shift(1) 那么它将产生我期望的结果。
我只是不明白为什么 shift 方法会产生完全不同的结果,具体取决于您是否从 str 与 float 构造 Decimal。
decimal
模块实现了IBM十进制算术规范。
它明确指出了 rotate (但不是 shift,尽管它看起来是相同的行为):
如果第一个操作数的系数少于 precision 位,则将其视为在旋转之前在左侧用零填充到长度 precision。同样,如果第一个操作数的系数超过 precision 位,则在使用前在左侧截断。
因此,如果超过 precision 位,则仅保留最右边的 precision 位,以供 shift 和 rotate 操作。
在以下精度为 6 的示例中,
12
被丢弃,345678
被移动或旋转:
import decimal as dec
c = dec.getcontext()
c.prec = 6
d = dec.Decimal('12345678')
print(d.shift(2))
print(f'{d.shift(-2):06}') # formatted to show leading zeros.
print(d.rotate(2))
print(d.rotate(-2))
输出:
567800 # 34 was shifted out the left side. Right shifted in zeros.
003456 # 78 was shifted out the right side. Left shifted in zeros.
567834 # 34 rotated from left to right side.
783456 # 78 rotated from right to left side.
因此,对于以下示例:
>>> dec.Decimal('1.229999999999999982236431605997495353221893310546875').shift(1)
Decimal('6.059974953532218933105468750E-24')
默认精度为28,所以只保留最后28位(相同指数):
1.229999999999999982236431605997495353221893310546875
0.000000000000000000000001605997495353221893310546875 # truncated on left to 28 digits
0.000000000000000000000006059974953532218933105468750 # shift 1, drops left 1 and adds right 0
即~6e-24。 它遵循规范。
提高处理整数的精度:
import decimal as dec
c = dec.getcontext()
c.prec = 53
print(dec.Decimal('1.229999999999999982236431605997495353221893310546875').shift(1))
输出:
12.299999999999999822364316059974953532218933105468750