Zaokrąglanie liczb dziesiętnych i całkowitych w Pythonie za pomocą „round” i „Decimal.quantize

Biznes

Poniżej wyjaśniono, jak zaokrąglać liczby w Pythonie poprzez zaokrąglanie lub zaokrąglanie do liczby parzystej. Zakłada się, że liczby są typu zmiennoprzecinkowego float lub całkowitego int.

  • funkcja wbudowana (np. w języku programowania): round()
    • Zaokrągla liczby dziesiętne do dowolnej liczby cyfr.
    • Zaokrągla liczby całkowite do dowolnej liczby cyfr.
    • round() zaokrągla do liczby parzystej, a nie do zwykłego zaokrąglenia
  • standardowa bibliotekadecimal quantize()
    • DecimalTworzenie obiektu
    • Zaokrąglanie liczb dziesiętnych do dowolnej liczby cyfr i zaokrąglanie do liczb parzystych
    • Zaokrąglanie liczb całkowitych do dowolnej liczby cyfr i zaokrąglanie do liczb parzystych
  • Zdefiniuj nową funkcję
    • Zaokrąglaj liczby dziesiętne do dowolnej liczby cyfr.
    • Zaokrąglaj liczby całkowite do dowolnej liczby cyfr
    • Uwaga: Dla wartości ujemnych

Zauważ, że jak wspomniano powyżej, wbudowana funkcja round nie jest ogólnym zaokrągleniem, ale zaokrągleniem do liczby parzystej. Zobacz poniżej po szczegóły.

funkcja wbudowana (np. w języku programowania): round()

Round() jest udostępniona jako funkcja wbudowana. Może być używana bez konieczności importowania jakichkolwiek modułów.

Pierwszy argument jest oryginalną liczbą, a drugi argument jest liczbą cyfr (do ilu cyfr zaokrąglić).

Zaokrągla liczby dziesiętne do dowolnej liczby cyfr.

Poniżej przedstawiono przykład przetwarzania dla typu zmiennoprzecinkowego float.

Jeśli drugi argument zostanie pominięty, to jest on zaokrąglany do liczby całkowitej. Typ staje się również typem całkowitym int.

f = 123.456

print(round(f))
# 123

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

Jeśli podany jest drugi argument, to zwraca typ zmiennoprzecinkowy float.

Jeśli podana jest dodatnia liczba całkowita, to określane jest miejsce dziesiętne; jeśli podana jest ujemna liczba całkowita, to określane jest miejsce całkowite. -1 zaokrągla do najbliższej dziesiątki, -2 do najbliższej setnej, a 0 zaokrągla do liczby całkowitej (pierwsze miejsce), ale zwraca typ float, inaczej niż w przypadku pominięcia.

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

Zaokrągla liczby całkowite do dowolnej liczby cyfr.

Poniżej znajduje się przykład przetwarzania dla typu integer int.

Jeśli pominięto drugi argument, lub jeśli podano 0 lub dodatnią liczbę całkowitą, to zwracana jest wartość oryginalna. Jeśli podano ujemną liczbę całkowitą, to jest ona zaokrąglana do odpowiedniej cyfry liczby całkowitej. W obu przypadkach zwracany jest typ integer 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() zaokrągla do liczby parzystej, a nie do zwykłego zaokrąglenia

Zauważ, że zaokrąglanie za pomocą wbudowanej funkcji round() w Pythonie 3 zaokrągla do liczby parzystej, a nie do ogólnego zaokrąglenia.

Jak napisano w oficjalnej dokumentacji, 0.5 jest zaokrąglane do 0, 5 jest zaokrąglane do 0, i tak dalej.

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

Definicja zaokrąglania do liczby parzystej jest następująca.

Jeśli ułamek jest mniejszy niż 0,5, zaokrąglij go w dół; jeśli ułamek jest większy niż 0,5, zaokrąglij go w górę; jeśli ułamek jest dokładnie 0,5, zaokrąglij go do liczby parzystej pomiędzy zaokrągleniem w dół i zaokrągleniem w górę.
Rounding – Wikipedia

0,5 nie zawsze jest obcinane.

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

W niektórych przypadkach definicja zaokrąglania do liczby parzystej nie ma zastosowania nawet do przetwarzania po dwóch miejscach po przecinku.

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

Wynika to z faktu, że liczby dziesiętne nie mogą być reprezentowane dokładnie jako liczby zmiennoprzecinkowe, jak stwierdzono w oficjalnej dokumentacji.

Zachowanie funkcji round() dla liczb zmiennoprzecinkowych może cię zaskoczyć:Na przykład, round(2.675, 2) da ci 2.67 zamiast 2.68, jak oczekiwano. To nie jest błąd.:Wynika to z faktu, że większość wartości dziesiętnych nie może być dokładnie reprezentowana przez liczby zmiennoprzecinkowe.
round() — Built-in Functions — Python 3.10.2 Documentation

Jeśli chcesz uzyskać ogólne zaokrąglenie lub dokładne zaokrąglenie liczb dziesiętnych do liczb parzystych, możesz użyć standardowej biblioteki decimal quantize (opisanej poniżej) lub zdefiniować nową funkcję.

Zauważ również, że round() w Pythonie 2 nie jest zaokrąglaniem do liczby parzystej, ale zaokrąglaniem.

quantize() biblioteki standardowej decimal

Moduł decimal biblioteki standardowej może być użyty do obsługi dokładnych dziesiętnych liczb zmiennoprzecinkowych.

Używając metody quantize() modułu decimal, można zaokrąglać liczby poprzez określenie trybu zaokrąglania.

Ustawione wartości dla argumentu zaokrąglenie metody quantize() mają odpowiednio następujące znaczenie.

  • ROUND_HALF_UP:Ogólne zaokrąglenia
  • ROUND_HALF_EVEN:Zaokrąglanie do liczb parzystych

Moduł decimal jest standardową biblioteką, więc nie jest wymagana żadna dodatkowa instalacja, ale import jest konieczny.

from decimal import Decimal, ROUND_HALF_UP, ROUND_HALF_EVEN

Tworzenie obiektu Decimal

Decimal() może być użyta do tworzenia obiektów typu Decimal.

Jeśli podasz typ float jako argument, możesz zobaczyć, jako co wartość jest faktycznie traktowana.

print(Decimal(0.05))
# 0.05000000000000000277555756156289135105907917022705078125

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

Jak pokazano w przykładzie, 0.05 nie jest traktowane jako dokładnie 0.05. Jest to powód, dla którego wbudowana funkcja round() opisana powyżej zaokrągla do innej wartości niż oczekiwana dla wartości dziesiętnych w tym 0.05 w przykładzie.

Ponieważ 0,5 to połowa (-1 potęga 2), można to wyrazić dokładnie w notacji binarnej.

print(Decimal(0.5))
# 0.5

Jeśli podasz typ łańcuchowy str zamiast typu float, zostanie on potraktowany jako typ dziesiętny dokładnej wartości.

print(Decimal('0.05'))
# 0.05

Zaokrąglanie liczb dziesiętnych do dowolnej liczby cyfr i zaokrąglanie do liczb parzystych

Wywołaj funkcję quantize() z obiektu typu Decimal, aby zaokrąglić wartość.

Pierwszym argumentem funkcji quantize() jest ciąg znaków o takiej samej liczbie cyfr jak liczba cyfr, którą chcesz znaleźć, np. '0.1' lub '0.01'.

Dodatkowo, argument ROUNDING określa tryb zaokrąglania; jeśli podano ROUND_HALF_UP, to używane jest zaokrąglanie ogólne.

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

W przeciwieństwie do wbudowanej funkcji round(), 0.5 jest zaokrąglane do 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

Jeśli argument rounding jest ustawiony na ROUND_HALF_EVEN, to zaokrąglanie jest wykonywane do liczb parzystych, tak jak we wbudowanej funkcji round().

Jak wspomniano powyżej, jeśli jako argument funkcji Decimal() podany jest typ zmiennoprzecinkowy, to jest on traktowany jako obiekt Decimal o wartości równej rzeczywistej wartości typu float, więc wynik użycia metody quantize() będzie inny niż oczekiwany, podobnie jak wbudowana funkcja 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

