Python: Float to Decimal conversion and subsequent shifting may give front result
Python: Float to Decimal conversion and subsequent shifting may give wrong result
当我在 Python 中将 float
转换为 decimal.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),这是完全出乎意料和错误的。
为什么会这样?
注意:
由于浮点数在内存中的表示,我理解浮点不精确。但是,我无法解释为什么这会给我这样一个完全错误的结果。
编辑:
是的,通常最好不要将浮点数转换为小数,而是将字符串转换为小数。但是,这不是我的问题的重点。我想了解为什么 float 转换后的移位会给出这样一个完全错误的结果。所以如果我 print(Decimal(123.4))
它给出 123.40000000000000568434188608080148696899414062
所以在移动之后我希望它是 1234.0000000000000568434188608080148696899414062
而不是几乎为零。
您需要更改 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 调用 Decimal.from_float()
Note that Decimal.from_float(0.1) is not the same as Decimal('0.1'). Since 0.1 is not exactly representable in binary floating point, the value is stored as the nearest representable value which is 0x1.999999999999ap-4. The exact equivalent of the value in decimal is 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 库必须根据您指定的 shift 来计算用零填充数字的量。要在内部执行此操作,需要精度减去系数的长度。
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
的文档暗示精度对于此计算很重要:
The second operand must be an integer in the range -precision through precision.
然而,它并没有解释这个警告,因为浮点数不能很好地适应内存限制。
我希望这会引发某种错误并提示您更改输入或提高精度,但至少您知道!
@MarkDickinson 在上面的评论中指出,您可以查看此 Python 错误跟踪器以获取更多信息:https://bugs.python.org/issue7233
当我在 Python 中将 float
转换为 decimal.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),这是完全出乎意料和错误的。
为什么会这样?
注意: 由于浮点数在内存中的表示,我理解浮点不精确。但是,我无法解释为什么这会给我这样一个完全错误的结果。
编辑:
是的,通常最好不要将浮点数转换为小数,而是将字符串转换为小数。但是,这不是我的问题的重点。我想了解为什么 float 转换后的移位会给出这样一个完全错误的结果。所以如果我 print(Decimal(123.4))
它给出 123.40000000000000568434188608080148696899414062
所以在移动之后我希望它是 1234.0000000000000568434188608080148696899414062
而不是几乎为零。
您需要更改 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 调用 Decimal.from_float()
Note that Decimal.from_float(0.1) is not the same as Decimal('0.1'). Since 0.1 is not exactly representable in binary floating point, the value is stored as the nearest representable value which is 0x1.999999999999ap-4. The exact equivalent of the value in decimal is 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 库必须根据您指定的 shift 来计算用零填充数字的量。要在内部执行此操作,需要精度减去系数的长度。
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
的文档暗示精度对于此计算很重要:
The second operand must be an integer in the range -precision through precision.
然而,它并没有解释这个警告,因为浮点数不能很好地适应内存限制。
我希望这会引发某种错误并提示您更改输入或提高精度,但至少您知道!
@MarkDickinson 在上面的评论中指出,您可以查看此 Python 错误跟踪器以获取更多信息:https://bugs.python.org/issue7233