小数移位:如果从 str 构造,则行为符合预期,如果从 float 构造,则行为奇怪

问题描述 投票:0回答:1

考虑以下 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。

python string decimal shift
1个回答
2
投票

decimal
模块实现了IBM十进制算术规范

它明确指出了 rotate (但不是 shift,尽管它看起来是相同的行为):

如果第一个操作数的系数少于 precision 位,则将其视为在旋转之前在左侧用零填充到长度 precision。同样,如果第一个操作数的系数超过 precision 位,则在使用前在左侧截断。

因此,如果超过 precision 位,则仅保留最右边的 precision 位,以供 shiftrotate 操作。

在以下精度为 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
© www.soinside.com 2019 - 2024. All rights reserved.