首页 > 解决方案 > Python:浮点数到十进制的转换和随后的移位可能会给出错误的结果

问题描述

当我在 Python 中转换 afloatdecimal.Decimal随后调用Decimal.shift它时,它可能会给我完全错误和意外的结果,具体取决于浮点数。为什么会这样?

转换 123.5 并移动它:

from decimal import Decimal

a = Decimal(123.5)
print(a.shift(1))  # Gives expected result

上面的代码打印了预期的结果1235.0

如果我改为转换并转换 123.4:

from decimal import Decimal

a = Decimal(123.4)
print(a.shift(1))  # Gives UNexpected result

它给了我3.418860808014869689941406250E-18(大约 0),这是完全出乎意料和错误的。

为什么会这样?


注意:由于浮点数在内存中的表示,我理解浮点不精确。但是,我无法解释为什么这会给我这样一个完全错误的结果。

编辑:是的,通常最好不要将浮点数转换为小数,而是将字符串转换为小数。然而,这不是我的问题的重点。我想了解为什么浮点转换后的移位会产生如此完全错误的结果。因此,如果我在换档后print(Decimal(123.4))给出123.40000000000000568434188608080148696899414062这样的结果,我希望它1234.0000000000000568434188608080148696899414062不会接近于零。

标签: pythonfloating-pointtype-conversiondecimal

解决方案


您需要更改 Decimal 构造函数输入以使用字符串而不是浮点数。

a = Decimal('123.5')
print(a.shift(1))

a = Decimal('123.4')
print(a.shift(1))

或者

a = Decimal(str(123.5))
print(a.shift(1))

a = Decimal(str(123.4))
print(a.shift(1))

输出将如预期的那样。

>>> 1235.0
>>> 1234.0

十进制实例可以从整数、字符串、浮点数或元组构造。从整数或浮点数构造执行该整数或浮点数的值的精确转换。

对于浮点数、小数调用Decimal.from_float()

请注意,Decimal.from_float(0.1) 与 Decimal('0.1') 不同。由于 0.1 在二进制浮点中不能精确表示,因此该值存储为最接近的可表示值,即 0x1.999999999999ap-4。十进制值的精确等值是 0.1000000000000000055511151231257827021181583404541015625。

在内部,Python 十进制库将浮点数转换为两个整数,表示产生浮点数的分数的分子和分母。

n, d = abs(123.4).as_integer_ratio()

然后它计算分母的位长,即表示二进制数所需的位数。

k = d.bit_length() - 1

然后从那里k通过将分子 * 5 乘以分母的位长的幂来使用位长来记录十进制数的系数。

coeff = str(n*5**k)

结果值用于创建一个新的 Decimal 对象,其构造函数参数为sign, coefficient,并exponent使用此值。

对于浮点数,123.5这些值为

>>> 1 1235 -1

对于浮点数,123.4这些值是

1 123400000000000005684341886080801486968994140625 -45

到目前为止,没有任何问题。

但是,当您调用 shift 时,Decimal 库必须根据您指定的班次计算用零填充数字的程度。要在内部执行此操作,它需要用系数长度减去精度。

amount_to_pad = context.prec - len(coeff)

默认精度仅为 28,并且像123.4系数这样的浮点数变得比上面提到的默认精度长得多。这会创建一个用零填充的负数,并使数字非常小,正如您所指出的。

解决此问题的一种方法是将精度提高到指数长度 + 以 (45 + 4) 开头的数字长度。

from decimal import Decimal, getcontext

getcontext().prec = 49

a = Decimal(123.4)
print(a)
print(a.shift(1))

>>> 123.400000000000005684341886080801486968994140625
>>> 1234.000000000000056843418860808014869689941406250

shift提示精度对此计算很重要的文档:

第二个操作数必须是 -precision 到 precision 范围内的整数。

然而,它并没有解释这个对内存限制不好的浮点数的警告。

我希望这会引发某种错误并提示您更改输入或提高精度,但至少您知道!

@MarkDickinson 在上面的评论中指出,您可以查看此 Python 错误跟踪器以获取更多信息:https ://bugs.python.org/issue7233


推荐阅读