Comment arrondir un nombre à des chiffres significatifs en Python

J'ai besoin d'arrondir un flotteur pour être affiché dans une interface utilisateur. Par exemple, à un chiffre significatif:

1234 -> 1000

0.12 -> 0.1

0.012 -> 0.01

0.062 -> 0.06

6253 -> 6000

1999 -> 2000

Y a-t-il une bonne façon de le faire en utilisant la bibliothèque Python, ou dois-je l'écrire moi-même?

102
demandé sur Peter Graham 2010-08-05 04:48:04

13 réponses

Vous pouvez utiliser des nombres négatifs pour arrondir les entiers:

>>> round(1234, -3)
1000.0

Ainsi, si vous n'avez besoin que du chiffre le plus significatif:

>>> from math import log10, floor
>>> def round_to_1(x):
...   return round(x, -int(floor(log10(abs(x)))))
... 
>>> round_to_1(0.0232)
0.02
>>> round_to_1(1234243)
1000000.0
>>> round_to_1(13)
10.0
>>> round_to_1(4)
4.0
>>> round_to_1(19)
20.0

Vous devrez probablement prendre soin de transformer float en entier s'il est plus grand que 1.

119
répondu Evgeny 2016-01-07 06:43:05

%g dans la mise en forme de chaîne formatera un flottant arrondi à un certain nombre de chiffres significatifs. Il utilisera parfois la notation scientifique 'e', donc convertissez la chaîne arrondie en un flottant puis via le formatage de la chaîne %S.

>>> '%s' % float('%.1g' % 1234)
'1000'
>>> '%s' % float('%.1g' % 0.12)
'0.1'
>>> '%s' % float('%.1g' % 0.012)
'0.01'
>>> '%s' % float('%.1g' % 0.062)
'0.06'
>>> '%s' % float('%.1g' % 6253)
'6000.0'
>>> '%s' % float('%.1g' % 1999)
'2000.0'
80
répondu Peter Graham 2010-08-05 04:24:19

Si vous voulez avoir autre que 1 décimal significatif (sinon le même que Evgeny):

>>> from math import log10, floor
>>> def round_sig(x, sig=2):
...   return round(x, sig-int(floor(log10(abs(x))))-1)
... 
>>> round_sig(0.0232)
0.023
>>> round_sig(0.0232, 1)
0.02
>>> round_sig(1234243, 3)
1230000.0
35
répondu indgar 2017-03-17 16:01:42

Pour arrondir un entier à 1 chiffre significatif, l'idée de base est de le convertir en un point flottant avec 1 chiffre avant le point et de l'arrondir, puis de le convertir à sa taille entière d'origine.

Pour ce faire, nous devons connaître la plus grande puissance de 10 inférieure à l'entier. Nous pouvons utiliser floor de la fonction log 10 pour cela.

from math import log10, floor
def round_int(i,places):
    if i == 0:
        return 0
    isign = i/abs(i)
    i = abs(i)
    if i < 1:
        return 0
    max10exp = floor(log10(i))
    if max10exp+1 < places:
        return i
    sig10pow = 10**(max10exp-places+1)
    floated = i*1.0/sig10pow
    defloated = round(floated)*sig10pow
    return int(defloated*isign)
5
répondu Cris Stringfellow 2012-03-04 18:18:19

J'ai modifié la solution d'indgar pour gérer les nombres négatifs et les petits nombres (y compris zéro).

def round_sig(x, sig=6, small_value=1.0e-9):
    return round(x, sig - int(floor(log10(max(abs(x), abs(small_value))))) - 1)
4
répondu user2829661 2016-03-26 19:50:51
print('{:g}'.format(float('{:.1g}'.format(12.345))))

Cette solution est différente de toutes les autres parce que:

  1. Ilexactement résout la question OP
  2. il ne pas besoin de tout paquet supplémentaire
  3. , il n' pas besoin définies par l'utilisateur fonction auxiliaire ou opération mathématique

