Rotunjirea zecimalelor și a numerelor întregi în Python cu „round” și „Decimal.quantize

Afaceri

În cele ce urmează se explică modul de rotunjire a numerelor în Python prin rotunjire sau rotunjire la un număr par. Se presupune că numerele sunt de tip float cu virgulă mobilă sau int întreg.

  • funcție încorporată (de exemplu, în limbajul de programare): round()
    • Rotunjiți zecimalele la orice număr de cifre.
    • Rotunjirea numerelor întregi la orice număr de cifre.
    • round() rotunjește la un număr par, nu la un număr comun de rotunjire
  • biblioteca standarddecimal quantize()
    • DecimalCrearea unui obiect
    • Rotunjirea zecimilor la orice număr de cifre și rotunjirea la numere pare
    • Rotunjirea numerelor întregi la orice număr de cifre și rotunjirea la numere pare
  • Definiți o nouă funcție
    • Rotunjiți zecimalele la orice număr de cifre.
    • Rotunjirea numerelor întregi la orice număr de cifre
    • Notă: Pentru valori negative

Rețineți că, așa cum s-a menționat mai sus, funcția încorporată round nu este o rotunjire generală, ci o rotunjire la un număr par. A se vedea mai jos pentru detalii.

funcție încorporată (de exemplu, în limbajul de programare): round()

Round() este furnizată ca funcție încorporată. Aceasta poate fi utilizată fără a importa niciun modul.

Primul argument este numărul original, iar al doilea argument este numărul de cifre (la câte cifre se rotunjește).

Rotunjiți zecimalele la orice număr de cifre.

Următorul este un exemplu de procesare pentru tipul float în virgulă mobilă.

În cazul în care al doilea argument este omis, acesta este rotunjit la un număr întreg. Tipul devine, de asemenea, un tip int întreg.

f = 123.456

print(round(f))
# 123

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

În cazul în care este specificat al doilea argument, se returnează un tip float în virgulă mobilă.

Dacă se specifică un număr întreg pozitiv, se specifică locul zecimalei; dacă se specifică un număr întreg negativ, se specifică locul numărului întreg. -1 rotunjește la cea mai apropiată zecime, -2 rotunjește la cea mai apropiată sutime, iar 0 rotunjește la un întreg (primul loc), dar returnează un tip float, spre deosebire de cazul în care este omis.

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

Rotunjirea numerelor întregi la orice număr de cifre.

Următorul este un exemplu de procesare pentru tipul integer int.

Dacă al doilea argument este omis sau dacă se specifică 0 sau un număr întreg pozitiv, valoarea originală este returnată ca atare. Dacă se specifică un număr întreg negativ, valoarea este rotunjită la cifra întreagă corespunzătoare. În ambele cazuri, se returnează un întreg de 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() rotunjește la un număr par, nu la un număr comun de rotunjire

Rețineți că rotunjirea cu funcția încorporată round() din Python 3 rotunjește la un număr par, nu la o rotunjire generală.

Așa cum este scris în documentația oficială, 0,5 este rotunjit la 0, 5 este rotunjit la 0, și așa mai departe.

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

Definiția rotunjirii la un număr par este următoarea.

Dacă fracția este mai mică de 0,5, rotunjiți-o în jos; dacă fracția este mai mare de 0,5, rotunjiți-o în sus; dacă fracția este exact 0,5, rotunjiți-o la numărul par dintre rotunjirea în jos și rotunjirea în sus.
Rounding – Wikipedia

0,5 nu este întotdeauna trunchiat.

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

În unele cazuri, definiția rotunjirii la un număr par nu se aplică nici măcar prelucrării după două zecimale.

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

Acest lucru se datorează faptului că zecimalele nu pot fi reprezentate exact ca numere cu virgulă mobilă, așa cum se menționează în documentația oficială.

Comportamentul lui round() pentru numere cu virgulă mobilă vă poate surprinde:De exemplu, round(2.675, 2) vă va da 2.67 în loc de 2.68, așa cum era de așteptat. Aceasta nu este o eroare.:Acesta este un rezultat al faptului că majoritatea zecimalelor nu pot fi reprezentate exact prin numere cu virgulă mobilă.
round() — Built-in Functions — Python 3.10.2 Documentation

Dacă doriți să obțineți o rotunjire generală sau o rotunjire precisă a zecimalei la numere pare, puteți utiliza cuantificarea zecimală din biblioteca standard (descrisă mai jos) sau puteți defini o nouă funcție.

De asemenea, rețineți că round() din Python 2 nu rotunjește la un număr par, ci rotunjește.

quantize() din biblioteca standard decimal

Modulul decimal din biblioteca standard poate fi utilizat pentru a gestiona numere zecimale exacte cu virgulă mobilă.

Utilizând metoda quantize() a modulului zecimal, este posibilă rotunjirea numerelor prin specificarea modului de rotunjire.

Valorile stabilite pentru argumentul rotunjire al metodei quantize() au următoarele semnificații.

  • ROUND_HALF_UP:Rotunjire generală
  • ROUND_HALF_EVEN:Rotunjirea la numere pare

Modulul decimal este o bibliotecă standard, deci nu este necesară o instalare suplimentară, dar este necesară importarea.

from decimal import Decimal, ROUND_HALF_UP, ROUND_HALF_EVEN

Crearea unui obiect Decimal

Decimal() poate fi utilizat pentru a crea obiecte de tip Decimal.

