【问题标题】:Nicely representing a floating-point number in python [duplicate]很好地表示python中的浮点数[重复]
【发布时间】:2011-02-09 10:53:49
【问题描述】:

我想将浮点数表示为四舍五入到一些有效数字的字符串,并且从不使用指数格式。本质上,我想显示任何浮点数并确保它“看起来不错”。

这个问题有几个部分:

  • 我需要能够指定 有效位数。
  • 有效位数 必须是可变的,不能是 使用string formatting operator 完成。 [编辑] 我已经更正了;字符串格式化操作符可以做到这一点。
  • 我需要将其四舍五入 人会期望,而不是某事 比如 1.999999999999

我已经想出了一种方法,虽然它看起来像一个工作回合,而且并不完美。 (最大精度为 15 位有效数字。)

>>> def f(number, sigfig):
    return ("%.15f" % (round(number, int(-1 * floor(log10(number)) + (sigfig - 1))))).rstrip("0").rstrip(".")

>>> print f(0.1, 1)
0.1
>>> print f(0.0000000000368568, 2)
0.000000000037
>>> print f(756867, 3)
757000

有没有更好的方法来做到这一点?为什么 Python 没有内置函数呢?

【问题讨论】:

  • 是否有任何语言确实对此具有内置函数?
  • 以防万一:您知道 '%.*g' % (3, 3.142596) 格式选项吗? (尤其是星号)
  • 我确实错过了星号格式化选项,但是使用 'g' 返回指数并且使用 'f' 远非完美: "%.*f" % (0, 34500000000000000000000) 返回 '34499999999999999000000'
  • 你不会说这是否是你正在做的事情,但如果你使用浮点数学来进行与金钱相关的计算,那是一个糟糕透顶的想法。使用十进制模块或将数量存储为整数美分(或当地货币等值)。小数确实是你最好的选择。

标签: python floating-point representation significant-digits


【解决方案1】:

似乎没有内置的字符串格式化技巧允许您 (1) 打印第一个有效数字出现在小数点后 15 位之后的浮点数和 (2) 不是科学计数法。这样就剩下手动字符串操作了。

下面我使用decimal 模块从浮点数中提取十进制数字。 float_to_decimal 函数用于将浮点数转换为Decimal 对象。 decimal.Decimal(str(f)) 的明显方式是错误的,因为 str(f) 可能会丢失有效数字。

float_to_decimal 是从 decimal module's documentation 中提取的。

一旦以整数元组的形式获得十进制数字,下面的代码就会做一件显而易见的事情:去掉所需的有效数字数量,必要时四舍五入,将数字连接成一个字符串,添加一个符号,根据需要在左侧或右侧放置一个小数点和零。

在底部你会发现几个我用来测试f函数的案例。

import decimal

def float_to_decimal(f):
    # http://docs.python.org/library/decimal.html#decimal-faq
    "Convert a floating point number to a Decimal with no loss of information"
    n, d = f.as_integer_ratio()
    numerator, denominator = decimal.Decimal(n), decimal.Decimal(d)
    ctx = decimal.Context(prec=60)
    result = ctx.divide(numerator, denominator)
    while ctx.flags[decimal.Inexact]:
        ctx.flags[decimal.Inexact] = False
        ctx.prec *= 2
        result = ctx.divide(numerator, denominator)
    return result 

def f(number, sigfig):
    # http://stackoverflow.com/questions/2663612/nicely-representing-a-floating-point-number-in-python/2663623#2663623
    assert(sigfig>0)
    try:
        d=decimal.Decimal(number)
    except TypeError:
        d=float_to_decimal(float(number))
    sign,digits,exponent=d.as_tuple()
    if len(digits) < sigfig:
        digits = list(digits)
        digits.extend([0] * (sigfig - len(digits)))    
    shift=d.adjusted()
    result=int(''.join(map(str,digits[:sigfig])))
    # Round the result
    if len(digits)>sigfig and digits[sigfig]>=5: result+=1
    result=list(str(result))
    # Rounding can change the length of result
    # If so, adjust shift
    shift+=len(result)-sigfig
    # reset len of result to sigfig
    result=result[:sigfig]
    if shift >= sigfig-1:
        # Tack more zeros on the end
        result+=['0']*(shift-sigfig+1)
    elif 0<=shift:
        # Place the decimal point in between digits
        result.insert(shift+1,'.')
    else:
        # Tack zeros on the front
        assert(shift<0)
        result=['0.']+['0']*(-shift-1)+result
    if sign:
        result.insert(0,'-')
    return ''.join(result)

if __name__=='__main__':
    tests=[
        (0.1, 1, '0.1'),
        (0.0000000000368568, 2,'0.000000000037'),           
        (0.00000000000000000000368568, 2,'0.0000000000000000000037'),
        (756867, 3, '757000'),
        (-756867, 3, '-757000'),
        (-756867, 1, '-800000'),
        (0.0999999999999,1,'0.1'),
        (0.00999999999999,1,'0.01'),
        (0.00999999999999,2,'0.010'),
        (0.0099,2,'0.0099'),         
        (1.999999999999,1,'2'),
        (1.999999999999,2,'2.0'),           
        (34500000000000000000000, 17, '34500000000000000000000'),
        ('34500000000000000000000', 17, '34500000000000000000000'),  
        (756867, 7, '756867.0'),
        ]

    for number,sigfig,answer in tests:
        try:
            result=f(number,sigfig)
            assert(result==answer)
            print(result)
        except AssertionError:
            print('Error',number,sigfig,result,answer)

