Hoe een getal op significante cijfers af te ronden in Python

Ik moet een float afronden om te worden weergegeven in een gebruikersinterface. Bijvoorbeeld tot één significant cijfer:

1234 -> 1000

0,12 -> 0,1

0,012 -> 0,01

0,062 -> 0,06

6253 -> 6000

1999 -> 2000

Is er een leuke manier om dit te doen met behulp van de Python-bibliotheek, of moet ik het zelf schrijven?


Antwoord 1, autoriteit 100%

U kunt negatieve getallen gebruiken om gehele getallen af te ronden:

>>> round(1234, -3)
1000.0

Dus als u alleen het meest significante cijfer nodig heeft:

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

Je zult er waarschijnlijk voor moeten zorgen dat float in integer wordt omgezet als het groter is dan 1.


Antwoord 2, autoriteit 70%

%g in tekenreeksopmaak zal een float opmaken die wordt afgerond op een aantal significante cijfers. Het zal soms de wetenschappelijke notatie ‘e’ gebruiken, dus converteer de afgeronde tekenreeks terug naar een float en vervolgens door %s tekenreeksopmaak.

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

Antwoord 3, autoriteit 38%

Als u meer dan 1 significant decimaalteken wilt hebben (anders hetzelfde als 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

Antwoord 4, autoriteit 28%

f'{float(f"{i:.1g}"):g}'
# Or with Python <3.6,
'{:g}'.format(float('{:.1g}'.format(i)))

Deze oplossing is anders dan alle andere omdat:

  1. het lost exactde OP-vraag op
  2. het heeft geeneen extra pakket
  3. nodig

  4. het heeft geeneen door de gebruiker gedefinieerde hulpfunctieof wiskundige bewerking
  5. nodig

Voor een willekeurig aantal nsignificante cijfers kunt u het volgende gebruiken:

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

Testen:

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

OPMERKING : Met deze oplossing is het niet mogelijk om het aantal significante cijfers van de input aan te passen, omdat er geen standaard manier is om cijfers te onderscheiden met verschillende aantallen trailing nullen (3.14 == 3.1400). Als u dit moet doen, dan is niet-standaard functies zoals die in de to-precisie pakket is nodig.


Antwoord 5, Autoriteit 7%

Ik heb het pakket to-precisie die doet wat u wilt. Hiermee kunt u uw cijfers meer of minder belangrijke cijfers geven.

Het geeft ook standaard, wetenschappelijke en engineering notatie uit met een bepaald aantal significante cijfers.

In het geaccepteerde antwoord is er de lijn

>>> round_to_1(1234243)
1000000.0

die daadwerkelijk 8 sig-vijgen aangeeft. Voor het nummer 1234243 geeft mijn bibliotheek slechts één belangrijk cijfer weer:

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

Het zal ook het laatste significante cijfer afronden en kan automatisch kiezen welke notatie te gebruiken als een notatie niet is opgegeven:

>>> to_precision(599, 2)
'600'
>>> to_precision(1164, 2)
'1.2e3'

Antwoord 6, Autoriteit 5%

Om de vraag direct te beantwoorden, hier is mijn versie met naamgeving uit de R-functie:

import math
def signif(x, digits=6):
    if x == 0 or not math.isfinite(x):
        return x
    digits -= math.ceil(math.log10(abs(x)))
    return round(x, digits)

Mijn belangrijkste reden voor het plaatsen van dit antwoord zijn de opmerkingen waarin wordt geklaagd dat “0,075” wordt afgerond op 0,07 in plaats van 0,08. Dit is te wijten, zoals aangegeven door “Novice C”, aan een combinatie van drijvende-kommaberekeningen met beide eindige precisie en een basis-2-representatie. Het getal dat het dichtst bij 0,075 ligt dat daadwerkelijk kan worden weergegeven, is iets kleiner, dus afronding komt anders uit dan je naïef zou verwachten.

Houd er ook rekening mee dat dit van toepassing is op elk gebruik van niet-decimale drijvende-kommaberekeningen, bijv. C en Java hebben beide hetzelfde probleem.

Om meer in detail te laten zien, vragen we Python om het getal in “hex” formaat te formatteren:

0.075.hex()

wat ons geeft: 0x1.3333333333333p-4. De reden hiervoor is dat de normale decimale representatie vaak afronding omvat en daarom niet is hoe de computer het getal werkelijk “ziet”. Als je dit formaat niet gewend bent, zijn een paar handige referenties de Python-documentenen de C-standaard.

Om een beetje te laten zien hoe deze getallen werken, kunnen we terugkeren naar ons beginpunt door het volgende te doen:

0x13333333333333 / 16**13 * 2**-4

die 0.075zou moeten afdrukken. 16**13is omdat er 13 hexadecimale cijfers achter de komma staan, en 2**-4is omdat hexadecimale exponenten grondtal-2 zijn.

Nu we een idee hebben van hoe floats worden weergegeven, kunnen we de module Decimalgebruiken om ons wat meer precisie te geven en ons te laten zien wat er aan de hand is:

from decimal import Decimal
Decimal(0x13333333333333) / 16**13 / 2**4

geven: 0.07499999999999999722444243844en hopelijk uitleggen waarom round(0.075, 2)resulteert in 0.07


Antwoord 7, autoriteit 3%

Om een geheel getal op 1 significant cijfer af te ronden, is het basisidee om het te converteren naar een drijvende komma met 1 cijfer voor het punt en dat af te ronden en het vervolgens terug te converteren naar de oorspronkelijke gehele grootte.

Om dit te doen, moeten we de grootste macht van 10 minder dan het gehele getal weten. We kunnen hiervoor verdieping van de log 10 functie gebruiken.

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)

