Закръгляне на десетични и цели числа в Python с „round“ и „Decimal.quantize

Бизнес

По-долу е обяснено как да закръгляме числа в Python чрез закръгляне или закръгляне до четно число. Предполага се, че числата са от тип float с плаваща запетая или int с цяло число.

  • вградена функция (напр. в език за програмиране): round()
    • Закръглете десетичните дроби до произволен брой цифри.
    • Закръгляне на цели числа до произволен брой цифри.
    • round() закръгля до четно число, а не до общо закръгляне
  • стандартна библиотекаdecimal quantize()
    • DecimalСъздаване на обект
    • Закръгляне на десетични числа до произволен брой цифри и закръгляне до четни числа
    • Закръгляне на цели числа до произволен брой цифри и закръгляне до четни числа
  • Дефиниране на нова функция
    • Закръглете десетичните знаци до произволен брой цифри.
    • Закръгляне на цели числа до произволен брой цифри
    • Забележка: За отрицателни стойности

Обърнете внимание, че както беше споменато по-горе, вградената функция round не е общо закръгляне, а закръгляне до четно число. Вижте по-долу за подробности.

вградена функция (напр. в език за програмиране): round()

Функцията Round() се предоставя като вградена функция. Тя може да се използва без импортиране на модули.

Първият аргумент е оригиналното число, а вторият аргумент е броят на цифрите (до колко цифри да се закръгли).

Закръглете десетичните дроби до произволен брой цифри.

По-долу е даден пример за обработка на тип float с плаваща запетая.

Ако вторият аргумент е пропуснат, той се закръгля до цяло число. Типът също се превръща в тип integer int.

f = 123.456

print(round(f))
# 123

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

Ако е посочен вторият аргумент, се връща тип float с плаваща запетая.

Ако е зададено цяло положително число, се задава десетичното място; ако е зададено цяло отрицателно число, се задава мястото на цялото число. -1 закръгля до най-близката десета, -2 закръгля до най-близката стотна, а 0 закръгля до цяло число (първото място), но връща тип 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'>

Закръгляне на цели числа до произволен брой цифри.

По-долу е даден пример за обработка на тип integer int.

Ако вторият аргумент е пропуснат или ако е зададена стойност 0 или цяло положително число, оригиналната стойност се връща такава, каквато е. Ако е зададено отрицателно цяло число, то се закръгля до съответната цяла цифра. И в двата случая се връща цяло число от тип 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() закръгля до четно число, а не до общо закръгляне

Обърнете внимание, че закръглянето с вградената функция round() в Python 3 се извършва до четно число, а не до общо закръгляне.

Както е записано в официалната документация, 0,5 се закръгля на 0, 5 се закръгля на 0 и т.н.

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

Дефиницията за закръгляне до четно число е следната.

Ако дробта е по-малка от 0,5, закръглете я надолу; ако дробта е по-голяма от 0,5, закръглете я нагоре; ако дробта е точно 0,5, закръглете я нагоре до четно число между закръглянето надолу и закръглянето нагоре.
Rounding – Wikipedia

0,5 невинаги се прекъсва.

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

В някои случаи определението за закръгляне до четно число не се прилага дори за обработка след два знака след десетичната запетая.

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

Това се дължи на факта, че числата с десетична запетая не могат да бъдат представени точно като числа с плаваща запетая, както е посочено в официалната документация.

Поведението на функцията round() за числа с плаваща запетая може да ви изненада:Например, при закръгляне(2.675, 2) ще получите 2.67 вместо очакваните 2.68. Това не е грешка.:Това е резултат от факта, че повечето десетични дроби не могат да бъдат представени точно чрез числа с плаваща запетая.
round() — Built-in Functions — Python 3.10.2 Documentation

Ако искате да постигнете общо закръгляне или точно закръгляне на десетичните дроби до четни числа, можете да използвате стандартната библиотека decimal quantize (описана по-долу) или да дефинирате нова функция.

Също така имайте предвид, че функцията round() в Python 2 не е закръгляне до четно число, а закръгляне.

quantize() на стандартната библиотека decimal

Десетичният модул на стандартната библиотека може да се използва за работа с точни десетични числа с плаваща запетая.

Чрез метода quantize() на десетичния модул е възможно да се закръглят числата, като се зададе режимът на закръгляване.

Зададените стойности за закръглянето на аргумента на метода quantize() имат съответно следните значения.

  • ROUND_HALF_UP:Общо закръгляне
  • ROUND_HALF_EVEN:Закръгляне до четни числа

Десетичният модул е стандартна библиотека, така че не е необходима допълнителна инсталация, но е необходимо импортиране.

from decimal import Decimal, ROUND_HALF_UP, ROUND_HALF_EVEN

