Como arredondar um número para números significativos em Python

Preciso de dar a volta a um carro alegórico para ser exibido numa IU. Por exemplo, para um número significativo:

1234 -> 1000

0.12 -> 0.1

0.012 -> 0.01

0.062 -> 0.06

6253 -> 6000

1999 -> 2000

Existe uma boa maneira de fazer isto usando a biblioteca Python, ou tenho de ser eu a escrevê-la?

Author: Peter Graham, 2010-08-05

13 answers

Pode usar números negativos para arredondar inteiros:

>>> round(1234, -3)
1000.0

Assim, se precisar apenas do algarismo mais significativo:

>>> 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
Provavelmente terás de tratar de transformar o carro flutuante em número inteiro, se for maior que 1.
 119
Author: Evgeny, 2016-01-07 06:43:05

%g na formatação de texto irá formatar um flutuador arredondado para um número de figuras significativas. Ele às vezes vai usar notação científica 'e', então converter a cadeia arredondada de volta para um float, em seguida, através de formatação de cadeia de %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
Author: Peter Graham, 2010-08-05 04:24:19

Se quiser ter outra casa decimal significativa (caso contrário, a mesma 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
Author: indgar, 2017-03-17 16:01:42

Para arredondar um inteiro para 1 figura significativa a ideia básica é convertê-lo para um ponto flutuante com 1 dígito antes do ponto e arredondá-lo, em seguida, convertê-lo de volta para o seu tamanho inteiro original.

Para fazer isto, precisamos de saber o maior poder de 10 menos do que o inteiro. Podemos usar o piso da função log 10 para isso.
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
Author: Cris Stringfellow, 2012-03-04 18:18:19

Modifiquei a solução do indgar para lidar com números negativos e números pequenos (incluindo zero).

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
Author: user2829661, 2016-03-26 19:50:51
print('{:g}'.format(float('{:.1g}'.format(12.345))))

Esta solução é diferente de todas as outras porque:

  1. Issoexactamente resolve a questão OP
  2. não é necessário nenhum pacote extra
  3. não necessita de nenhuma função auxiliar definida pelo utilizador ou operação matemática

Para um número arbitrário n de números significativos, pode utilizar:

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

Teste:

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

Nota com isto solução, não é possível adaptar dinamicamente o número de figuras significativas da entrada porque não existe uma maneira padrão de distinguir números com diferentes números de zeros finais (3.14 == 3.1400). Se você precisar fazer isso, então funções não-padrão como as fornecidas no Pacote to-precision são necessárias.

 4
Author: 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
([3]}espero que tomando o melhor de todas as respostas acima (menos ser capaz de colocá-lo como uma lambda de uma linha;). Ainda não explorou, sinta-se à vontade para editar esta resposta:
round_to_n(1e15 + 1, 11)  # 999999999999999.9
 3
Author: AJP, 2016-07-13 22:17:22

Eu criei o pacote to-precision que faz o que você quer. Ele permite que você dê seus números mais ou menos significativos.

Também produz notação padrão, científica e de engenharia com um número especificado de números significativos.

Na resposta aceite há a linha

>>> round_to_1(1234243)
1000000.0

Que especifica de facto 8 figos sig. Para o número 1234243, A Minha Biblioteca apenas apresenta uma figura significativa:

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

Também rodeará o última figura significativa e pode escolher automaticamente que Notação usar se uma notação não for especificada:

>>> to_precision(599, 2)
'600'
>>> to_precision(1164, 2)
'1.2e3'
 3
Author: William Rusnack, 2018-07-29 23:45:58
Não consigo pensar em nada que possa resolver isto. Mas é bastante bem tratado para números de vírgula flutuante.
>>> round(1.2322, 2)
1.23
Os inteiros são mais complicados. Eles não são armazenados como base 10 em memória, então lugares significativos não é uma coisa natural a fazer. É bastante trivial implementar uma vez que eles são uma corda embora.

Ou para inteiros:

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

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

Se você gostaria de criar uma função que lida com qualquer número, minha preferência seria convertê - los ambos para strings e procurar uma casa decimal para decidir o que fazer:

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

Outra opção é verificar o tipo. Isto será muito menos flexível, e provavelmente não jogará bem com outros números como Decimal objectos:

>>> def roundall2(n, sigfigs):
...   if type(n) is int: return intround(n, sigfigs)
...   else: return round(n, sigfigs)
 1
Author: Tim McNamara, 2010-08-05 02:28:48
Também encontrei isto, mas precisava de controlo sobre o tipo de arredondamento. Assim, eu escrevi uma função rápida (Veja o código abaixo) que pode levar em conta o valor, o tipo de arredondamento e os dígitos significativos desejados.
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
Author: drew.ray, 2017-09-28 17:00:06
 0
Author: eddygeek, 2018-01-08 13:34:33

Se queres andar por aí sem envolver cordas, o link que encontrei enterrado nos comentários acima:

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

Parece-me o melhor. Então, quando você imprime com qualquer descritor de formatação de string, você obtém uma saída razoável, e você pode usar a representação numérica para outros fins de cálculo. O código na ligação é três linhas: def, doc e return. Ele tem um bug: você precisa verificar para explodir logaritmo. Isso é fácil. Compare a entrada com sys.float_info.min. A solução completa é:
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)) ) )

Funciona para qualquer valor numérico escalar, e n pode ser um float Se precisar de mudar a resposta por alguma razão. Você pode realmente empurrar o limite para:

sys.float_info.min*sys.float_info.epsilon

Sem provocar um erro, se por alguma razão estiver a trabalhar com valores minúsculos.

 0
Author: getting_sleepy, 2018-04-13 06:49:58

Esta função faz uma ronda normal se o número for maior que 10 * * (- decimal_posições), caso contrário adiciona mais casas decimais até que o número de posições decimais significativas seja atingido:

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)
Espero que ajude.
 0
Author: Ettore Galli, 2018-07-19 11:50:52