将浮点数转换为四舍五入的十进制数等价值。

4
当您将一个 float 转换为 Decimal 时,Decimal 将包含尽可能准确的二进制数表示。精度很高很好,但这并不总是您想要的。由于许多十进制数字在二进制中无法完全表示,因此生成的 Decimal 会有一点偏差 - 有时会有一点高,有时会有一点低。
>>> from decimal import Decimal
>>> for f in (0.1, 0.3, 1e25, 1e28, 1.0000000000001):
    print Decimal(f)

0.1000000000000000055511151231257827021181583404541015625
0.299999999999999988897769753748434595763683319091796875
10000000000000000905969664
9999999999999999583119736832
1.000000000000099920072216264088638126850128173828125

理想情况下,我们希望将Decimal四舍五入为最可能的十进制等价数。

我尝试将其转换为str,因为从字符串创建的Decimal将是精确的。不幸的是,str四舍五入了一些过多。

>>> for f in (0.1, 0.3, 1e25, 1e28, 1.0000000000001):
    print Decimal(str(f))

0.1
0.3
1E+25
1E+28
1.0

有没有一种方法可以从浮点数中得到一个漂亮的圆整的 Decimal

1
有人能解释一下为什么要踩我吗?我保证不会个人攻击。 - Mark Ransom
1
如果您想创建一个对应于常量、字面值的十进制数,最好使用字符串作为输入而不是浮点数。如果您从某个计算中获得了浮点数,您可能想要执行以下操作之一:保留所有精度、四舍五入到上下文的精度或更改浮点数的来源以提供一个十进制数。通常不建议将精度舍入到浮点数的“repr”表示精度。 - user2357112
(我不是那个给你点踩的人。) - user2357112
2
请注意,在Python 2.7之前,Decimal构造函数不接受浮点数作为输入,特别是为了让人们思考表示误差。2.6文档中写道:“要从浮点数创建Decimal,请先将其转换为字符串。这可以明确地提醒有关转换的细节(包括表示误差)。” - user2357112
1
@EricPostpischil:Python的repr完全相同(类似于Burger和Dybvig)。 - Mark Dickinson
显示剩余4条评论
2个回答

4

事实证明,reprfloat转换为字符串的效果比str更好。这是一种快捷且简单的转换方式。

>>> for f in (0.1, 0.3, 1e25, 1e28, 1.0000000000001):
    print Decimal(repr(f))

0.1
0.3
1E+25
1E+28
1.0000000000001

在我发现这个方法之前,我想出了一种蛮力的方式来进行四舍五入。它的优点是认识到大数值在15位数字内是准确的 - 上面的repr方法只能识别1e25和1e28示例的一个有效数字。
from decimal import Decimal,DecimalTuple

def _increment(digits, exponent):
    new_digits = [0] + list(digits)
    new_digits[-1] += 1
    for i in range(len(new_digits)-1, 0, -1):
        if new_digits[i] > 9:
            new_digits[i] -= 10
            new_digits[i-1] += 1
    if new_digits[0]:
        return tuple(new_digits[:-1]), exponent + 1
    return tuple(new_digits[1:]), exponent

def nearest_decimal(f):
    sign, digits, exponent = Decimal(f).as_tuple()
    if len(digits) > 15:
        round_up = digits[15] >= 5
        exponent += len(digits) - 15
        digits = digits[:15]
        if round_up:
            digits, exponent = _increment(digits, exponent)
    while digits and digits[-1] == 0 and exponent < 0:
        digits = digits[:-1]
        exponent += 1
    return Decimal(DecimalTuple(sign, digits, exponent))

>>> for f in (0.1, 0.3, 1e25, 1e28, 1.0000000000001):
    print nearest_decimal(f)

0.1
0.3
1.00000000000000E+25
1.00000000000000E+28
1.0000000000001

编辑:我发现还有一个使用暴力四舍五入的原因。repr试图返回一个唯一标识底层float位表示的字符串,但它并不一定保证最后一位数字的准确性。通过使用少一个数字,我的四舍五入函数更常会是你期望的数字。

>>> print Decimal(repr(2.0/3.0))
0.6666666666666666
>>> print dec.nearest_decimal(2.0/3.0)
0.666666666666667

使用`repr`函数创建的十进制数实际上更加精确,但它暗示了一个不存在的精度级别。`nearest_decimal`函数提供了更好的精度和准确性匹配。

我不明白为什么只有最后一个值不同。 - Grijesh Chauhan
如果你发现自己在猜测将十进制表示转换为“float”以便恢复为“Decimal”的过程中可能出现了什么问题,那么你或者你被迫使用的API正在做错事情。我有点失望,因为你的解决方案强化了这样一种观念,即Python中称为“repr”的算法比将十进制转换为固定位数更好。正如评论所说,“repr”既不是“更好”的,也不是“正确”的,它只是看起来不错:https://dev59.com/4HbZa4cB1Zd3GeqPHpvk#5MEJoYgBc1ULPQZF04aP - Pascal Cuoq
@PascalCuoq,我并不声称repr是最好的选择,只是它对于许多目的来说是足够的,并且可以认为比从float转换为Decimal或从str(float)转换为Decimal的默认转换更好。我的最新编辑应该会加强这一点。 - Mark Ransom

0

我已经在Pharo Smalltalk中实现了这个功能,使用了一个名为asMinimalDecimalFractionFloat方法。

这与打印最短的十进制小数分数完全相同,假设正确舍入(到最近的)。这将被重新解释为相同的浮点/双精度浮点数。

有关更多参考,请参阅我的答案Count number of digits after `.` in floating point numbers?


谢谢,但问题特别涉及Python。 - Mark Ransom
Python是否实现了Robert G. Burger和R. Kent Dybvig算法的一种形式,以打印浮点数的最短十进制形式?如果是,您可以像我在Smalltalk中所做的那样,劫持该算法以生成分数而不是字符串。 - aka.nice
啊啊,但我看到repr正在做这件事情...正如Mark Dickinson所报道的那样。 - aka.nice

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接