将float转换为舍入的十进制等效值

时间:2013-09-19 04:06:07

标签: python floating-point decimal rounding

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

2 个答案:

答案 0 :(得分:4)

事实证明,repr可以更好地将float转换为字符串,而不是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功能可在精度和准确度之间实现更好的匹配。

答案 1 :(得分:0)

我已经在Pharo Smalltalk中使用名为Float的{​​{1}}方法实现了此功能。

与打印最小的小数部分完全相同的问题将被重新解释为相同的float / double,假设正确的舍入(到最近)。

请参阅Count number of digits after `.` in floating point numbers?的答案以获取更多参考资料