Redondeo de decimais e enteiros en Python con “redondear” e “Decimal.quantize

Negocios

A continuación explícase como redondear números en Python redondeando ou redondeando a un número par. Suponse que os números son de tipo flotante en coma flotante ou enteiro int.

  • función integrada (por exemplo, en linguaxe de programación):round()
    • Redondea os decimais a calquera número de díxitos.
    • Redondea os enteiros a calquera número de díxitos.
    • round() redondea a un número par, non a un redondeo común
  • biblioteca estándardecimalquantize()
    • DecimalCreando un obxecto
    • Redondeo de decimais a calquera número de díxitos e redondeo a números pares
    • Redondeo de números enteiros a calquera número de díxitos e redondeo a números pares
  • Definir unha nova función
    • Redondea os decimais a calquera número de díxitos.
    • Redondea os enteiros a calquera número de díxitos
    • Nota: Para valores negativos

Teña en conta que, como se mencionou anteriormente, a función integrada de redondeo non é un redondeo xeral, senón un redondeo a un número par. Consulte a continuación para obter máis detalles.

función integrada (por exemplo, en linguaxe de programación):round()

Round() ofrécese como unha función integrada. Pódese usar sen importar ningún módulo.

O primeiro argumento é o número orixinal e o segundo é o número de díxitos (a cantos díxitos se redondea).

Redondea os decimais a calquera número de díxitos.

O seguinte é un exemplo de procesamento para o tipo flotante de coma flotante.

Se o segundo argumento se omite, arredondarase a un número enteiro. O tipo tamén se converte nun tipo int enteiro.

f = 123.456

print(round(f))
# 123

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

Se se especifica o segundo argumento, devolve un tipo flotante de coma flotante.

Se se especifica un número enteiro positivo, especifícase a cifra decimal; se se especifica un número enteiro negativo, especifícase o lugar do número enteiro. -1 redondea á décima máis próxima, -2 redondea á centésima máis próxima e 0 redondea a un número enteiro (o primeiro lugar), pero devolve un tipo flotante, a diferenza de cando se omite.

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

Redondea os enteiros a calquera número de díxitos.

O seguinte é un exemplo de procesamento para o tipo enteiro int.

Se se omite o segundo argumento, ou se se especifica 0 ou un número enteiro positivo, o valor orixinal devólvese tal e como está. Se se especifica un número enteiro negativo, arredondarase ao enteiro correspondente. En ambos casos, devólvese un tipo int enteiro.

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() redondea a un número par, non a un redondeo común

Teña en conta que o redondeo coa función round() incorporada en Python 3 redondea a un número par, non a un redondeo xeral.

Tal e como está escrito na documentación oficial, 0,5 redondea a 0, 5 a 0, etc.

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

A definición de redondeo a un número par é a seguinte.

Se a fracción é inferior a 0,5, redondea por abaixo; se a fracción é maior que 0,5, arredéiaa cara arriba; se a fracción é exactamente 0,5, redondea ao número par entre o redondeo cara abaixo e o redondeo cara arriba.
Rounding – Wikipedia

0,5 non sempre está truncado.

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

Nalgúns casos, a definición de redondeo a un número par nin sequera se aplica ao procesamento despois de dous decimais.

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

Isto débese ao feito de que os decimais non se poden representar exactamente como números de coma flotante, como se indica na documentación oficial.

O comportamento de round() para números de coma flotante pode sorprendelo:Por exemplo, round(2,675, 2) darache 2,67 en lugar de 2,68 como se esperaba. Isto non é un erro.:Isto é o resultado do feito de que a maioría dos decimais non se poden representar exactamente mediante números de coma flotante.
round() — Built-in Functions — Python 3.10.2 Documentation

Se queres conseguir un redondeo xeral ou un redondeo preciso dos decimais a números pares, podes usar a cuantificación decimal estándar da biblioteca (descrita a continuación) ou definir unha nova función.

Tamén teña en conta que round() en Python 2 non se redondea a un número par, senón que se redondea.

quantize() do decimal da biblioteca estándar

O módulo decimal da biblioteca estándar pódese usar para manexar números decimais exactos en coma flotante.

Usando o método quantize() do módulo decimal, é posible redondear números especificando o modo de redondeo.

Os valores establecidos para o redondeo dos argumentos do método quantize() teñen os seguintes significados, respectivamente.

  • ROUND_HALF_UP:Redondeo xeral
  • ROUND_HALF_EVEN:Redondeo a números pares

O módulo decimal é unha biblioteca estándar, polo que non se precisa instalación adicional, pero é necesario importar.

from decimal import Decimal, ROUND_HALF_UP, ROUND_HALF_EVEN

Creación dun obxecto decimal