Pour un nombre arbitraire n de chiffres significatifs, vous pouvez utiliser:

print('{:g}'.format(float('{:.{p}g}'.format(i, p=n))))

Essai:

a = [1234, 0.12, 0.012, 0.062, 6253, 1999, -3.14, 0., -48.01, 0.75]
b = ['{:g}'.format(float('{:.1g}'.format(i)))) for i in a]
# b == ['1000', '0.1', '0.01', '0.06', '6000', '2000', '-3', '0', '-50', '0.8']

Note: avec cette solution, il est impossible d'adapter dynamiquement le nombre de chiffres significatifs à partir de l'entrée car il n'y a pas de moyen standard de distinguer les nombres avec des nombres différents de zéros de fin (3.14 == 3.1400). Si vous avez besoin de le faire, alors des fonctions non standard comme celles fournies dans le paquet to-precision sont nécessaires.

4
répondu Falken 2018-02-15 17:10:34
def round_to_n(x, n):
    if not x: return 0
    power = -int(math.floor(math.log10(abs(x)))) + (n - 1)
    factor = (10 ** power)
    return round(x * factor) / factor

round_to_n(0.075, 1)      # 0.08
round_to_n(0, 1)          # 0
round_to_n(-1e15 - 1, 16) # 1000000000000001.0

J'espère prendre le meilleur de toutes les réponses ci-dessus (Moins être capable de le mettre comme un lambda d'une ligne ;) ). N'avez pas encore exploré, n'hésitez pas à modifier cette réponse:

round_to_n(1e15 + 1, 11)  # 999999999999999.9
3
répondu AJP 2016-07-13 22:17:22

J'ai créé le paquet to-precision {[5] } qui fait ce que vous voulez. Il vous permet de donner à vos chiffres des chiffres plus ou moins significatifs.

Il produit également une notation standard, scientifique et technique avec un nombre spécifié de chiffres significatifs.

Dans la réponse acceptée, il y a la ligne

>>> round_to_1(1234243)
1000000.0

Qui spécifie en fait 8 sig figs. Pour le numéro 1234243, Ma bibliothèque n'affiche qu'un chiffre significatif:

>>> from to_precision import to_precision
>>> to_precision(1234243, 1, 'std')
'1000000'
>>> to_precision(1234243, 1, 'sci')
'1e6'
>>> to_precision(1234243, 1, 'eng')
'1e6'

Il arrondira également le dernier figure significative et peut choisir automatiquement la notation à utiliser si une notation n'est pas spécifiée:

>>> to_precision(599, 2)
'600'
>>> to_precision(1164, 2)
'1.2e3'
3
répondu William Rusnack 2018-07-29 23:45:58

Je ne peux pas penser à quelque chose qui serait capable de gérer cela hors de la boîte. Mais c'est assez bien géré pour les nombres à virgule flottante.

>>> round(1.2322, 2)
1.23

Les entiers sont plus délicats. Ils ne sont pas stockés en tant que base 10 en mémoire, donc les endroits importants ne sont pas une chose naturelle à faire. C'est assez trivial à implémenter une fois qu'ils sont une chaîne.

, Ou pour les entiers:

>>> def intround(n, sigfigs):
...   n = str(n)
...   return n[:sigfigs] + ('0' * (len(n)-(sigfigs)))

>>> intround(1234, 1)
'1000'
>>> intround(1234, 2)

Si vous souhaitez créer une fonction qui gère n'importe quel nombre, ma préférence serait de les convertir tous les deux en chaînes et recherchez une décimale pour décider quoi faire:

>>> def roundall1(n, sigfigs):
...   n = str(n)
...   try:
...     sigfigs = n.index('.')
...   except ValueError:
...     pass
...   return intround(n, sigfigs)

Une autre option consiste à vérifier le type. Ce sera beaucoup moins flexible, et ne jouera probablement pas bien avec d'autres nombres tels que Decimal objects:

