如果由 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 时,为什么移位方法会产生如此截然不同的结果?

python floating-point decimal shift
1个回答
3
投票

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.