Decimal() pódese usar para crear obxectos de tipo Decimal.

Se especificas un tipo flotante como argumento, podes ver como se trata realmente o valor.

print(Decimal(0.05))
# 0.05000000000000000277555756156289135105907917022705078125

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

Como se mostra no exemplo, 0,05 non se trata exactamente como 0,05. Esta é a razón pola que a función incorporada round() descrita anteriormente redondeou a un valor diferente do esperado para os valores decimais, incluíndo 0,05 no exemplo.

Dado que 0,5 é a metade (-1 potencia de 2), pódese expresar exactamente en notación binaria.

print(Decimal(0.5))
# 0.5

Se especifica o tipo de cadea str en lugar do tipo flotante, tratarase como o tipo decimal do valor exacto.

print(Decimal('0.05'))
# 0.05

Redondeo de decimais a calquera número de díxitos e redondeo a números pares

Chama a quantize() desde un obxecto de tipo Decimal para redondear o valor.

O primeiro argumento de quantize() é unha cadea co mesmo número de díxitos que o número de díxitos que quere atopar, como “0,1” ou “0,01”.

Ademais, o argumento ROUNDING especifica o modo de redondeo; se se especifica ROUND_HALF_UP, utilízase o redondeo xeral.

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

A diferenza da función integrada round(), 0,5 redondéase a 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

Se o argumento redondeo está definido como ROUND_HALF_EVEN, o redondeo realízase a números pares como na función incorporada round().

Como se mencionou anteriormente, se se especifica un tipo flotante de coma flotante como argumento de Decimal(), trátase como un obxecto Decimal cun valor igual ao valor real do tipo flotante, polo que o resultado de usar o quantize() método será diferente do que se espera, igual que a función incorporada 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

Se o argumento de Decimal() se especifica como unha cadea de tipo str, trátase como un obxecto Decimal de exactamente ese valor, polo que o resultado é o esperado.

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

Dado que 0.5 pódese manexar correctamente polo tipo flotante, non hai ningún problema en especificar o tipo flotante como argumento de Decimal() cando se redondea a un número enteiro, pero é máis seguro especificar o tipo de cadea str cando se redondea a un decimal.

Por exemplo, 2,675 é en realidade 2,67499…. en tipo flotante. Polo tanto, se queres redondear a dúas cifras decimais, debes especificar unha cadea a Decimal(), se non, o resultado será diferente do resultado esperado se redondea ao número enteiro máis próximo (ROUND_HALF_UP) ou a un número par (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

Teña en conta que o método quantize() devolve un número de tipo Decimal, polo que se quere operar cun número de tipo flotante, cómpre convertelo a un tipo flotante usando float(), se non, producirase un erro.

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

Redondeo de números enteiros a calquera número de díxitos e redondeo a números pares

Se queres redondear a un díxito enteiro, especificando algo como ’10’ como primeiro argumento non che dará o resultado desexado.

i = 99518

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

Isto débese a que quantize() realiza o redondeo segundo o expoñente do obxecto Decimal, pero o expoñente de Decimal(’10’) é 0, non 1.

Podes especificar un expoñente arbitrario usando E como cadea de expoñentes (por exemplo, ‘1E1’). O expoñente do expoñente pódese comprobar no método 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)

Tal e como está, o resultado estará en notación exponencial usando E. Se queres usar a notación normal, ou se queres operar co tipo int enteiro despois de redondear, usa int() para converter o resultado.

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

Se o redondeo do argumento se define en ROUND_HALF_UP, producirase un redondeo xeral, por exemplo, 5 redondearase a 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

Por suposto, non hai ningún problema se o especificas como cadea.

Definir unha nova función

O método de uso do módulo decimal é preciso e seguro, pero se non estás cómodo coa conversión de tipos, podes definir unha nova función para lograr o redondeo xeral.

Hai moitas formas posibles de facelo, por exemplo, a seguinte función.

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

Se non precisa especificar o número de díxitos e redondear sempre ao primeiro decimal, pode utilizar unha forma máis sinxela.

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

Se precisa ser preciso, é máis seguro usar decimal.

O seguinte é só para referencia.

Redondea os decimais a calquera número de díxitos.

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

A diferenza da rolda, 0,5 pasa a ser 1 segundo o redondeo xeral.

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

Redondea os enteiros a calquera número de díxitos

i = 99518

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

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

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

A diferenza da rolda, 5 pasa a ser 10 segundo o redondeo común.

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

Nota: Para valores negativos

Na función de exemplo anterior, -0,5 redondéase a 0.

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

Hai varias formas de pensar sobre o redondeo de valores negativos, pero se queres converter -0,5 en -1, podes modificalo do seguinte xeito, por exemplo

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