Pregunta

Quiero representar un número de coma flotante como una cadena redondea a un número de dígitos significativos, y nunca con el formato exponencial. En esencia, quiero mostrar cualquier número de coma flotante y asegúrese de que “se ve bien”.

Hay varias partes a este problema:

  • Tengo que ser capaz de especificar el número de dígitos significativos.
  • El número de dígitos significativos tiene que ser variable, que no puede ser hecho con el formato de cadenas href="http://docs.python.org/library/stdtypes.html#string-formatting-operations" operador. [Editar] He sido corregidas; el operador de formato de cadenas puede hacer esto.
  • lo necesito para ser redondeado la forma de una persona podría esperar, no es algo como 1.999999999999

He descubierto una manera de hacer esto, sin embargo se ve como un trabajo todo el año y no es del todo perfecto. (La precisión máxima es de 15 dígitos significativos.)

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

¿Hay una mejor manera de hacer esto? ¿Por qué no Python tiene una función incorporada para esto?

¿Fue útil?

Solución

Parece que hay ningún encuentra incorporado el formato de cadenas de truco que le permite flotadores (1) de impresión cuyo primer dígito significativo aparece después de la 15ª posición decimal y (2) que no están en notación científica. Por lo que las hojas de manipulación de cadenas manual.

A continuación utilizo el módulo decimal para extraer los dígitos decimales del flotador. La función float_to_decimal se utiliza para convertir el flotador a un objeto Decimal. La forma más obvia decimal.Decimal(str(f)) está mal porque str(f) puede perder dígitos significativos.

float_to_decimal fue levantada de la decimal del módulo de documentación .

Una vez que los dígitos decimales se obtienen como una tupla de enteros, el código de abajo hace lo obvio: cortar el número deseado de dígitos sigificant, hasta ronda si es necesario, se unen los dígitos juntos en una cadena, tachuela en una muestra, colocar un punto decimal y ceros a la izquierda o la derecha, según corresponda.

En la parte inferior encontrará algunos casos he utilizado para probar la función 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)

Otros consejos

Si desea precisión de coma flotante es necesario utilizar el módulo decimal, que es parte de la Python estándar Biblioteca :

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

Aquí hay un fragmento que los formatos de un valor de acuerdo a las barras de error dados.

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

Ejemplos:

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

Se necesitan flotadores de precisión arbitraria para responder adecuadamente a esta pregunta. Por lo tanto, utilizando la decimales Módulo es una necesidad. No existe un método para convertir un decimal a una cadena sin tener que utilizar el formato exponencial (parte de la pregunta original), así que escribí una función para hacer precisamente eso:

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)

El problema es tratar de vuelta a cifras significativas. Decimal "cuantización ()" método no redondos más alto que el punto decimal, y el "() redonda" función siempre devuelve un flotador. No sé si estos son errores, sino que significa que la única forma de números de punto flotante de precisión infinita redondos es analizarlo como una lista o una cadena y hacer el redondeo de forma manual. En otras palabras, no hay cuerdo respuesta a esta pregunta.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top