Saltar al contenido

Convierta flotante en cadena en formato posicional (sin notación científica y precisión falsa)

Solución:

Desafortunadamente, parece que ni siquiera el formato de nuevo estilo con float.__format__ apoya esto. El formato predeterminado de floats es lo mismo que con repr; y con f bandera hay 6 dígitos fraccionarios por defecto:

>>> format(0.0000000005, 'f')
'0.000000'

Sin embargo, existe un truco para obtener el resultado deseado, no el más rápido, pero relativamente simple:

  • primero, el flotador se convierte en una cadena usando str() o repr()
  • luego una nueva Decimal instancia se crea a partir de esa cadena.
  • Decimal.__format__ apoyos f bandera que da el resultado deseado y, a diferencia de floats imprime la precisión real en lugar de la precisión predeterminada.

Así podemos hacer una función de utilidad simple float_to_str:

import decimal

# create a new context for this task
ctx = decimal.Context()

# 20 digits should be enough for everyone :D
ctx.prec = 20

def float_to_str(f):
    """
    Convert the given float to a string,
    without resorting to scientific notation
    """
    d1 = ctx.create_decimal(repr(f))
    return format(d1, 'f')

Se debe tener cuidado de no utilizar el contexto decimal global, por lo que se construye un nuevo contexto para esta función. Ésta es la forma más rápida; otra forma sería usar decimal.local_context pero sería más lento, creando un nuevo contexto local de hilo y un administrador de contexto para cada conversión.

Esta función ahora devuelve la cadena con todos los dígitos posibles de la mantisa, redondeados a la representación equivalente más corta:

>>> float_to_str(0.1)
'0.1'
>>> float_to_str(0.00000005)
'0.00000005'
>>> float_to_str(420000000000000000.0)
'420000000000000000'
>>> float_to_str(0.000000000123123123123123123123)
'0.00000000012312312312312313'

El último resultado se redondea al último dígito

Como señaló @Karin, float_to_str(420000000000000000.0) no coincide estrictamente con el formato esperado; vuelve 420000000000000000 sin arrastrar .0.

Si está satisfecho con la precisión de la notación científica, ¿podríamos simplemente adoptar un enfoque simple de manipulación de cuerdas? Tal vez no sea terriblemente inteligente, pero parece funcionar (pasa todos los casos de uso que ha presentado), y creo que es bastante comprensible:

def float_to_str(f):
    float_string = repr(f)
    if 'e' in float_string:  # detect scientific notation
        digits, exp = float_string.split('e')
        digits = digits.replace('.', '').replace('-', '')
        exp = int(exp)
        zero_padding = '0' * (abs(int(exp)) - 1)  # minus 1 for decimal point in the sci notation
        sign = '-' if f < 0 else ''
        if exp > 0:
            float_string = '{}{}{}.0'.format(sign, digits, zero_padding)
        else:
            float_string = '{}0.{}{}'.format(sign, zero_padding, digits)
    return float_string

n = 0.000000054321654321
assert(float_to_str(n) == '0.000000054321654321')

n = 0.00000005
assert(float_to_str(n) == '0.00000005')

n = 420000000000000000.0
assert(float_to_str(n) == '420000000000000000.0')

n = 4.5678e-5
assert(float_to_str(n) == '0.000045678')

n = 1.1
assert(float_to_str(n) == '1.1')

n = -4.5678e-5
assert(float_to_str(n) == '-0.000045678')

Rendimiento:

Me preocupaba que este enfoque fuera demasiado lento, así que corrí timeit y comparado con la solución OP de contextos decimales. Parece que la manipulación de cuerdas es bastante más rápida. Editar: Parece que solo es mucho más rápido en Python 2. En Python 3, los resultados fueron similares, pero con el enfoque decimal un poco más rápido.

Resultado:

  • Python 2: usando ctx.create_decimal(): 2.43655490875

  • Python 2: usando manipulación de cadenas: 0.305557966232

  • Python 3: usando ctx.create_decimal(): 0.19519368198234588

  • Python 3: usando manipulación de cadenas: 0.2661344590014778

Aquí está el código de tiempo:

from timeit import timeit

CODE_TO_TIME = '''
float_to_str(0.000000054321654321)
float_to_str(0.00000005)
float_to_str(420000000000000000.0)
float_to_str(4.5678e-5)
float_to_str(1.1)
float_to_str(-0.000045678)
'''
SETUP_1 = '''
import decimal

# create a new context for this task
ctx = decimal.Context()

# 20 digits should be enough for everyone :D
ctx.prec = 20

def float_to_str(f):
    """
    Convert the given float to a string,
    without resorting to scientific notation
    """
    d1 = ctx.create_decimal(repr(f))
    return format(d1, 'f')
'''
SETUP_2 = '''
def float_to_str(f):
    float_string = repr(f)
    if 'e' in float_string:  # detect scientific notation
        digits, exp = float_string.split('e')
        digits = digits.replace('.', '').replace('-', '')
        exp = int(exp)
        zero_padding = '0' * (abs(int(exp)) - 1)  # minus 1 for decimal point in the sci notation
        sign = '-' if f < 0 else ''
        if exp > 0:
            float_string = '{}{}{}.0'.format(sign, digits, zero_padding)
        else:
            float_string = '{}0.{}{}'.format(sign, zero_padding, digits)
    return float_string
'''

print(timeit(CODE_TO_TIME, setup=SETUP_1, number=10000))
print(timeit(CODE_TO_TIME, setup=SETUP_2, number=10000))

A partir de NumPy 1.14.0, puede usar numpy.format_float_positional. Por ejemplo, ejecutando las entradas de su pregunta:

>>> numpy.format_float_positional(0.000000054321654321)
'0.000000054321654321'
>>> numpy.format_float_positional(0.00000005)
'0.00000005'
>>> numpy.format_float_positional(0.1)
'0.1'
>>> numpy.format_float_positional(4.5678e-20)
'0.000000000000000000045678'

numpy.format_float_positional utiliza el algoritmo Dragon4 para producir la representación decimal más corta en formato posicional que regresa a la entrada flotante original. También hay numpy.format_float_scientific para notación científica, y ambas funciones ofrecen argumentos opcionales para personalizar cosas como redondeo y recorte de ceros.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *