80 votes

Convertir flotter à la chaîne sans notation scientifique et de la fausse précision

Je souhaite imprimer des nombres à virgule flottante, de sorte qu'ils sont toujours écrits sous forme décimale (par exemple, 12345000000000000000000.0 ou 0.000000000000012345, pas en notation scientifique, mais je veux garder la 15.7 décimales de précision et rien de plus.

Il est bien connu que l' repr d'un float est écrit en notation scientifique si l'exposant est supérieure à 15 ans, ou de moins de -4:

>>> n = 0.000000054321654321
>>> n
5.4321654321e-08  # scientific notation

Si str est utilisé, la chaîne résultante est de nouveau en notation scientifique:

>>> str(n)
'5.4321654321e-08'

Il a été suggéré que je peux utiliser format avec f de drapeau et d'une précision suffisante pour se débarrasser de la notation scientifique:

>>> format(0.00000005, '.20f')
'0.00000005000000000000'

Il fonctionne pour ce numéro, si elle a certains supplémentaire des zéros à la fin. Mais alors le même format échoue .1, ce qui donne des chiffres au-delà de la machine réelle précision de flotteur:

>>> format(0.1, '.20f')
'0.10000000000000000555'

Et si mon nombre est 4.5678e-20, à l'aide de .20f serait encore perdre de la précision relative:

>>> format(4.5678e-20, '.20f')
'0.00000000000000000005'

Ainsi, ces approches ne correspond pas à mes exigences.


Cela conduit à la question: quelle est la méthode la plus simple et aussi performant l'impression d'arbitraire nombre à virgule flottante au format décimal, ayant les mêmes chiffres que dans repr(n) (ou str(n) sur Python 3), mais toujours en utilisant le format décimal, pas la notation scientifique.

C'est, une fonction ou une opération, par exemple, convertit la valeur float 0.00000005 string '0.00000005'; 0.1 de '0.1'; 420000000000000000.0 de '420000000000000000.0' ou 420000000000000000 et les formats de la valeur float -4.5678e-5 en tant que '-0.000045678'.


Après le prime de la période: Il semble qu'il y a au moins 2 approches viables, comme Karin démontré que l'utilisation de la manipulation de la chaîne, on peut réaliser d'importantes boost de vitesse par rapport à mon algorithme initial sur Python 2.

Ainsi,

Depuis que je suis avant tout développement sur Python 3, j'accepte ma propre réponse, et récompense par Karin le bounty.

65voto

Antti Haapala Points 11542

Malheureusement, il semble que même le nouveau style de mise en forme avec float.__format__ y sont favorables. La mise en forme par défaut de floats est le même qu'avec repr; et, f drapeau il y a 6 chiffres fractionnaires par défaut:

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

Cependant, il existe un hack pour obtenir le résultat souhaité - pas le plus rapide, mais relativement simple:

  • d'abord le flotteur est converti en chaîne à l'aide de str() ou repr()
  • puis un nouveau Decimal instance est créée à partir de cette chaîne.
  • Decimal.__format__ soutient f drapeau qui donne le résultat souhaité, et, contrairement à d' floats il imprime le réel de précision au lieu de précision par défaut.

Ainsi, nous pouvons faire une simple fonction utilitaire 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')

Les soins doivent être prises pour ne pas utiliser le mondial décimal contexte, de sorte qu'un nouveau contexte est construit pour cette fonction. C'est le moyen le plus rapide; une autre solution serait d'utiliser decimal.local_context , mais il serait plus lente, la création d'un nouveau thread-contexte local et un gestionnaire de contexte pour chaque conversion.

Cette fonction retourne la chaîne avec tous les chiffres possibles à partir de mantisse, arrondie à la plus courte de l'équivalent de la représentation:

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

Le dernier résultat est arrondi au dernier chiffre

Comme @Karin noté, float_to_str(420000000000000000.0) n'est pas strictement correspondre le format attendu; elle renvoie 420000000000000000 sans fuite .0.

35voto

Karin Points 6226