Jeśli argument funkcji Decimal() jest określony jako łańcuch znaków typu str, to jest on traktowany jako obiekt Decimal o dokładnie takiej wartości, więc wynik jest zgodny z oczekiwaniami.

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

Ponieważ 0.5 może być poprawnie obsługiwane przez typ float, nie ma problemu z podaniem typu float jako argumentu funkcji Decimal() przy zaokrąglaniu do liczby całkowitej, ale bezpieczniej jest podać typ string str przy zaokrąglaniu do miejsca dziesiętnego.

Na przykład, 2.675 jest w rzeczywistości 2.67499…. w typie float. Dlatego, jeśli chcesz zaokrąglić do dwóch miejsc po przecinku, musisz podać łańcuch znaków do funkcji Decimal(), w przeciwnym razie wynik będzie różnił się od oczekiwanego, niezależnie od tego, czy zaokrąglisz do najbliższej liczby całkowitej (ROUND_HALF_UP), czy do liczby parzystej (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

Zauważ, że metoda quantize() zwraca liczbę typu Decimal, więc jeśli chcesz operować na liczbie typu float, musisz przekonwertować ją na typ float za pomocą funkcji float(), w przeciwnym razie wystąpi błąd.

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

Zaokrąglanie liczb całkowitych do dowolnej liczby cyfr i zaokrąglanie do liczb parzystych

Jeśli chcesz zaokrąglić do cyfry całkowitej, podanie czegoś takiego jak '10' jako pierwszego argumentu nie da ci pożądanego wyniku.

i = 99518

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

Dzieje się tak, ponieważ funkcja quantize() wykonuje zaokrąglenie zgodnie z wykładnikiem obiektu Decimal, ale wykładnik Decimal('10') wynosi 0, a nie 1.

Możesz określić dowolny wykładnik przez użycie E jako łańcucha wykładników (np. '1E1'). Wykładnik wykładnika może być sprawdzony w metodzie 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)

Tak jak jest, wynik będzie w notacji wykładniczej używając E. Jeśli chcesz użyć normalnej notacji, lub jeśli chcesz operować na typie integer int po zaokrągleniu, użyj int() aby przekonwertować wynik.

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

Jeżeli argument rounding jest ustawiony na ROUND_HALF_UP, to nastąpi ogólne zaokrąglenie, np. 5 zostanie zaokrąglone do 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

Oczywiście nie ma problemu, jeśli podasz go jako ciąg znaków.

Zdefiniuj nową funkcję

Metoda korzystania z modułu dziesiętnego jest dokładna i bezpieczna, ale jeśli nie czujesz się komfortowo z konwersją typów, możesz zdefiniować nową funkcję, aby uzyskać ogólne zaokrąglenie.

Istnieje wiele możliwych sposobów, aby to zrobić, na przykład następująca funkcja.

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

Jeśli nie musisz określać liczby cyfr i zawsze zaokrąglasz do pierwszego miejsca po przecinku, możesz użyć prostszej formy.

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

Jeśli musisz być precyzyjny, bezpieczniej jest użyć wartości dziesiętnych.

Poniższe informacje mają jedynie charakter poglądowy.

Zaokrąglaj liczby dziesiętne do dowolnej liczby cyfr.

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

W przeciwieństwie do zaokrąglenia, 0,5 staje się 1 zgodnie z ogólnym zaokrągleniem.

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

Zaokrąglaj liczby całkowite do dowolnej liczby cyfr

i = 99518

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

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

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

W odróżnieniu od zaokrąglenia, 5 staje się 10 jak w powszechnym zaokrągleniu.

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

Uwaga: Dla wartości ujemnych

W powyższym przykładzie funkcji, -0.5 jest zaokrąglone do 0.

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

Istnieją różne sposoby myślenia o zaokrąglaniu dla wartości ujemnych, ale jeśli chcesz zamienić -0,5 na -1, możesz zmodyfikować to w następujący sposób, na przykład

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