Създаване на обект Decimal

Decimal() може да се използва за създаване на обекти от тип Decimal.

Ако посочите като аргумент тип float, можете да видите как всъщност се третира стойността.

print(Decimal(0.05))
# 0.05000000000000000277555756156289135105907917022705078125

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

Както е показано в примера, 0,05 не се разглежда като точно 0,05. Това е причината, поради която вградената функция round(), описана по-горе, закръгля до различна от очакваната стойност за десетични стойности, включително 0,05 в примера.

Тъй като 0,5 е половината (-1 степен на 2), то може да се изрази точно в двоичен запис.

print(Decimal(0.5))
# 0.5

Ако зададете типа string str вместо типа float, той ще бъде третиран като типа Decimal на точната стойност.

print(Decimal('0.05'))
# 0.05

Закръгляне на десетични числа до произволен брой цифри и закръгляне до четни числа

Извикайте quantize() от обект от тип Decimal, за да закръглите стойността.

Първият аргумент на quantize() е низ със същия брой цифри като броя на цифрите, които искате да намерите, например '0.1' или '0.01'.

Освен това аргументът ROUNDING указва начина на закръгляне; ако е посочен аргументът ROUND_HALF_UP, се използва общо закръгляне.

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

За разлика от вградената функция round(), 0,5 се закръгля до 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

Ако аргументът rounding е зададен на ROUND_HALF_EVEN, закръглянето се извършва до четни числа, както при вградената функция round().

Както беше споменато по-горе, ако като аргумент на Decimal() е зададен тип float с плаваща запетая, той се третира като обект Decimal със стойност, равна на действителната стойност на типа float, така че резултатът от използването на метода quantize() ще бъде различен от очаквания, точно както вградената функция 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

Ако аргументът на Decimal() е зададен като низ от тип str, той се третира като обект Decimal с точно такава стойност, така че резултатът е очакваният.

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

Тъй като 0.5 може да бъде правилно обработено от типа float, няма проблем да се посочи типът float като аргумент на Decimal() при закръгляне до цяло число, но е по-безопасно да се посочи типът string str при закръгляне до десетичен знак.

Например 2,675 всъщност е 2,67499…. в тип float. Следователно, ако искате да закръгляте до два знака след десетичната запетая, трябва да зададете низ на Decimal(), в противен случай резултатът ще се различава от очаквания, независимо дали закръгляте до най-близкото цяло число (ROUND_HALF_UP) или до четно число (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

Обърнете внимание, че методът quantize() връща число от десетичен тип, така че ако искате да оперирате с число от плаващ тип, трябва да го преобразувате в плаващ тип с помощта на float(), в противен случай ще възникне грешка.

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

Закръгляне на цели числа до произволен брой цифри и закръгляне до четни числа

Ако искате да закръглите до цяло число, задаването на нещо като '10' като първи аргумент няма да ви даде желания резултат.

i = 99518

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

Това се дължи на факта, че quantize() извършва закръгляне според експонентата на обекта Decimal, но експонентата на Decimal('10') е 0, а не 1.

Можете да зададете произволен експонент, като използвате E като низ от експоненти (напр. '1E1'). Експонентата може да бъде проверена в метода 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)

В този си вид резултатът ще бъде в експоненциален запис с помощта на E. Ако искате да използвате нормален запис или ако искате да работите с целочисления тип int след закръгляне, използвайте 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

Ако аргументът закръгляне е зададен на ROUND_HALF_UP, ще се извърши общо закръгляне, например 5 ще бъде закръглено на 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

Разбира се, няма проблем, ако го зададете като низ.

Дефиниране на нова функция

Методът на използване на десетичния модул е точен и сигурен, но ако не ви е удобно преобразуването на типове, можете да дефинирате нова функция, за да постигнете общо закръгляне.

Има много възможни начини да направите това, например следната функция.

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

Ако не е необходимо да посочвате броя на цифрите и винаги закръгляте до първия знак след десетичната запетая, можете да използвате по-проста форма.

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

Ако трябва да сте точни, е по-безопасно да използвате десетична бройна система.

Следната информация е само за справка.

Закръглете десетичните знаци до произволен брой цифри.

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

За разлика от закръглянето, 0,5 става 1 според общото закръгляне.

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

Закръгляне на цели числа до произволен брой цифри

i = 99518

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

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

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

За разлика от закръглянето, 5 става 10 според обичайното закръгляне.

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

Забележка: За отрицателни стойности

В примерната функция по-горе -0,5 се закръгля до 0.

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

Съществуват различни начини за закръгляне на отрицателни стойности, но ако искате да превърнете -0,5 в -1, можете да го промените по следния начин, например

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