Afrunding af decimaler og hele tal i Python med “round” og “Decimal.quantize

Forretning

I det følgende forklares det, hvordan man afrunder tal i Python ved at afrunde eller afrunde til et lige tal. Tallene antages at være af float- eller int-typen med flydende komma eller heltal.

  • indbygget funktion (f.eks. i et programmeringssprog): round()
    • Afrunde decimaler til et vilkårligt antal cifre.
    • Runder hele tal til et vilkårligt antal cifre.
    • round() runder til et lige tal, ikke til en almindelig afrunding
  • standardbibliotekdecimal quantize()
    • DecimalOprettelse af et objekt
    • Afrunding af decimaltal til et vilkårligt antal cifre og afrunding til lige tal
    • Afrunding af hele tal til et vilkårligt antal cifre og afrunding til lige tal
  • Definer en ny funktion
    • Afrunde decimaler til et vilkårligt antal cifre.
    • Afrunding af hele tal til et vilkårligt antal cifre
    • Bemærk: For negative værdier

Bemærk, at den indbyggede funktion round som nævnt ovenfor ikke er en generel afrunding, men en afrunding til et lige tal. Se nedenfor for nærmere oplysninger.

indbygget funktion (f.eks. i et programmeringssprog): round()

Round() findes som en indbygget funktion. Den kan bruges uden at importere nogen moduler.

Det første argument er det oprindelige tal, og det andet argument er antallet af cifre (hvor mange cifre der skal afrundes til).

Afrunde decimaler til et vilkårligt antal cifre.

Følgende er et eksempel på behandling af float-typen med flydende komma.

Hvis det andet argument udelades, afrundes det til et heltal. Typen bliver også en int-type af typen heltal.

f = 123.456

print(round(f))
# 123

print(type(round(f)))
# <class 'int'>

Hvis det andet argument er angivet, returneres en floating-point float-type.

Hvis der angives et positivt heltal, angives decimalpladsen; hvis der angives et negativt heltal, angives heltalpladsen. -1 runder til nærmeste tiendedel, -2 runder til nærmeste hundrededel, og 0 runder til et heltal (første sted), men returnerer en float-type, i modsætning til hvis den udelades.

print(round(f, 1))
# 123.5

print(round(f, 2))
# 123.46

print(round(f, -1))
# 120.0

print(round(f, -2))
# 100.0

print(round(f, 0))
# 123.0

print(type(round(f, 0)))
# <class 'float'>

Runder hele tal til et vilkårligt antal cifre.

Følgende er et eksempel på behandling af typen integer int.

Hvis det andet argument udelades, eller hvis der angives 0 eller et positivt heltal, returneres den oprindelige værdi som den er. Hvis der angives et negativt heltal, afrundes værdien til det tilsvarende heltal. I begge tilfælde returneres et heltal af typen int.

i = 99518

print(round(i))
# 99518

print(round(i, 2))
# 99518

print(round(i, -1))
# 99520

print(round(i, -2))
# 99500

print(round(i, -3))
# 100000

round() runder til et lige tal, ikke til en almindelig afrunding

Bemærk, at afrunding med den indbyggede round()-funktion i Python 3 runder til et lige tal og ikke til en generel afrunding.

Som det fremgår af den officielle dokumentation, afrundes 0,5 til 0, 5 afrundes til 0 og så videre.

print('0.4 =>', round(0.4))
print('0.5 =>', round(0.5))
print('0.6 =>', round(0.6))
# 0.4 => 0
# 0.5 => 0
# 0.6 => 1

print('4 =>', round(4, -1))
print('5 =>', round(5, -1))
print('6 =>', round(6, -1))
# 4 => 0
# 5 => 0
# 6 => 10

Definitionen af afrunding til et lige tal er som følger.

Hvis brøken er mindre end 0,5, skal den afrundes nedad; hvis brøken er større end 0,5, skal den afrundes opad; hvis brøken er præcis 0,5, skal den afrundes opad til det lige tal mellem afrunding nedad og afrunding opad.
Rounding – Wikipedia

0,5 er ikke altid afkortet.

print('0.5 =>', round(0.5))
print('1.5 =>', round(1.5))
print('2.5 =>', round(2.5))
print('3.5 =>', round(3.5))
print('4.5 =>', round(4.5))
# 0.5 => 0
# 1.5 => 2
# 2.5 => 2
# 3.5 => 4
# 4.5 => 4

I nogle tilfælde gælder definitionen af afrunding til et lige tal ikke engang for behandling efter to decimaler.

print('0.05 =>', round(0.05, 1))
print('0.15 =>', round(0.15, 1))
print('0.25 =>', round(0.25, 1))
print('0.35 =>', round(0.35, 1))
print('0.45 =>', round(0.45, 1))
# 0.05 => 0.1
# 0.15 => 0.1
# 0.25 => 0.2
# 0.35 => 0.3
# 0.45 => 0.5

Dette skyldes, at decimaltal ikke kan repræsenteres nøjagtigt som flydende punkttal, som det fremgår af den officielle dokumentation.

Opførslen af round() for flydende tal kan overraske dig:F.eks. vil runde(2,675, 2) give 2,67 i stedet for 2,68 som forventet. Dette er ikke en fejl.:Dette skyldes, at de fleste decimaltal ikke kan repræsenteres nøjagtigt med flydende punkttal.
round() — Built-in Functions — Python 3.10.2 Documentation

Hvis du ønsker at opnå generel afrunding eller præcis afrunding af decimaltal til lige tal, kan du bruge standardbibliotekets decimal quantize (beskrevet nedenfor) eller definere en ny funktion.

Bemærk også, at round() i Python 2 ikke runder til et lige tal, men afrunder.

quantize() i standardbibliotekets decimal

Decimalmodulet i standardbiblioteket kan bruges til at håndtere nøjagtige decimaltal med flydende kommaer.

Ved hjælp af metoden quantize() i decimalmodulet er det muligt at afrunde tal ved at angive afrundingsmetoden.

De indstillede værdier for argumentet afrunding i quantize()-metoden har henholdsvis følgende betydninger.

  • ROUND_HALF_UP:Generel afrunding
  • ROUND_HALF_EVEN:Afrunding til lige tal

Decimalmodulet er et standardbibliotek, så der kræves ingen yderligere installation, men det er nødvendigt at importere det.

from decimal import Decimal, ROUND_HALF_UP, ROUND_HALF_EVEN

Oprettelse af et decimalobjekt

Decimal() kan bruges til at oprette objekter af typen Decimal.

Hvis du angiver en float-type som et argument, kan du se, hvad værdien faktisk behandles som.

print(Decimal(0.05))
# 0.05000000000000000277555756156289135105907917022705078125

print(type(Decimal(0.05)))
# <class 'decimal.Decimal'>

Som det fremgår af eksemplet, behandles 0,05 ikke som nøjagtig 0,05. Dette er grunden til, at den indbyggede funktion round(), der er beskrevet ovenfor, runder til en anden værdi end forventet for decimalværdier, herunder 0,05 i eksemplet.

Da 0,5 er halvdelen (-1 potens af 2), kan det udtrykkes nøjagtigt i binær notation.

print(Decimal(0.5))
# 0.5

Hvis du angiver stringtypen str i stedet for float-typen, vil den blive behandlet som decimaltypen for den nøjagtige værdi.

print(Decimal('0.05'))
# 0.05

Afrunding af decimaltal til et vilkårligt antal cifre og afrunding til lige tal

Kald quantize() fra et objekt af typen Decimal for at afrunde værdien.

Det første argument i quantize() er en streng med samme antal cifre som det antal cifre, du ønsker at finde, f.eks. “0.1” eller “0.01”.

Desuden angiver argumentet ROUNDING afrundingsmetoden; hvis ROUND_HALF_UP er angivet, anvendes generel afrunding.

f = 123.456

print(Decimal(str(f)).quantize(Decimal('0'), rounding=ROUND_HALF_UP))
# 123

print(Decimal(str(f)).quantize(Decimal('0.1'), rounding=ROUND_HALF_UP))
# 123.5

print(Decimal(str(f)).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP))
# 123.46

I modsætning til den indbyggede funktion round() bliver 0,5 afrundet til 1.

print('0.4 =>', Decimal(str(0.4)).quantize(Decimal('0'), rounding=ROUND_HALF_UP))
print('0.5 =>', Decimal(str(0.5)).quantize(Decimal('0'), rounding=ROUND_HALF_UP))
print('0.6 =>', Decimal(str(0.6)).quantize(Decimal('0'), rounding=ROUND_HALF_UP))
# 0.4 => 0
# 0.5 => 1
# 0.6 => 1

Hvis argumentet afrunding er sat til ROUND_HALF_EVEN, afrundes der til lige tal som i den indbyggede funktion round().

Som nævnt ovenfor, hvis en float-type med flydende komma er angivet som argument for Decimal(), behandles det som et Decimal-objekt med en værdi svarende til den faktiske værdi af float-typen, så resultatet af at bruge quantize()-metoden vil være anderledes end forventet, ligesom den indbyggede funktion round().

print('0.05 =>', round(0.05, 1))
print('0.15 =>', round(0.15, 1))
print('0.25 =>', round(0.25, 1))
print('0.35 =>', round(0.35, 1))
print('0.45 =>', round(0.45, 1))
# 0.05 => 0.1
# 0.15 => 0.1
# 0.25 => 0.2
# 0.35 => 0.3
# 0.45 => 0.5

print('0.05 =>', Decimal(0.05).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
print('0.15 =>', Decimal(0.15).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
print('0.25 =>', Decimal(0.25).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
print('0.35 =>', Decimal(0.35).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
print('0.45 =>', Decimal(0.45).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
# 0.05 => 0.1
# 0.15 => 0.1
# 0.25 => 0.2
# 0.35 => 0.3
# 0.45 => 0.5

Hvis argumentet for Decimal() er angivet som en streng af typen str, behandles det som et Decimal-objekt med præcis denne værdi, så resultatet er som forventet.

print('0.05 =>', Decimal(str(0.05)).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
print('0.15 =>', Decimal(str(0.15)).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
print('0.25 =>', Decimal(str(0.25)).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
print('0.35 =>', Decimal(str(0.35)).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
print('0.45 =>', Decimal(str(0.45)).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
# 0.05 => 0.0
# 0.15 => 0.2
# 0.25 => 0.2
# 0.35 => 0.4
# 0.45 => 0.4

Da 0,5 kan håndteres korrekt af float-typen, er der ingen problemer med at angive float-typen som argument for Decimal(), når der afrundes til et heltal, men det er mere sikkert at angive stringtypen str, når der afrundes til et decimaltal.

F.eks. er 2,675 faktisk 2,67499…. i float-typen. Hvis du vil afrunde til to decimaler, skal du derfor angive en streng til Decimal(), ellers vil resultatet være forskelligt fra det forventede resultat, uanset om du runder til det nærmeste hele tal (ROUND_HALF_UP) eller til et lige tal (ROUND_HALF_EVEN).

print(Decimal(2.675))
# 2.67499999999999982236431605997495353221893310546875

print(Decimal(2.675).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP))
# 2.67

print(Decimal(str(2.675)).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP))
# 2.68

print(Decimal(2.675).quantize(Decimal('0.01'), rounding=ROUND_HALF_EVEN))
# 2.67

print(Decimal(str(2.675)).quantize(Decimal('0.01'), rounding=ROUND_HALF_EVEN))
# 2.68

Bemærk, at quantize()-metoden returnerer et tal af decimaltypen, så hvis du ønsker at operere med et tal af float-typen, skal du konvertere det til en float-type ved hjælp af float(), ellers opstår der en fejl.

d = Decimal('123.456').quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)

print(d)
# 123.46

print(type(d))
# <class 'decimal.Decimal'>

# print(1.2 + d)
# TypeError: unsupported operand type(s) for +: 'float' and 'decimal.Decimal'

print(1.2 + float(d))
# 124.66

Afrunding af hele tal til et vilkårligt antal cifre og afrunding til lige tal

Hvis du vil afrunde til et helt tal, får du ikke det ønskede resultat, hvis du angiver noget som “10” som det første argument.

i = 99518

print(Decimal(i).quantize(Decimal('10'), rounding=ROUND_HALF_UP))
# 99518

Det skyldes, at quantize() foretager afrunding i henhold til eksponenten for Decimal-objektet, men eksponenten for Decimal('10') er 0 og ikke 1.

Du kan angive en vilkårlig eksponent ved at bruge E som en eksponentstreng (f.eks. “1E1”). Eksponenten eksponent kan kontrolleres i as_tuple-metoden.

print(Decimal('10').as_tuple())
# DecimalTuple(sign=0, digits=(1, 0), exponent=0)

print(Decimal('1E1').as_tuple())
# DecimalTuple(sign=0, digits=(1,), exponent=1)

Som det er nu, vil resultatet være i eksponentiel notation ved hjælp af E. Hvis du ønsker at bruge normal notation, eller hvis du ønsker at operere med et heltal af typen int efter afrunding, skal du bruge int() til at konvertere resultatet.

print(Decimal(i).quantize(Decimal('1E1'), rounding=ROUND_HALF_UP))
# 9.952E+4

print(int(Decimal(i).quantize(Decimal('1E1'), rounding=ROUND_HALF_UP)))
# 99520

print(int(Decimal(i).quantize(Decimal('1E2'), rounding=ROUND_HALF_UP)))
# 99500

print(int(Decimal(i).quantize(Decimal('1E3'), rounding=ROUND_HALF_UP)))
# 100000

Hvis argumentet afrunding er sat til ROUND_HALF_UP, vil der ske en generel afrunding, f.eks. vil 5 blive afrundet til 10.

print('4 =>', int(Decimal(4).quantize(Decimal('1E1'), rounding=ROUND_HALF_UP)))
print('5 =>', int(Decimal(5).quantize(Decimal('1E1'), rounding=ROUND_HALF_UP)))
print('6 =>', int(Decimal(6).quantize(Decimal('1E1'), rounding=ROUND_HALF_UP)))
# 4 => 0
# 5 => 10
# 6 => 10

Der er naturligvis ikke noget problem, hvis du angiver det som en streng.

Definer en ny funktion

Metoden med decimalmodulet er præcis og sikker, men hvis du ikke er fortrolig med typekonvertering, kan du definere en ny funktion for at opnå generel afrunding.

Der er mange mulige måder at gøre dette på, f.eks. følgende funktion.

def my_round(val, digit=0):
    p = 10 ** digit
    return (val * p * 2 + 1) // 2 / p

Hvis du ikke behøver at angive antallet af cifre og altid runder til den første decimal, kan du bruge en enklere form.

my_round_int = lambda x: int((x * 2 + 1) // 2)

Hvis du skal være præcis, er det mere sikkert at bruge decimaltal.

Følgende er kun til orientering.

Afrunde decimaler til et vilkårligt antal cifre.

print(int(my_round(f)))
# 123

print(my_round_int(f))
# 123

print(my_round(f, 1))
# 123.5

print(my_round(f, 2))
# 123.46

I modsætning til afrunding bliver 0,5 til 1 som ved almindelig afrunding.

print(int(my_round(0.4)))
print(int(my_round(0.5)))
print(int(my_round(0.6)))
# 0
# 1
# 1

Afrunding af hele tal til et vilkårligt antal cifre

i = 99518

print(int(my_round(i, -1)))
# 99520

print(int(my_round(i, -2)))
# 99500

print(int(my_round(i, -3)))
# 100000

I modsætning til afrunding bliver 5 til 10 som ved almindelig afrunding.

print(int(my_round(4, -1)))
print(int(my_round(5, -1)))
print(int(my_round(6, -1)))
# 0
# 10
# 10

Bemærk: For negative værdier

I eksempelfunktionen ovenfor afrundes -0,5 til 0.

print(int(my_round(-0.4)))
print(int(my_round(-0.5)))
print(int(my_round(-0.6)))
# 0
# 0
# -1

Der er forskellige måder at tænke på afrunding af negative værdier på, men hvis du vil gøre -0,5 til -1, kan du f.eks. ændre det på følgende måde

import math

def my_round2(val, digit=0):
    p = 10 ** digit
    s = math.copysign(1, val)
    return (s * val * p * 2 + 1) // 2 / p * s

print(int(my_round2(-0.4)))
print(int(my_round2(-0.5)))
print(int(my_round2(-0.6)))
# 0
# -1
# -1
Copied title and URL