Округление десятичных и целых чисел в Python с помощью «round» и «Decimal.quantize

Бизнес

Ниже объясняется, как округлять числа в Python путем округления или округления до четного числа. Предполагается, что числа имеют тип с плавающей точкой float или целочисленный тип int.

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

Обратите внимание, что, как упоминалось выше, встроенная функция round — это не общее округление, а округление до четного числа. Подробнее см. ниже.

встроенная функция (например, в языке программирования): round()

Round() предоставляется как встроенная функция. Ее можно использовать без импорта каких-либо модулей.

Первый аргумент — исходное число, а второй — количество цифр (до скольких цифр округлять).

Округлите десятичные числа до любого количества цифр.

Ниже приведен пример обработки для типа float с плавающей точкой.

Если второй аргумент опущен, то он округляется до целого числа. Тип также становится целочисленным типом 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'>

Округлите целые числа до любого количества цифр.

Ниже приведен пример обработки для целочисленного типа 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

Если вы хотите добиться общего округления или точного округления десятичных дробей до четных чисел, вы можете использовать стандартную библиотеку decimal quantize (описана ниже) или определить новую функцию.

Также обратите внимание, что round() в Python 2 — это не округление до четного числа, а округление.

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

Модуль decimal стандартной библиотеки может быть использован для работы с точными десятичными числами с плавающей запятой.

Используя метод quantize() модуля decimal, можно округлять числа, указывая режим округления.

Установленные значения для аргумента округления метода quantize() имеют следующие значения, соответственно.

  • ROUND_HALF_UP:Общее округление
  • ROUND_HALF_EVEN:Округление до четных чисел

Модуль decimal является стандартной библиотекой, поэтому дополнительной установки не требуется, но импортировать его необходимо.

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

Кроме того, аргумент 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() возвращает число типа 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

Если аргумент rounding имеет значение 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
Copied title and URL