Antwoord 8, autoriteit 2%

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

hopelijk het beste van alle bovenstaande antwoorden (minus kan het als een line lambda;)). Heb nog niet verkend, voel je vrij om dit antwoord te bewerken:

round_to_n(1e15 + 1, 11)  # 999999999999999.9

Antwoord 9, Autoriteit 2%

Ik heb de oplossing van Indgar gewijzigd om negatieve cijfers en kleine nummers (inclusief nul) aan te pakken.

from math import log10, floor
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)

Antwoord 10, Autoriteit 2%

Als u wilt rond zonder strings te betrekken, vond de link die ik vond in de bovenstaande opmerkingen:

http://code.activestate.com/livists/pyphon-tutor/ 70739 /

slaat me zo goed mogelijk. Wanneer u vervolgens afdrukt met elke serre-opmaak descriptors, krijgt u een redelijke uitvoer en kunt u de numerieke weergave voor andere berekeningsdoeleinden gebruiken.

De code op de link is een drie voering: DEF, DOC en RETOUR. Het heeft een bug: u moet controleren op exploderende logaritmen. Dat is makkelijk. Vergelijk de ingang op sys.float_info.min. De volledige oplossing is:

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

Het werkt voor elke scalaire numerieke waarde, en n kan een floatzijn als je het antwoord om de een of andere reden moet verschuiven. Je kunt echt de limiet verleggen om:

sys.float_info.min*sys.float_info.epsilon

zonder een fout te veroorzaken, als u om de een of andere reden met minuscule waarden werkt.


Antwoord 11

Ik kan niets bedenken dat dit out-of-the-box aan zou kunnen. Maar het wordt redelijk goed afgehandeld voor getallen met drijvende komma.

>>> round(1.2322, 2)
1.23

Gehele getallen zijn lastiger. Ze worden niet als basis 10 in het geheugen opgeslagen, dus belangrijke plaatsen zijn niet vanzelfsprekend. Het is echter vrij triviaal om te implementeren als ze eenmaal een string zijn.

Of voor gehele getallen:

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

Als je een functie wilt maken die elk getal kan verwerken, zou ik ze het liefst allebei naar strings converteren en naar een decimaalteken zoeken om te beslissen wat je moet doen:

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

Een andere optie is om te controleren op type. Dit zal veel minder flexibel zijn en zal waarschijnlijk niet goed spelen met andere getallen zoals Decimalobjecten:

>>> def roundall2(n, sigfigs):
...   if type(n) is int: return intround(n, sigfigs)
...   else: return round(n, sigfigs)

Antwoord 12

Het gepost antwoord was de beste beschikbare toen gegeven, maar het heeft een aantal beperkingen en produceert niet technisch correcte significante cijfers.

Numpy.Format_Float_Positional ondersteunt het gewenste gedrag direct. Het volgende fragment retourneert de vlotter xgeformatteerd naar 4 significante cijfers, met wetenschappelijke notatie onderdrukt.

import numpy as np
x=12345.6
np.format_float_positional(x, precision=4, unique=False, fractional=False, trim='k')
> 12340.

Antwoord 13

De sigfig pakket / bibliotheek dekt dit. Na installeren U kunt het volgende doen:

>>> from sigfig import round
>>> round(1234, 1)
1000
>>> round(0.12, 1)
0.1
>>> round(0.012, 1)
0.01
>>> round(0.062, 1)
0.06
>>> round(6253, 1)
6000
>>> round(1999, 1)
2000

Antwoord 14

Python 2.6+ nieuwe stijl formatteren (AS% -stijl is verouderd):

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

In Python 2.7+ kunt u de toonaangevende 0s.

weglaten


Antwoord 15

Ik heb dit ook ingeschakeld, maar ik had controle over het afrondingstype. Daarom schreef ik een snelle functie (zie hieronder code) die waarde, afrondingstype en gewenste significante cijfers in aanmerking kunnen nemen.

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

Antwoord 16

Deze functie doet een normale ronde als het nummer groter is dan 10 ** (- decimal_positions), anders voegt er meer decimaal toe totdat het aantal zinvolle decimale posities wordt bereikt:

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)

Hopelijk helpt het.


Antwoord 17

https://stackoverflow.com/users/1391441/gabriel, lost het volgende uw bezorgdheid over rnd( .075, 1)?
Voorbehoud: retourneert waarde als een float

def round_to_n(x, n):
    fmt = '{:1.' + str(n) + 'e}'    # gives 1.n figures
    p = fmt.format(x).split('e')    # get mantissa and exponent
                                    # round "extra" figure off mantissa
    p[0] = str(round(float(p[0]) * 10**(n-1)) / 10**(n-1))
    return float(p[0] + 'e' + p[1]) # convert str to float
>>> round_to_n(750, 2)
750.0
>>> round_to_n(750, 1)
800.0
>>> round_to_n(.0750, 2)
0.075
>>> round_to_n(.0750, 1)
0.08
>>> math.pi
3.141592653589793
>>> round_to_n(math.pi, 7)
3.141593

Antwoord 18

Dit retourneert een tekenreeks, zodat resultaten zonder breuken en kleine waarden die anders in de E-notatie zouden verschijnen, correct worden weergegeven:

def sigfig(x, num_sigfig):
    num_decplace = num_sigfig - int(math.floor(math.log10(abs(x)))) - 1
    return '%.*f' % (num_decplace, round(x, num_decplace))

Antwoord 19

Gegeven een vraag die zo grondig beantwoord is, waarom zou je er dan niet nog een toevoegen

Dit past iets beter bij mijn esthetiek, hoewel veel van bovenstaande vergelijkbaar zijn

import numpy as np
number=-456.789
significantFigures=4
roundingFactor=significantFigures - int(np.floor(np.log10(np.abs(number)))) - 1
rounded=np.round(number, roundingFactor)
string=rounded.astype(str)
print(string)

Dit werkt voor individuele getallen en numpy arrays, en zou prima moeten werken voor negatieve getallen.

Er is nog een extra stap die we kunnen toevoegen – np.round() geeft een decimaal getal terug, zelfs als afgerond een geheel getal is (d.w.z. voor significante cijfers=2 verwachten we misschien -460 terug te krijgen, maar in plaats daarvan krijgen we -460,0). We kunnen deze stap toevoegen om daarvoor te corrigeren:

if roundingFactor<=0:
    rounded=rounded.astype(int)

Helaas werkt deze laatste stap niet voor een reeks getallen – ik laat dat aan u, beste lezer, over om erachter te komen of u dat nodig heeft.


Antwoord 20

import math
  def sig_dig(x, n_sig_dig):
      num_of_digits = len(str(x).replace(".", ""))
      if n_sig_dig >= num_of_digits:
          return x
      n = math.floor(math.log10(x) + 1 - n_sig_dig)
      result = round(10 ** -n * x) * 10 ** n
      return float(str(result)[: n_sig_dig + 1])
    >>> sig_dig(1234243, 3)
    >>> sig_dig(243.3576, 5)
        1230.0
        243.36

Antwoord 21

De meeste van deze antwoorden hebben betrekking op de wiskundige, decimale en/of numpy invoer- of uitvoerwaarden als tekenreeksen. Hier is een eenvoudige oplossing in basispython die zowel grote als kleine getallen verwerkt en een float uitvoert:

def sig_fig_round(number, digits=3):
    power = "{:e}".format(number).split('e')[1]
    return round(number, -(int(power) - digits))

Other episodes