简体   繁体   English

很好地代表python中的浮点数

[英]Nicely representing a floating-point number in python

I want to represent a floating-point number as a string rounded to some number of significant digits, and never using the exponential format. 我想将一个浮点数表示为一个四舍五入到一些有效数字的字符串,并且从不使用指数格式。 Essentially, I want to display any floating-point number and make sure it “looks nice”. 基本上,我想显示任何浮点数,并确保它“看起来不错”。

There are several parts to this problem: 这个问题有几个部分:

  • I need to be able to specify the number of significant digits. 我需要能够指定有效位数。
  • The number of significant digits needs to be variable, which can't be done with with the string formatting operator . 有效位数需要变量,使用字符串格式化运算符无法完成。 [edit] I've been corrected; [编辑]我被纠正了; the string formatting operator can do this. 字符串格式化操作符可以执行此操作
  • I need it to be rounded the way a person would expect, not something like 1.999999999999 我需要它像一个人期望的那样四舍五入,而不是像1.999999999999那样

I've figured out one way of doing this, though it looks like a work-round and it's not quite perfect. 我已经想出了这样做的一种方法,虽然它看起来像是一种工作,但它并不完美。 (The maximum precision is 15 significant digits.) (最大精度为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

Is there a better way to do this? 有一个更好的方法吗? Why doesn't Python have a built-in function for this? 为什么Python没有内置函数呢?

It appears there is no built-in string formatting trick which allows you to (1) print floats whose first significant digit appears after the 15th decimal place and (2) not in scientific notation. 似乎没有内置的字符串格式化技巧,它允许您(1)打印浮点数,其第一个有效数字出现在小数点后15位,(2)不是科学计数法。 So that leaves manual string manipulation. 这就留下了手动字符串操作。

Below I use the decimal module to extract the decimal digits from the float. 下面我使用decimal模块从float中提取十进制数字。 The float_to_decimal function is used to convert the float to a Decimal object. float_to_decimal函数用于将float转换为Decimal对象。 The obvious way decimal.Decimal(str(f)) is wrong because str(f) can lose significant digits. decimal.Decimal(str(f))显然是错误的,因为str(f)可能会丢失有效数字。

float_to_decimal was lifted from the decimal module's documentation . float_to_decimal十进制模块的文档中解除。

Once the decimal digits are obtained as a tuple of ints, the code below does the obvious thing: chop off the desired number of sigificant digits, round up if necessary, join the digits together into a string, tack on a sign, place a decimal point and zeros to the left or right as appropriate. 一旦获得十进制数字作为整数元组,下面的代码就会显而易见:砍掉所需数量的重要数字,必要时向上舍入,将数字连接成一个字符串,粘贴在一个符号上,放置一个小数适当地向左或向右指向零点和零点。

At the bottom you'll find a few cases I used to test the f function. 在底部你会发现我用来测试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)

If you want floating point precision you need to use the decimal module, which is part of the Python Standard Library : 如果你想要浮点精度,你需要使用decimal模块,它是Python标准库的一部分

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

Here is a snippet that formats a value according to the given error bars. 这是一个片段,根据给定的误差线格式化值。

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))

Examples: 例子:

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

Arbitrary precision floats are needed to properly answer this question. 需要任意精确浮动来正确回答这个问题。 Therefore using the decimal module is a must. 因此必须使用十进制模块 There is no method to convert a decimal to a string without ever using the exponential format (part of the original question), so I wrote a function to do just that: 没有使用指数格式(原始问题的一部分)将小数转换为字符串的方法,所以我写了一个函数来做到这一点:

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)

The problem is trying to round to significant figures. 问题是试图围绕重要数字。 Decimal's "quantize()" method won't round higher than the decimal point, and the "round()" function always returns a float. 十进制的“quantize()”方法不会高于小数点,而“round()”函数总是返回一个浮点数。 I don't know if these are bugs, but it means that the only way to round infinite precision floating point numbers is to parse it as a list or string and do the rounding manually. 我不知道这些是否是错误,但这意味着舍入无限精度浮点数的唯一方法是将其解析为列表或字符串并手动进行舍入。 In other words, there is no sane answer to this question. 换句话说,这个问题没有明智的答案。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM