Zaokroževanje decimalk in celih števil v Pythonu z “round” in “Decimal.quantize

Posel

V nadaljevanju je razloženo, kako v Pythonu zaokrožiti števila z zaokroževanjem ali zaokroževanjem na sodo število. Predpostavlja se, da so števila tipa float ali int s plavajočo vejico.

  • vgrajena funkcija (npr. v programskem jeziku): round()
    • Decimalna števila zaokrožite na poljubno število števk.
    • zaokrožite cela števila na poljubno število števk.
    • round() zaokroži na sodo število in ne na skupno zaokroževanje
  • standardna knjižnicadecimal quantize()
    • DecimalUstvarjanje predmeta
    • zaokroževanje decimalk na poljubno število števk in zaokroževanje na sodo število
    • Zaokroževanje celih števil na poljubno število števk in zaokroževanje na sodo število
  • Opredelitev nove funkcije
    • Decimalna števila zaokrožite na poljubno število števk.
    • zaokrožite cela števila na poljubno število števk
    • Opomba: Za negativne vrednosti

Upoštevajte, da vgrajena funkcija round ni splošno zaokroževanje, temveč zaokroževanje na sodo število, kot je navedeno zgoraj. Za podrobnosti glejte spodaj.

vgrajena funkcija (npr. v programskem jeziku): round()

Funkcija Round() je na voljo kot vgrajena funkcija. Uporabite jo lahko brez uvoza modulov.

Prvi argument je izvirno število, drugi argument pa je število števk (na koliko števk zaokrožiti).

Decimalna števila zaokrožite na poljubno število števk.

V nadaljevanju je prikazan primer obdelave tipa float s plavajočo vejico.

Če je drugi argument izpuščen, se zaokroži na celo število. Tudi tip postane celoštevilski tip int.

f = 123.456

print(round(f))
# 123

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

Če je naveden drugi argument, vrne tip float s plavajočo vejico.

Če je določeno pozitivno celo število, je določeno decimalno mesto; če je določeno negativno celo število, je določeno mesto celega števila. -1 zaokroži na najbližjo desetino, -2 zaokroži na najbližjo stotino, 0 pa zaokroži na celo število (prvo mesto), vendar vrne tip float, za razliko od izpuščenega primera.

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

zaokrožite cela števila na poljubno število števk.

V nadaljevanju je prikazan primer obdelave za celoštevilsko vrsto int.

Če je drugi argument izpuščen ali če je navedena vrednost 0 ali pozitivno celo število, se vrne prvotna vrednost. Če je navedeno negativno celo število, se vrednost zaokroži na ustrezno celoštevilsko številko. V obeh primerih se vrne celoštevilski tip 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() zaokroži na sodo število in ne na skupno zaokroževanje

Upoštevajte, da zaokroževanje z vgrajeno funkcijo round() v Pythonu 3 zaokroži na sodo število in ne na splošno zaokroževanje.

Kot je zapisano v uradni dokumentaciji, se 0,5 zaokroži na 0, 5 se zaokroži na 0 in tako naprej.

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

Definicija zaokroževanja na sodo število je naslednja.

Če je delež manjši od 0,5, ga zaokrožite navzdol; če je delež večji od 0,5, ga zaokrožite navzgor; če je delež natanko 0,5, ga zaokrožite do sodega števila med zaokroževanjem navzdol in navzgor.
Rounding – Wikipedia

0,5 ni vedno okrnjen.

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

V nekaterih primerih opredelitev zaokroževanja na sodo število ne velja niti za obdelavo po dveh decimalnih mestih.

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

To je posledica dejstva, da decimalk ni mogoče natančno predstaviti kot števil s plavajočo vejico, kot je navedeno v uradni dokumentaciji.

Obnašanje funkcije round() za števila s plavajočo vejico vas lahko preseneti:Če na primer zaokrožite(2,675, 2), dobite 2,67 namesto pričakovanih 2,68. To ni napaka.:To je posledica dejstva, da večine decimalk ni mogoče natančno predstaviti s števili s plavajočo vejico.
round() — Built-in Functions — Python 3.10.2 Documentation

Če želite doseči splošno zaokroževanje ali natančno zaokroževanje decimalk na sodo število, lahko uporabite standardno knjižnico decimal quantize (opisano spodaj) ali določite novo funkcijo.

Upoštevajte tudi, da funkcija round() v Pythonu 2 ni zaokroževanje na sodo število, temveč zaokroževanje.

quantize() standardne knjižnice decimal

Modul decimal standardne knjižnice lahko uporabite za obdelavo natančnih decimalnih števil s plavajočo vejico.

Z metodo quantize() modula decimal je mogoče zaokrožiti števila z določitvijo načina zaokroževanja.

Nastavljene vrednosti za argument zaokroževanje metode quantize() imajo naslednji pomen.

  • ROUND_HALF_UP:Splošno zaokroževanje
  • ROUND_HALF_EVEN:Zaokroževanje na sodo število

Modul decimal je standardna knjižnica, zato ni potrebna dodatna namestitev, vendar je uvoz potreben.

from decimal import Decimal, ROUND_HALF_UP, ROUND_HALF_EVEN

Ustvarjanje predmeta Decimal

S funkcijo Decimal() lahko ustvarite predmete vrste Decimal.

Če kot argument navedete tip float, lahko vidite, kako se vrednost dejansko obravnava.

print(Decimal(0.05))
# 0.05000000000000000277555756156289135105907917022705078125

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

Kot je prikazano v primeru, se 0,05 ne obravnava kot natanko 0,05. To je razlog, da vgrajena funkcija round(), opisana zgoraj, zaokroži na drugačno vrednost, kot je pričakovana za decimalne vrednosti, vključno z 0,05 v primeru.

Ker je 0,5 polovica (-1-kratnik 2), ga lahko natančno izrazimo v dvojiškem zapisu.

print(Decimal(0.5))
# 0.5

Če namesto vrste float določite vrsto string str, se ta obravnava kot vrsta Decimal za točno določeno vrednost.

print(Decimal('0.05'))
# 0.05

zaokroževanje decimalk na poljubno število števk in zaokroževanje na sodo število

Iz objekta tipa Decimal pokličite quantize(), da zaokrožite vrednost.

Prvi argument funkcije quantize() je niz z enakim številom števk, kot je število števk, ki jih želite najti, na primer '0,1' ali '0,01'.

Poleg tega argument ROUNDING določa način zaokroževanja; če je naveden ROUND_HALF_UP, se uporabi splošno zaokroževanje.

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

Za razliko od vgrajene funkcije round() se 0,5 zaokroži na 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

Če je argument zaokroževanje nastavljen na ROUND_HALF_EVEN, se izvede zaokroževanje na sodo število kot pri vgrajeni funkciji round().

Kot je navedeno zgoraj, če je kot argument funkcije Decimal() naveden tip float s plavajočo vejico, se obravnava kot objekt Decimal z vrednostjo, ki je enaka dejanski vrednosti tipa float, zato bo rezultat uporabe metode quantize() drugačen od pričakovanega, tako kot pri vgrajeni funkciji 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

Če je argument funkcije Decimal() določen kot niz tipa str, se obravnava kot objekt Decimal s točno takšno vrednostjo, zato je rezultat pričakovan.

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

Ker tip float pravilno obdela vrednost 0,5, pri zaokroževanju na celo število ni težav z navedbo tipa float kot argumenta funkcije Decimal(), vendar je pri zaokroževanju na decimalno mesto varneje navesti tip string str.

Na primer 2,675 je dejansko 2,67499…. v vrsti float. Če torej želite zaokrožiti na dve decimalni mesti, morate funkciji Decimal() navesti niz, sicer se bo rezultat razlikoval od pričakovanega, ne glede na to, ali boste zaokrožili na najbližje celo število (ROUND_HALF_UP) ali na sodo število (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

Upoštevajte, da metoda quantize() vrne število decimalnega tipa, zato ga morate, če želite delovati s številom tipa float, pretvoriti v tip float z uporabo metode float(), sicer bo prišlo do napake.

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

Zaokroževanje celih števil na poljubno število števk in zaokroževanje na sodo število

Če želite zaokrožiti na celoštevilsko številko, če kot prvi argument navedete vrednost '10', ne boste dobili želenega rezultata.

i = 99518

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

To je zato, ker funkcija quantize() izvaja zaokroževanje glede na eksponent predmeta Decimal, vendar je eksponent predmeta Decimal('10') 0 in ne 1.

Določite lahko poljuben eksponent, tako da uporabite niz E kot eksponent (npr. '1E1'). Eksponentni eksponent lahko preverite v metodi as_tuple.

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)

V tem primeru bo rezultat v eksponentnem zapisu z uporabo E. Če želite uporabiti običajni zapis ali če želite po zaokroževanju operirati s celoštevilskim tipom int, uporabite int() za pretvorbo rezultata.

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

Če je argument zaokroževanje nastavljen na ROUND_HALF_UP, se izvede splošno zaokroževanje, npr. 5 se zaokroži na 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

Seveda ni težav, če ga določite kot niz.

Opredelitev nove funkcije

Metoda z uporabo decimalnega modula je natančna in varna, če pa vam pretvorba tipov ne ustreza, lahko za splošno zaokroževanje določite novo funkcijo.

To lahko storite na več načinov, na primer z naslednjo funkcijo.

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

Če vam ni treba navesti števila števk in vedno zaokrožite na prvo decimalno mesto, lahko uporabite preprostejšo obliko.

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

Če morate biti natančni, je varneje uporabiti decimalno število.

Naslednji podatki so zgolj informativne narave.

Decimalna števila zaokrožite na poljubno število števk.

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

Za razliko od zaokroževanja 0,5 postane 1 v skladu s splošnim zaokroževanjem.

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

zaokrožite cela števila na poljubno število števk

i = 99518

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

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

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

Za razliko od zaokroževanja 5 postane 10 po običajnem zaokroževanju.

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

Opomba: Za negativne vrednosti

V zgornjem primeru funkcije je -0,5 zaokroženo na 0.

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

Obstajajo različni načini zaokroževanja negativnih vrednosti, vendar če želite iz vrednosti -0,5 narediti vrednost -1, jo lahko na primer spremenite na naslednji način

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