2010-04-18 9 views
12

Quiero representar un número de coma flotante como una cadena redondeada a un número de dígitos significativos, y nunca utilizando el formato exponencial. Esencialmente, quiero mostrar cualquier número de punto flotante y me aseguro de que “ se vea bien ”.Representando bien un número de coma flotante en python

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 debe ser variable, que no puede ser hecho con string formatting operator. [editar] He sido corregido; el operador de formateo de cadenas puede hacer esto.
  • lo necesito para ser redondeado la forma en que una persona era de esperar, no algo como 1.999999999999

que he descubierto una forma de hacer esto, sin embargo se ve como un trabajo todo el año y no es bastante 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 forma mejor de hacerlo? ¿Por qué Python no tiene una función incorporada para esto?

+0

¿Hay alguna idiomas que hacen * * tienen una función incorporada para esto? –

+3

Por si acaso: ¿conoce la opción de formato '%. * G'% (3, 3.142596)? (Especialmente el asterisco) – tzot

+0

Me perdí la opción de formateo de asterisco, sin embargo, usar 'g' devuelve exponentes y usar 'f' está lejos de ser perfecto: "%. * F"% (0, 34500000000000000000000) devuelve '34499999999999999000000' – dln385

Respuesta

8

Parece que no hay ningún truco de formato de cadena incorporado que le permita (1) imprimir carrozas cuyo primer dígito significativo aparece después del lugar decimo decimal y (2) no en notación científica. Entonces eso deja la manipulación manual de cadenas.

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

float_to_decimal se levantó del decimal module's documentation.

Una vez que los dígitos decimales se obtienen como una tupla de enteros, el código siguiente hace lo obvio: cortar el número deseado de dígitos significativos, redondear si es necesario, unir los dígitos en una cadena, tachuela en una señal , coloque un punto decimal y ceros a la izquierda o a la derecha según corresponda.

En la parte inferior encontrará algunos casos que usé 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) 
+0

Ese es un código hermoso. Es exactamente lo que quería. Si lo cambia para que tome un decimal en lugar de float (lo que es trivial), podemos evitar todos los errores de coma flotante. El único problema que encuentro es con enteros largos. Por ejemplo, intente '(34500000000000000000000, 17, '34500000000000000000000')'. Aunque, de nuevo, esto es probablemente solo por errores de coma flotante. – dln385

+0

Buena captura. Hice un ligero cambio en la definición de 'd' que corrige el problema con entradas largas, y como efecto secundario, también permite pasar cadenas numéricas para el' número' arg. – unutbu

+0

Esa corrección funciona, pero encontré otros errores no relacionados. Usando '(756867, 7, '756867.0')', obtengo '75686.7'. Parece que las condiciones para rellenar el resultado están un poco bajas. – dln385

6

Si desea precisión de coma flotante es necesario utilizar el módulo decimal, que es parte de la Python Standard Library: se necesitan

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

El módulo decimal parece que podría ayudar, pero esto realmente no responde mi pregunta. – dln385

+0

@ dln385: ¿Cómo es eso? Cumple con todos los requisitos que enumeró. –

+0

1. Eso especifica precisión, no dígitos significativos. Hay una gran diferencia. 2. No redondeará más allá del punto decimal. Por ejemplo, 756867 con 3 dígitos significativos es 757000. Consulte la pregunta original. 3. Ese método se descompone con números grandes, como los largos. – dln385

-1

flotadores de precisión arbitraria para responder adecuadamente a esta pregunta. Por lo tanto, usar el decimal module es obligatorio. 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 escribió 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 redondear significativa figuras.El método "quantize()" de Decimal no redondeará más alto que el punto decimal, y la función "round()" siempre devuelve un flotante. No sé si se trata de errores, pero significa que la única forma de redondear números de coma flotante de precisión infinita es analizarlo como una lista o cadena y hacer el redondeo manualmente. En otras palabras, no hay una respuesta sensata a esta pregunta.

+0

No necesita flotantes de precisión arbitrarios. Tenga en cuenta que nunca necesita la respuesta redondeada como un flotador, solo como una cuerda. BTW '-1 * anything' se puede escribir como' -cualquier cosa'. – Ponkadoodle

+0

No entiendo que "no redondeará más allá del punto decimal". Pruebe: 'Decimal ('123.456'). Quantize (Decimal ('1e1'))', por ejemplo. –

+0

Por cierto, al redondear una instancia Decimal se devuelve otro decimal en Python 3.x. –

0

Aquí hay un fragmento que formatea un valor de acuerdo con las barras de error especificadas.

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