Акругленне дзесятковых і цэлых лікаў у Python з дапамогай “round” і “Decimal.quantize”

Бізнэс

Ніжэй тлумачыцца, як акругліць лічбы ў Python шляхам акруглення або акруглення да цотнага ліку. Мяркуецца, што лічбы маюць тып з плаваючай кропкай з плаваючай кропкай або цэлае цэлае.

  • убудаваная функцыя (напрыклад, на мове праграмавання):round()
    • Акругліць дзесятковыя дробы да любой колькасці лічбаў.
    • Акругліць цэлыя лікі да любой колькасці лічбаў.
    • round() акругляе да цотнага ліку, а не да звычайнага акруглення
  • стандартная бібліятэкаdecimalquantize()
    • DecimalСтварэнне аб’екта
    • Акругленне дзесятковых лічбаў да любой колькасці лічбаў і акругленне да цотных лікаў
    • Акругленне цэлых лікаў да любой колькасці лічбаў і акругленне да цотных лікаў
  • Вызначце новую функцыю
    • Акругліць дзесяткі да любой колькасці лічбаў.
    • Акругліць цэлыя лікі да любой колькасці лічбаў
    • Заўвага: для адмоўных значэнняў

Звярніце ўвагу, што, як было сказана вышэй, убудаваная функцыя round з’яўляецца не агульным акругленнем, а акругленнем да цотнага ліку. Падрабязнасці глядзіце ніжэй.

убудаваная функцыя (напрыклад, на мове праграмавання):round()

Round() прадастаўляецца як убудаваная функцыя. Яго можна выкарыстоўваць без імпарту модуляў.

Першы аргумент – гэта першапачатковы лік, а другі – колькасць лічбаў (да колькі лічбаў акругліць).

Акругліць дзесятковыя дробы да любой колькасці лічбаў.

Ніжэй прыведзены прыклад апрацоўкі для тыпу з плаваючай кропкай.

Калі другі аргумент апушчаны, ён акругляецца да цэлага ліку. Тып таксама становіцца цэлым тыпам int.

f = 123.456

print(round(f))
# 123

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

Калі зададзены другі аргумент, ён вяртае тып з плаваючай кропкай.

Калі зададзена дадатнае цэлае лік, паказваецца дзесятковы знак; калі зададзена адмоўнае цэлае лік, указваецца цэлае месца. -1 акругляе да бліжэйшай дзесятай, -2 акругляе да бліжэйшай сотай і 0 акругляе да цэлага ліку (першае месца), але вяртае тып з плаваючай колькасцю, у адрозненне ад апускання.

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

Акругліць цэлыя лікі да любой колькасці лічбаў.

Ніжэй прыведзены прыклад апрацоўкі цэлага тыпу 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() для лікаў з плаваючай кропкай могуць вас здзівіць:Напрыклад, round(2,675, 2) дасць вам 2,67 замест 2,68, як чакалася. Гэта не памылка.:Гэта вынік таго факту, што большасць дзесятковых знакаў не можа быць дакладна прадстаўлена лічбамі з плаваючай кропкай.
round() — Built-in Functions — Python 3.10.2 Documentation

Калі вы хочаце дасягнуць агульнага акруглення або дакладнага акруглення дзесятковых да цотных лікаў, вы можаце выкарыстоўваць стандартную бібліятэку дзесятковага квантавання (апісана ніжэй) або вызначыць новую функцыю.

Таксама звярніце ўвагу, што round() у Python 2 не акругляе да цотнага ліку, а акругляе.

quantize() дзесятковай стандартнай бібліятэкі

Дзесятковы модуль стандартнай бібліятэкі можа быць выкарыстаны для апрацоўкі дакладных дзесятковых лікаў з плаваючай кропкай.

Выкарыстоўваючы метад 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

Калі вы задасце тып радка str замест тыпу float, ён будзе разглядацца як дзесятковы тып дакладнага значэння.

print(Decimal('0.05'))
# 0.05

Акругленне дзесятковых лічбаў да любой колькасці лічбаў і акругленне да цотных лікаў

Выклічце quantize() з аб’екта тыпу Decimal, каб акругліць значэнне.

Першы аргумент quantize() – гэта радок з такой жа колькасцю лічбаў, што і колькасць лічбаў, якую вы хочаце знайсці, напрыклад, ‘0.1’ або ‘0.01’.

Акрамя таго, аргумент RUNDING вызначае рэжым акруглення; калі зададзены 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

Калі акругленне аргумента зададзена ў ROUND_HALF_EVEN, акругленне выконваецца да цотных лікаў, як ва ўбудаванай функцыі round().

Як згадвалася вышэй, калі ў якасці аргумента Decimal() зададзены тып з плаваючай кропкай з плаваючай кропкай, ён разглядаецца як аб’ект 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 пры акругленні да дзесятковага знака.

Напрыклад, 2,675 на самай справе 2,67499…. у тыпе з плаваючай колькасцю. Такім чынам, калі вы хочаце акругліць да двух знакаў пасля коскі, вы павінны паказаць радок у 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() вяртае нумар тыпу Decimal, таму, калі вы хочаце апераваць з нумарам тыпу float, вам трэба пераўтварыць яго ў тып з float з дапамогай 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