>>> def roundall2(n, sigfigs):
...   if type(n) is int: return intround(n, sigfigs)
...   else: return round(n, sigfigs)
1
répondu Tim McNamara 2010-08-05 02:28:48

J'ai aussi rencontré cela mais j'avais besoin de contrôle sur le type d'arrondi. Ainsi, j'ai écrit une fonction rapide (Voir le code ci-dessous) qui peut prendre en compte la valeur, le type d'arrondi et les chiffres significatifs souhaités.

import decimal
from math import log10, floor

def myrounding(value , roundstyle='ROUND_HALF_UP',sig = 3):
    roundstyles = [ 'ROUND_05UP','ROUND_DOWN','ROUND_HALF_DOWN','ROUND_HALF_UP','ROUND_CEILING','ROUND_FLOOR','ROUND_HALF_EVEN','ROUND_UP']

    power =  -1 * floor(log10(abs(value)))
    value = '{0:f}'.format(value) #format value to string to prevent float conversion issues
    divided = Decimal(value) * (Decimal('10.0')**power) 
    roundto = Decimal('10.0')**(-sig+1)
    if roundstyle not in roundstyles:
        print('roundstyle must be in list:', roundstyles) ## Could thrown an exception here if you want.
    return_val = decimal.Decimal(divided).quantize(roundto,rounding=roundstyle)*(decimal.Decimal(10.0)**-power)
    nozero = ('{0:f}'.format(return_val)).rstrip('0').rstrip('.') # strips out trailing 0 and .
    return decimal.Decimal(nozero)


for x in list(map(float, '-1.234 1.2345 0.03 -90.25 90.34543 9123.3 111'.split())):
    print (x, 'rounded UP: ',myrounding(x,'ROUND_UP',3))
    print (x, 'rounded normal: ',myrounding(x,sig=3))
0
répondu drew.ray 2017-09-28 17:00:06

Utilisation de python 2.6 + formatage nouveau style (Car %-style est obsolète):

>>> "{0}".format(float("{0:.1g}".format(1216)))
'1000.0'
>>> "{0}".format(float("{0:.1g}".format(0.00356)))
'0.004'

En python 2.7+, vous pouvez omettre le premier 0s.

0
répondu eddygeek 2018-01-08 13:34:33

Si vous voulez arrondir sans impliquer de chaînes, le lien que j'ai trouvé enterré dans les commentaires ci-dessus:

Http://code.activestate.com/lists/python-tutor/70739/

Me semble le mieux. Ensuite, lorsque vous imprimez avec des descripteurs de formatage de chaîne, vous obtenez une sortie raisonnable et vous pouvez utiliser la représentation numérique à d'autres fins de calcul.

Le code du lien est un trois liner: def, doc et return. Il a un bug: vous devez vérifier pour exploser logarithme. C'est facile. Comparez l'entrée à sys.float_info.min. La solution complète est:

import sys,math

def tidy(x, n):
"""Return 'x' rounded to 'n' significant digits."""
y=abs(x)
if y <= sys.float_info.min: return 0.0
return round( x, int( n-math.ceil(math.log10(y)) ) )

Cela fonctionne pour n'importe quelle valeur numérique scalaire, et n peut être un float Si vous avez besoin de déplacer la réponse pour une raison quelconque. Vous pouvez réellement pousser la limite à:

sys.float_info.min*sys.float_info.epsilon

Sans provoquer d'erreur, si pour une raison quelconque vous travaillez avec des valeurs minuscules.

0
répondu getting_sleepy 2018-04-13 06:49:58

Cette fonction effectue un tour normal si le nombre est supérieur à 10**(- decimal_positions), sinon elle ajoute plus de décimales jusqu'à ce que le nombre de positions décimales significatives soit atteint:

def smart_round(x, decimal_positions):
    dp = - int(math.log10(abs(x))) if x != 0.0 else int(0)
    return round(float(x), decimal_positions + dp if dp > 0 else decimal_positions)

J'espère que ça aide.

0
répondu Ettore Galli 2018-07-19 11:50:52