【讨论】:

  • 那是一些漂亮的代码。这正是我想要的。如果您将其更改为使用 Decimal 而不是 float(这很简单),那么我们可以一起避免浮点错误。我发现的唯一问题是长整数。例如,尝试(34500000000000000000000, 17, '34500000000000000000000')。不过,这可能只是浮点错误。
  • 好收获。我对d 的定义稍作更改,它解决了长整数的问题,并且作为副作用,您还可以为number arg 传递数字字符串。
  • 该修复有效,但我发现了其他不相关的错误。使用(756867, 7, '756867.0'),我返回75686.7。填充结果的条件似乎有点偏离。
【解决方案2】:

如果您想要浮点精度,您需要使用decimal 模块,它是Python Standard Library 的一部分:

>>> import decimal
>>> d = decimal.Decimal('0.0000000000368568')
>>> print '%.15f' % d
0.000000000036857

【讨论】:

  • 十进制模块看起来可能会有所帮助,但这确实不能回答我的问题。
  • @dln385:怎么会这样?它满足您列出的所有要求。
  • 1.这指定了精度,而不是有效数字。有很大的不同。 2. 小数点后不四舍五入。例如,3 位有效数字的 756867 是 757000。请参阅原始问题。 3. 该方法无法处理大数字,例如长整数。
  • @dln385:你读过文档吗? 1. “十进制模块包含重要位置的概念,因此 1.30 + 1.20 是 2.50。保留尾随零以表示重要性。这是货币应用程序的惯用表示。对于乘法,“教科书”方法使用被乘数中的所有数字。例如,1.3 * 1.2 得到 1.56,而 1.30 * 1.20 得到 1.5600。” 2. 它使用定点而不是浮点,因此您一开始就不会遇到此类问题,并且 3. 十进制模块支持任意精度。
  • @Billy ONeal:是的,我确实阅读了文档。在我上一条评论中,我解释了为什么 '%.15f' % d 没有回答我的问题。至于一般的十进制模块,我找不到四舍五入到有效数字(包括小数点左侧)或显示没有指数的数字的方法。另外,应该注意'%.15f' % d 仍然会分解大整数,即使使用小数也是如此。
【解决方案3】:

这是一个根据给定误差线格式化值的 sn-p。

from math import floor, log10, round

def sigfig3(v, errplus, errmin):
    i = int(floor(-log10(max(errplus,errmin)) + 2))
    if i > 0:
        fmt = "%%.%df" % (i)
        return "{%s}^{%s}_{%s}" % (fmt % v,fmt % errplus, fmt % errmin)
    else:
        return "{%d}^{%d}_{%d}" % (round(v, i),round(errplus, i), numpy.round(i))

例子:

5268685 (+1463262,-2401422) becomes 5300000 (+1500000,-2400000)
0.84312 +- 0.173124 becomes 0.84 +- 0.17

【讨论】:

    【解决方案4】:

    需要任意精度的浮点数才能正确回答这个问题。因此使用decimal module 是必须的。在不使用指数格式(原始问题的一部分)的情况下,没有任何方法可以将小数转换为字符串,因此我编写了一个函数来做到这一点:

    def removeExponent(decimal):
        digits = [str(n) for n in decimal.as_tuple().digits]
        length = len(digits)
        exponent = decimal.as_tuple().exponent
        if length <= -1 * exponent:
            zeros = -1 * exponent - length
            digits[0:0] = ["0."] + ["0"] * zeros
        elif 0 < -1 * exponent < length:
            digits.insert(exponent, ".")
        elif 0 <= exponent:
            digits.extend(["0"] * exponent)
        sign = []
        if decimal.as_tuple().sign == 1:
            sign = ["-"]
        print "".join(sign + digits)
    

    问题是试图四舍五入到有效数字。 Decimal 的“quantize()”方法不会四舍五入高于小数点,并且“round()”函数总是返回一个浮点数。我不知道这些是否是错误,但这意味着舍入无限精度浮点数的唯一方法是将其解析为列表或字符串并手动进行舍入。换句话说,这个问题没有理智的答案。

    【讨论】:

    • 您不需要任意精度的浮点数。请注意,他从不将四舍五入的答案作为浮点数,而只是作为字符串。顺便说一句,-1 * anything 可以写成-anything
    • 我不明白你的“不会四舍五入高于小数点”。试试:Decimal('123.456').quantize(Decimal('1e1')),例如。
    • 顺便说一句,在 Python 3.x 中舍入一个 Decimal 实例会返回另一个 Decimal。
    • @Mark Dickinson:你说得对,我没想到 'Decimal('123.456').quantize(Decimal('10'))' 会有不同的行为。此外,如果小数舍入在 Python 3.x 中返回小数,那么我似乎该升级了。感谢您的指点。
    猜你喜欢
    • 2011-08-10
    • 2013-08-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-28
    • 1970-01-01
    相关资源
    最近更新 更多