Si vous êtes satisfait de la précision dans la notation scientifique, on pourrait juste prendre une simple manipulation de la chaîne d'approche? C'est peut-être pas très intelligent, mais il semble fonctionner (passe tous les cas d'utilisation que vous avez présenté), et je pense que c'est assez compréhensible:

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

Performance:

J'étais inquiet que cette approche peut-être trop lent, alors j'ai couru timeit et en rapport avec l'OP de la solution de décimales contextes. Il semble que la manipulation de la chaîne est en fait assez un peu plus vite. Edit: Il semble qu'à être beaucoup plus rapide en Python 2. En Python 3, les résultats étaient similaires, mais avec la virgule approche un peu plus rapide.

Résultat:

  • Python 2: à l'aide d' ctx.create_decimal(): 2.43655490875

  • Python 2: à l'aide de la manipulation de la chaîne: 0.305557966232

  • Python 3: à l'aide d' ctx.create_decimal(): 0.19519368198234588

  • Python 3: utilisation de la manipulation de la chaîne: 0.2661344590014778

Voici le calendrier de code:

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

6voto

gukoff Points 824

Si vous êtes prêt à perdre votre précision arbitraire en appelant str() sur le nombre à virgule, puis c'est le chemin à parcourir:

import decimal

def float_to_string(number, precision=20):
    return '{0:.{prec}f}'.format(
        decimal.Context(prec=100).create_decimal(str(number)),
        prec=precision,
    ).rstrip('0').rstrip('.') or '0'

Il ne comprend pas les variables globales et vous permet de choisir la précision de vous-même. La précision décimale 100 est choisi comme limite supérieure pour l' str(float) de la longueur. La réelle supremum est beaucoup plus faible. L' or '0' de la partie est de la situation avec de petits nombres et le zéro de précision.

Notez qu'il a encore ses conséquences:

>> float_to_string(0.10101010101010101010101010101)
'0.10101010101'

Sinon, si la précision est importante, format est tout simplement parfait:

import decimal

def float_to_string(number, precision=20):
    return '{0:.{prec}f}'.format(
        number, prec=precision,
    ).rstrip('0').rstrip('.') or '0'

Il ne manque pas la précision d'être perdu, tout en appelant str(f). L' or

>> float_to_string(0.1, precision=10)
'0.1'
>> float_to_string(0.1)
'0.10000000000000000555'
>>float_to_string(0.1, precision=40)
'0.1000000000000000055511151231257827021182'

>>float_to_string(4.5678e-5)
'0.000045678'

>>float_to_string(4.5678e-5, precision=1)
'0'

De toute façon, un maximum de décimales sont limitées, car l' float type lui-même a ses limites et ne peut pas exprimer vraiment long flotteurs:

>> float_to_string(0.1, precision=10000)
'0.1000000000000000055511151231257827021181583404541015625'

Aussi, les nombres entiers sont formatés comme-est.

>> float_to_string(100)
'100'

3voto

silgon Points 2646

Je pense que rstrip pouvez obtenir le travail fait.

a=5.4321654321e-08
'{0:.40f}'.format(a).rstrip("0") # float number and delete the zeros on the right
# '0.0000000543216543210000004442039220863003' # there's roundoff error though

Laissez-moi savoir si cela fonctionne pour vous.

2voto

BPL Points 5420

Question intéressante, pour ajouter un peu plus de contenu à la question, voici un petit test de comparaison de @Antti de la Haapala et @Harold solutions de sorties:

import decimal
import math

ctx = decimal.Context()


def f1(number, prec=20):
    ctx.prec = prec
    return format(ctx.create_decimal(str(number)), 'f')


def f2(number, prec=20):
    return '{0:.{prec}f}'.format(
        number, prec=prec,
    ).rstrip('0').rstrip('.')

k = 2*8

for i in range(-2**8,2**8):
    if i<0:
        value = -k*math.sqrt(math.sqrt(-i))
    else:
        value = k*math.sqrt(math.sqrt(i))

    value_s = '{0:.{prec}E}'.format(value, prec=10)

    n = 10

    print ' | '.join([str(value), value_s])
    for f in [f1, f2]:
        test = [f(value, prec=p) for p in range(n)]
        print '\t{0}'.format(test)

Aucun d'eux donne à la "cohérence" des résultats pour tous les cas.

  • Avec Anti vous verrez des chaînes de caractères comme '-000' ou '000'
  • Avec Harolds vous verrez comme les chaînes "

Je préfère la cohérence, même si je suis à sacrifier un peu de vitesse. Qui dépend de compromis que vous voulez assumer pour votre cas d'utilisation.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X