Zaokrouhlování desetinných a celých čísel v jazyce Python pomocí příkazů „round“ a „Decimal.quantize

Podnikání

V následujícím textu je vysvětleno, jak zaokrouhlovat čísla v jazyce Python pomocí zaokrouhlování nebo zaokrouhlování na sudé číslo. Předpokládá se, že čísla jsou typu float s plovoucí desetinnou čárkou nebo int.

  • vestavěná funkce (např. v programovacím jazyce): round()
    • Zaokrouhlujte desetinná čísla na libovolný počet číslic.
    • Zaokrouhlujte celá čísla na libovolný počet číslic.
    • round() zaokrouhluje na sudé číslo, nikoli na běžné zaokrouhlení
  • standardní knihovnadecimal quantize()
    • DecimalVytvoření objektu
    • Zaokrouhlování desetinných čísel na libovolný počet číslic a zaokrouhlování na sudá čísla
    • Zaokrouhlování celých čísel na libovolný počet číslic a zaokrouhlování na sudá čísla
  • Definice nové funkce
    • Zaokrouhlujte desetinná čísla na libovolný počet číslic.
    • Zaokrouhlování celých čísel na libovolný počet číslic
    • Poznámka: Pro záporné hodnoty

Všimněte si, že jak bylo uvedeno výše, vestavěná funkce round není obecným zaokrouhlením, ale zaokrouhlením na sudé číslo. Podrobnosti viz níže.

vestavěná funkce (např. v programovacím jazyce): round()

Funkce Round() je k dispozici jako vestavěná funkce. Lze ji používat bez nutnosti importovat jakékoli moduly.

Prvním argumentem je původní číslo a druhým argumentem je počet číslic (na kolik číslic zaokrouhlit).

Zaokrouhlujte desetinná čísla na libovolný počet číslic.

Následuje příklad zpracování pro typ float s pohyblivou řádovou čárkou.

Pokud je druhý argument vynechán, zaokrouhlí se na celé číslo. Typ se rovněž stane celočíselným typem int.

f = 123.456

print(round(f))
# 123

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

Pokud je zadán druhý argument, vrací typ float s plovoucí desetinnou čárkou.

Pokud je zadáno celé kladné číslo, je zadáno desetinné místo; pokud je zadáno celé záporné číslo, je zadáno celé místo. -1 zaokrouhluje na nejbližší desetinu, -2 zaokrouhluje na nejbližší setinu a 0 zaokrouhluje na celé číslo (první místo), ale na rozdíl od vynechání vrací typ float.

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

Zaokrouhlujte celá čísla na libovolný počet číslic.

Následuje příklad zpracování pro celočíselný typ int.

Pokud je druhý argument vynechán nebo pokud je zadána 0 nebo celé kladné číslo, je vrácena původní hodnota. Pokud je zadáno záporné celé číslo, zaokrouhlí se na příslušnou celočíselnou hodnotu. V obou případech je vrácena celočíselná hodnota typu 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() zaokrouhluje na sudé číslo, nikoli na běžné zaokrouhlení

Všimněte si, že zaokrouhlování pomocí vestavěné funkce round() v jazyce Python 3 se zaokrouhluje na sudé číslo, nikoli na obecné zaokrouhlení.

Jak je uvedeno v oficiální dokumentaci, 0,5 se zaokrouhluje na 0, 5 se zaokrouhluje na 0 atd.

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

Definice zaokrouhlování na sudé číslo je následující.

Pokud je zlomek menší než 0,5, zaokrouhlete jej dolů; pokud je zlomek větší než 0,5, zaokrouhlete jej nahoru; pokud je zlomek přesně 0,5, zaokrouhlete jej na sudé číslo mezi zaokrouhlením dolů a zaokrouhlením nahoru.
Rounding – Wikipedia

0,5 není vždy zkrácený.

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 některých případech se definice zaokrouhlování na sudé číslo nevztahuje ani na zpracování po dvou desetinných místech.

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

Důvodem je skutečnost, že desetinná čísla nelze reprezentovat přesně jako čísla s pohyblivou řádovou čárkou, jak je uvedeno v oficiální dokumentaci.

Chování funkce round() pro čísla s plovoucí desetinnou čárkou vás možná překvapí.:Například zaokrouhlením(2,675, 2) získáte 2,67 místo očekávaných 2,68. Nejedná se o chybu.:Je to důsledek toho, že většinu desetinných čísel nelze přesně reprezentovat čísly s pohyblivou řádovou čárkou.
round() — Built-in Functions — Python 3.10.2 Documentation

Pokud chcete dosáhnout obecného zaokrouhlení nebo přesného zaokrouhlení desetinných čísel na sudá čísla, můžete použít standardní knihovnu decimal quantize (popsanou níže) nebo definovat novou funkci.

Všimněte si také, že funkce round() v jazyce Python 2 není zaokrouhlování na sudé číslo, ale zaokrouhlování.

quantize() standardní knihovny decimal

Ke zpracování přesných čísel s pohyblivou desetinnou čárkou lze použít modul decimal standardní knihovny.

Pomocí metody quantize() modulu decimal je možné zaokrouhlovat čísla zadáním režimu zaokrouhlování.

Nastavené hodnoty pro zaokrouhlování argumentu metody quantize() mají následující význam.

  • ROUND_HALF_UP:Obecné zaokrouhlování
  • ROUND_HALF_EVEN:Zaokrouhlování na sudá čísla

Modul decimal je standardní knihovna, takže není nutná žádná další instalace, ale je nutné jej importovat.

from decimal import Decimal, ROUND_HALF_UP, ROUND_HALF_EVEN

Vytvoření objektu Decimal

Funkci Decimal() lze použít k vytvoření objektů typu Decimal.

Pokud jako argument zadáte typ float, můžete se podívat, jak se s hodnotou skutečně zachází.

print(Decimal(0.05))
# 0.05000000000000000277555756156289135105907917022705078125

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

Jak je uvedeno v příkladu, hodnota 0,05 není považována za hodnotu přesně 0,05. To je důvod, proč výše popsaná vestavěná funkce round() zaokrouhluje na jinou hodnotu, než se očekává pro desetinné hodnoty včetně 0,05 v příkladu.

Protože 0,5 je polovina (-1 mocnina 2), lze ji přesně vyjádřit ve dvojkovém zápisu.

print(Decimal(0.5))
# 0.5

Pokud místo typu float zadáte typ string str, bude s hodnotou zacházeno jako s přesným typem Decimal.

print(Decimal('0.05'))
# 0.05

Zaokrouhlování desetinných čísel na libovolný počet číslic a zaokrouhlování na sudá čísla

Volání funkce quantize() z objektu typu Decimal pro zaokrouhlení hodnoty.

Prvním argumentem funkce quantize() je řetězec se stejným počtem číslic, jako je počet číslic, které chcete zjistit, například '0,1' nebo '0,01'.

Argument ROUNDING navíc určuje způsob zaokrouhlování; pokud je zadáno ROUND_HALF_UP, použije se obecné zaokrouhlování.

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

Na rozdíl od vestavěné funkce round() je hodnota 0,5 zaokrouhlena 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

Pokud je argument zaokrouhlování nastaven na hodnotu ROUND_HALF_EVEN, provede se zaokrouhlení na sudá čísla jako ve vestavěné funkci round().

Jak bylo uvedeno výše, pokud je jako argument metody Decimal() zadán typ float s pohyblivou řádovou čárkou, je s ním zacházeno jako s objektem Decimal s hodnotou rovnou skutečné hodnotě typu float, takže výsledek použití metody quantize() bude jiný, než se očekává, stejně jako u vestavěné funkce 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

Pokud je argument funkce Decimal() zadán jako řetězec typu str, je považován za objekt Decimal přesně této hodnoty, takže výsledek odpovídá očekávání.

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

Protože 0,5 lze správně zpracovat pomocí typu float, není problém zadat jako argument funkce Decimal() při zaokrouhlování na celé číslo typ float, ale při zaokrouhlování na desetinné místo je bezpečnější zadat typ string str.

Například 2,675 je ve skutečnosti 2,67499…. v typu float. Pokud tedy chcete zaokrouhlovat na dvě desetinná místa, musíte funkci Decimal() zadat řetězec, jinak se výsledek bude lišit od očekávaného, ať už zaokrouhlujete na nejbližší celé číslo (ROUND_HALF_UP), nebo na sudé číslo (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

Všimněte si, že metoda quantize() vrací číslo typu Decimal, takže pokud chcete pracovat s číslem typu float, musíte jej převést na typ float pomocí funkce float(), jinak dojde k chybě.

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

Zaokrouhlování celých čísel na libovolný počet číslic a zaokrouhlování na sudá čísla

Pokud chcete zaokrouhlovat na celé číslo, zadáním něčeho jako '10' jako prvního argumentu nezískáte požadovaný výsledek.

i = 99518

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

Je to proto, že funkce quantize() provádí zaokrouhlení podle exponentu objektu Decimal, ale exponent objektu Decimal('10') je 0, nikoli 1.

Libovolný exponent můžete zadat pomocí řetězce E jako exponent (např. '1E1'). Exponent exponentu lze zkontrolovat v metodě 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 této podobě bude výsledek v exponenciálním zápisu pomocí E. Pokud chcete použít normální zápis nebo pokud chcete po zaokrouhlení pracovat s celočíselným typem int, použijte k převodu výsledku funkci int().

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

Pokud je argument zaokrouhlování nastaven na hodnotu ROUND_HALF_UP, dojde k obecnému zaokrouhlování, např. 5 bude zaokrouhleno 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

Pokud jej zadáte jako řetězec, není to samozřejmě žádný problém.

Definice nové funkce

Metoda použití desetinného modulu je přesná a bezpečná, ale pokud vám nevyhovuje typová konverze, můžete definovat novou funkci, která zajistí obecné zaokrouhlování.

Existuje mnoho způsobů, jak to provést, například následující funkce.

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

Pokud nepotřebujete zadávat počet číslic a zaokrouhlujete vždy na první desetinné místo, můžete použít jednodušší tvar.

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

Pokud potřebujete být přesní, je bezpečnější použít desetinné číslo.

Následující údaje jsou pouze orientační.

Zaokrouhlujte desetinná čísla na libovolný počet číslic.

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

Na rozdíl od zaokrouhlování se z 0,5 stane 1 podle obecného zaokrouhlování.

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

Zaokrouhlování celých čísel na libovolný počet číslic

i = 99518

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

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

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

Na rozdíl od zaokrouhlování se z 5 stane 10 podle běžného zaokrouhlování.

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

Poznámka: Pro záporné hodnoty

Ve výše uvedeném příkladu funkce je -0,5 zaokrouhleno na 0.

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

O zaokrouhlování záporných hodnot lze uvažovat různými způsoby, ale pokud chcete z hodnoty -0,5 udělat hodnotu -1, můžete ji upravit například takto

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