Dacă specificați un tip float ca argument, puteți vedea cum este tratată de fapt valoarea.

print(Decimal(0.05))
# 0.05000000000000000277555756156289135105907917022705078125

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

După cum se arată în exemplu, 0,05 nu este tratat ca fiind exact 0,05. Acesta este motivul pentru care funcția încorporată round() descrisă mai sus a rotunjit la o valoare diferită de cea așteptată pentru valorile zecimale, inclusiv 0,05 din exemplu.

Deoarece 0,5 este o jumătate (-1 putere a lui 2), poate fi exprimat exact în notație binară.

print(Decimal(0.5))
# 0.5

Dacă specificați tipul de șir de caractere str în loc de tipul float, acesta va fi tratat ca fiind tipul zecimal al valorii exacte.

print(Decimal('0.05'))
# 0.05

Rotunjirea zecimilor la orice număr de cifre și rotunjirea la numere pare

Se apelează quantize() de la un obiect de tip Decimal pentru a rotunji valoarea.

Primul argument al lui quantize() este un șir de caractere cu același număr de cifre ca și numărul de cifre pe care doriți să le găsiți, cum ar fi „0,1” sau „0,01”.

În plus, argumentul ROUNDING specifică modul de rotunjire; dacă se specifică ROUND_HALF_UP, se utilizează rotunjirea generală.

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

Spre deosebire de funcția încorporată round(), 0,5 este rotunjit la 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

În cazul în care argumentul rotunjire este setat la ROUND_HALF_EVEN, rotunjirea se efectuează la numere pare ca în funcția încorporată round().

După cum s-a menționat mai sus, dacă se specifică un tip float în virgulă mobilă ca argument al funcției Decimal(), acesta este tratat ca un obiect Decimal cu o valoare egală cu valoarea reală a tipului float, astfel încât rezultatul utilizării metodei quantize() va fi diferit de cel așteptat, la fel ca în cazul funcției încorporate 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

Dacă argumentul lui Decimal() este specificat ca un șir de caractere de tip str, acesta este tratat ca un obiect Decimal cu exact acea valoare, astfel încât rezultatul este cel așteptat.

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

Deoarece 0,5 poate fi gestionat corect de tipul float, nu există nicio problemă în a specifica tipul float ca argument al funcției Decimal() atunci când se rotunjește la un număr întreg, dar este mai sigur să se specifice tipul string str atunci când se rotunjește la o zecimală.

De exemplu, 2,675 este de fapt 2,67499…. în format float. Prin urmare, dacă doriți să rotunjiți la două zecimale, trebuie să specificați un șir de caractere pentru Decimal(), altfel rezultatul va fi diferit de rezultatul așteptat, indiferent dacă rotunjiți la cel mai apropiat număr întreg (ROUND_HALF_UP) sau la un număr par (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

Rețineți că metoda quantize() returnează un număr de tip zecimal, astfel încât, dacă doriți să operați pe un număr de tip float, trebuie să îl convertiți într-un număr de tip float folosind float(), altfel va apărea o eroare.

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

Rotunjirea numerelor întregi la orice număr de cifre și rotunjirea la numere pare

Dacă doriți să rotunjiți la o cifră întreagă, dacă specificați ceva de genul „10” ca prim argument, nu veți obține rezultatul dorit.

i = 99518

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

Acest lucru se datorează faptului că quantize() efectuează rotunjirea în funcție de exponentul obiectului Decimal, dar exponentul lui Decimal('10') este 0, nu 1.

Se poate specifica un exponent arbitrar folosind E ca șir de exponenți (de exemplu, „1E1”). Exponentul exponentului poate fi verificat în metoda 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)

Așa cum este, rezultatul va fi în notație exponențială folosind E. Dacă doriți să folosiți notația normală sau dacă doriți să operați cu tipul int întreg după rotunjire, utilizați int() pentru a converti rezultatul.

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

Dacă argumentul rotunjire este setat la ROUND_HALF_UP, va avea loc o rotunjire generală, de exemplu, 5 va fi rotunjit la 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

Desigur, nu există nicio problemă dacă îl specificați sub forma unui șir de caractere.

Definiți o nouă funcție

Metoda de utilizare a modulului zecimal este precisă și sigură, dar dacă nu vă simțiți confortabil cu conversia de tip, puteți defini o nouă funcție pentru a realiza o rotunjire generală.

Există mai multe moduri posibile de a face acest lucru, de exemplu, următoarea funcție.

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

Dacă nu aveți nevoie să specificați numărul de cifre și dacă rotunjiți întotdeauna la prima zecimală, puteți utiliza o formă mai simplă.

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

Dacă trebuie să fiți precis, este mai sigur să folosiți zecimale.

Cele de mai jos sunt doar cu titlu de referință.

Rotunjiți zecimalele la orice număr de 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

Spre deosebire de rundă, 0,5 devine 1 conform rotunjirii generale.

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

Rotunjirea numerelor întregi la orice număr de cifre

i = 99518

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

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

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

Spre deosebire de rotunjire, 5 devine 10, conform rotunjirii obișnuite.

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

Notă: Pentru valori negative

În funcția de exemplu de mai sus, -0,5 este rotunjit la 0.

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

Există diferite moduri de a gândi despre rotunjirea valorilor negative, dar dacă doriți să transformați -0,5 în -1, îl puteți modifica după cum urmează, de exemplu

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