Comportamiento de redondeo de Python 3.x

176

Estaba volviendo a leer What's New In Python 3.0 y dice:

La estrategia de redondeo de la función round () y el tipo de retorno han cambiado. Los casos intermedios exactos ahora se redondean al resultado par más cercano en lugar de alejarse de cero. (Por ejemplo, la ronda (2.5) ahora devuelve 2 en lugar de 3.)

y la documentación para la ronda :

Para los tipos integrados que admiten round (), los valores se redondean al múltiplo más cercano de 10 a la potencia menos n; si dos múltiplos están igualmente cerca, el redondeo se realiza hacia la opción pareja

Entonces, bajo v2.7.3 :

In [85]: round(2.5)
Out[85]: 3.0

In [86]: round(3.5)
Out[86]: 4.0

como hubiera esperado Sin embargo, ahora bajo v3.2.3 :

In [32]: round(2.5)
Out[32]: 2

In [33]: round(3.5)
Out[33]: 4

Esto parece contrario a la intuición y contrario a lo que entiendo sobre el redondeo (y está destinado a hacer tropezar a las personas). El inglés no es mi lengua materna, pero hasta que leí esto pensé que sabía lo que significaba el redondeo: - / Estoy seguro de que en el momento en que se introdujo la v3 debe haber habido una discusión sobre esto, pero no pude encontrar una buena razón en mi busqueda

  1. ¿Alguien tiene idea de por qué esto se cambió a esto?
  2. ¿Hay otros lenguajes de programación convencionales (por ejemplo, C, C ++, Java, Perl, ..) que hacen este tipo de redondeo (para mí inconsistente)?

¿Que me estoy perdiendo aqui?

ACTUALIZACIÓN: El comentario de @ Li-aungYip sobre "Redondeo del banco" me dio el término / palabras clave de búsqueda correctos para buscar y encontré esta pregunta SO: ¿Por qué .NET usa el redondeo del banco de manera predeterminada? , así que lo leeré con cuidado.

Levon
fuente
27
No tengo tiempo para buscar esto, pero creo que esto se llama "redondeo bancario". Creo que es común en la industria financiera.
Li-aung Yip
2
@sberry bueno, sí, su comportamiento es consistente con su propia descripción. Entonces, si dijera "redondeo" está duplicando su valor y lo hizo, también sería consistente :) .. pero parece contrario a lo que comúnmente significa redondeo . Entonces estoy buscando una mejor comprensión.
Levon
1
@ Li-aungYip Gracias por el plomo re "Redondeo del banquero" .. Lo buscaré.
Levon
1
Relacionado: stackoverflow.com/questions/10093783/…
Sven Marnach
3
Solo una nota: el redondeo de los banqueros no es común solo en las finanzas. Así es como me enseñaron a redondear en la escuela primaria ya en los años 70 :-)
Lennart Regebro

Respuestas:

160

La forma de Python 3.0 se considera el método de redondeo estándar en estos días, aunque algunas implementaciones de lenguaje aún no están en el bus.

La técnica simple de "siempre redondear 0.5 arriba" resulta en un ligero sesgo hacia el número más alto. Con grandes cantidades de cálculos, esto puede ser significativo. El enfoque de Python 3.0 elimina este problema.

Hay más de un método de redondeo de uso común. IEEE 754, el estándar internacional para matemáticas de punto flotante, define cinco métodos de redondeo diferentes (el que usa Python 3.0 es el predeterminado). Y hay otros.

Este comportamiento no es tan conocido como debería ser. AppleScript fue, si no recuerdo mal, uno de los primeros en adoptar este método de redondeo. El roundcomando en AppleScript en realidad ofrece varias opciones, pero el valor predeterminado es redondeado, incluso en IEEE 754. Aparentemente, el ingeniero que implementó el roundcomando se hartó tanto de todas las solicitudes para "hacer que funcione como aprendí en escuela "que implementó precisamente eso: round 2.5 rounding as taught in schooles un comando válido de AppleScript. :-)

un poco
fuente
44
No estaba al tanto de este "método de redondeo estándar predeterminado casi universalmente en estos días", ¿sabría usted (o cualquier otra persona) si C / C ++ / Java / Perl o cualquier otro lenguaje "mainstream" implementa el redondeo de la misma manera?
Levon
3
Ruby lo hace. Los lenguajes .NET de Microsoft lo hacen. Sin embargo, Java no parece. No puedo rastrearlo para todos los idiomas posibles, pero supongo que es más común en idiomas diseñados recientemente. Me imagino que C y C ++ son lo suficientemente viejos como para que no lo sean.
poco
55
ruby regresa 3para2.5.round
jfs
14
Agregué un poco sobre el manejo de AppleScript de esto porque me encanta la forma sarcástica de implementar el comportamiento "antiguo".
poco
2
@kindall Este método ha sido el modo de redondeo predeterminado de IEEE desde 1985 (cuando se publicó IEEE 754-1985). También ha sido el modo de redondeo predeterminado en C desde al menos C89 (y, por lo tanto, también en C ++), sin embargo , desde C99 (y C ++ 11 con soporte esporádico antes), ha estado disponible una función "round ()" que utiliza los lazos se alejan de cero en su lugar. El redondeo interno de coma flotante y la familia de funciones rint () siguen obedeciendo la configuración del modo de redondeo, que por defecto es redondear los lazos a pares.
Wlerin
41

Puede controlar el redondeo que obtiene en Py3000 utilizando el módulo Decimal :

>>> decimal.Decimal('3.5').quantize(decimal.Decimal('1'), 
    rounding=decimal.ROUND_HALF_UP)
>>> Decimal('4')

>>> decimal.Decimal('2.5').quantize(decimal.Decimal('1'),    
    rounding=decimal.ROUND_HALF_EVEN)
>>> Decimal('2')

>>> decimal.Decimal('3.5').quantize(decimal.Decimal('1'), 
    rounding=decimal.ROUND_HALF_DOWN)
>>> Decimal('3')
perro
fuente
Gracias. No estaba familiarizado con este módulo. ¿Alguna idea de cómo obtendría el comportamiento de Python v 2.x? Los ejemplos que muestra no parecen hacer eso. Simplemente curioso si eso sería posible.
Levon
1
@Levon: La constante ROUND_HALF_UPes la misma que el comportamiento anterior de Python 2.X.
dawg
2
También puede establecer un contexto para el módulo Decimal que lo haga implícitamente por usted. Ver la setcontext()función.
poco
Esto es exactamente lo que estaba buscando hoy. Trabajando como se esperaba en Python 3.4.3. También vale la pena señalar, puede controlar cuánto se redondea cambiando quantize(decimal.Decimal('1')a quantize(decimal.Decimal('0.00')si desea redondear a los 100 más cercanos, como por dinero.
Igor
Esta solución funciona como un reemplazo por el round(number, ndigits)tiempo que ndigitssea ​​positivo, pero molestamente no puede usarla para reemplazar algo como round(5, -1).
Pekka Klärck
15

Solo para agregar aquí una nota importante de la documentación:

https://docs.python.org/dev/library/functions.html#round

Nota

El comportamiento de round () para flotadores puede ser sorprendente: por ejemplo, round (2.675, 2) da 2.67 en lugar del esperado 2.68. Esto no es un error: es el resultado del hecho de que la mayoría de las fracciones decimales no se pueden representar exactamente como un flotante. Consulte Aritmética de coma flotante: problemas y limitaciones para obtener más información.

Así que no se sorprenda de obtener los siguientes resultados en Python 3.2:

>>> round(0.25,1), round(0.35,1), round(0.45,1), round(0.55,1)
(0.2, 0.3, 0.5, 0.6)

>>> round(0.025,2), round(0.035,2), round(0.045,2), round(0.055,2)
(0.03, 0.04, 0.04, 0.06)
skif1979
fuente
Vi eso. Y mi primera reacción: ¿Quién está usando una CPU de 16 bits que es incapaz de representar todas las permutaciones de "2.67x"? Decir que las fracciones no se pueden expresar en flotante parece un chivo expiatorio aquí: ninguna CPU moderna es tan inexacta, en CUALQUIER idioma (excepto Python)
Adam
9
@ Adam: Creo que estás malentendido. El formato binario (IEEE 754 binary64) utilizado para almacenar flotantes no puede representar 2.675exactamente: lo más cerca que puede estar la computadora 2.67499999999999982236431605997495353221893310546875. Eso está bastante cerca, pero no es exactamente igual a 2.675: está muy ligeramente más cerca 2.67que a 2.68. Entonces, la roundfunción hace lo correcto y la redondea al valor más cercano de 2 dígitos después del punto, a saber 2.67. Esto no tiene nada que ver con Python, y todo que ver con punto flotante binario.
Mark Dickinson
3
No es "lo correcto" porque se le dio una constante de código fuente :), pero veo su punto.
Adam
@ Adam: Me encontré con esta peculiaridad en JS antes, por lo que no es un idioma específico.
Igor
5

Recientemente tuve problemas con esto también. Por lo tanto, he desarrollado un módulo python 3 que tiene 2 funciones trueround () y trueround_precision () que abordan esto y dan el mismo comportamiento de redondeo al que se usaban desde la escuela primaria (no el redondeo bancario). Aquí está el módulo. Simplemente guarde el código y cópielo o impórtelo. Nota: el módulo trueround_precision puede cambiar el comportamiento de redondeo según las necesidades de acuerdo con los indicadores ROUND_CEILING, ROUND_DOWN, ROUND_FLOOR, ROUND_HALF_DOWN, ROUND_HALF_EVEN, ROUND_HALF_UP, ROUND_UP y ROUND_05UP en el módulo decimal (consulte la documentación de los módulos para obtener más información). Para las funciones a continuación, consulte las cadenas de documentos o use help (trueround) y help (trueround_precision) si se copia en un intérprete para obtener más documentación.

#! /usr/bin/env python3
# -*- coding: utf-8 -*-

def trueround(number, places=0):
    '''
    trueround(number, places)

    example:

        >>> trueround(2.55, 1) == 2.6
        True

    uses standard functions with no import to give "normal" behavior to 
    rounding so that trueround(2.5) == 3, trueround(3.5) == 4, 
    trueround(4.5) == 5, etc. Use with caution, however. This still has 
    the same problem with floating point math. The return object will 
    be type int if places=0 or a float if places=>1.

    number is the floating point number needed rounding

    places is the number of decimal places to round to with '0' as the
        default which will actually return our interger. Otherwise, a
        floating point will be returned to the given decimal place.

    Note:   Use trueround_precision() if true precision with
            floats is needed

    GPL 2.0
    copywrite by Narnie Harshoe <[email protected]>
    '''
    place = 10**(places)
    rounded = (int(number*place + 0.5if number>=0 else -0.5))/place
    if rounded == int(rounded):
        rounded = int(rounded)
    return rounded

def trueround_precision(number, places=0, rounding=None):
    '''
    trueround_precision(number, places, rounding=ROUND_HALF_UP)

    Uses true precision for floating numbers using the 'decimal' module in
    python and assumes the module has already been imported before calling
    this function. The return object is of type Decimal.

    All rounding options are available from the decimal module including 
    ROUND_CEILING, ROUND_DOWN, ROUND_FLOOR, ROUND_HALF_DOWN, ROUND_HALF_EVEN, 
    ROUND_HALF_UP, ROUND_UP, and ROUND_05UP.

    examples:

        >>> trueround(2.5, 0) == Decimal('3')
        True
        >>> trueround(2.5, 0, ROUND_DOWN) == Decimal('2')
        True

    number is a floating point number or a string type containing a number on 
        on which to be acted.

    places is the number of decimal places to round to with '0' as the default.

    Note:   if type float is passed as the first argument to the function, it
            will first be converted to a str type for correct rounding.

    GPL 2.0
    copywrite by Narnie Harshoe <[email protected]>
    '''
    from decimal import Decimal as dec
    from decimal import ROUND_HALF_UP
    from decimal import ROUND_CEILING
    from decimal import ROUND_DOWN
    from decimal import ROUND_FLOOR
    from decimal import ROUND_HALF_DOWN
    from decimal import ROUND_HALF_EVEN
    from decimal import ROUND_UP
    from decimal import ROUND_05UP

    if type(number) == type(float()):
        number = str(number)
    if rounding == None:
        rounding = ROUND_HALF_UP
    place = '1.'
    for i in range(places):
        place = ''.join([place, '0'])
    return dec(number).quantize(dec(place), rounding=rounding)

Espero que esto ayude,

Narnie

narnie
fuente
5

Python 3.x redondea .5 valores a un vecino que es par

assert round(0.5) == 0
assert round(1.5) == 2
assert round(2.5) == 2

import decimal

assert decimal.Decimal('0.5').to_integral_value() == 0
assert decimal.Decimal('1.5').to_integral_value() == 2
assert decimal.Decimal('2.5').to_integral_value() == 2

sin embargo, uno puede cambiar el redondeo decimal "atrás" para siempre redondear .5 hacia arriba, si es necesario:

decimal.getcontext().rounding = decimal.ROUND_HALF_UP

assert decimal.Decimal('0.5').to_integral_value() == 1
assert decimal.Decimal('1.5').to_integral_value() == 2
assert decimal.Decimal('2.5').to_integral_value() == 3

i = int(decimal.Decimal('2.5').to_integral_value()) # to get an int
assert i == 3
assert type(i) is int
kares
fuente
1

Comportamiento de redondeo de Python 2 en Python 3.

Sumando 1 en el decimoquinto lugar decimal. Precisión de hasta 15 dígitos.

round2=lambda x,y=None: round(x+1e-15,y)
SmartManoj
fuente
3
¿Podría explicar la intuición detrás de esta fórmula?
Hadi
2
Por lo que entiendo, las fracciones que no se pueden representar con precisión tendrán hasta 15 9, luego la imprecisión. Por ejemplo, 2.675es 2.67499999999999982236431605997495353221893310546875. Agregar 1e-15 lo inclinará sobre 2.675 y lo redondeará correctamente. si la fracción ya está sobre la constante del código, agregar 1e-15 no cambiará nada al redondeo.
Benoit Dufresne
1

Algunos casos:

in: Decimal(75.29 / 2).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
in: round(75.29 / 2, 2)
out: 37.65 GOOD

in: Decimal(85.55 / 2).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
in: round(85.55 / 2, 2)
out: 42.77 BAD

Para arreglar:

in: round(75.29 / 2 + 0.00001, 2)
out: 37.65 GOOD
in: round(85.55 / 2 + 0.00001, 2)
out: 42.78 GOOD

Si desea más decimales, por ejemplo 4, debe agregar (+ 0.0000001).

Trabaja para mi.

Virako
fuente
Esta fue la única solución que funcionó para mí, gracias por publicar. Todo el mundo parece tener la intención de redondear 0,5 arriba / abajo, por lo que no pude manejar los problemas de redondeo decimal múltiple.
Gayathri
-1

Reproducción de muestra:

['{} => {}'.format(x+0.5, round(x+0.5)) for x in range(10)]

['0.5 => 0', '1.5 => 2', '2.5 => 2', '3.5 => 4', '4.5 => 4', '5.5 => 6', '6.5 => 6', '7.5 => 8', '8.5 => 8', '9.5 => 10']

API: https://docs.python.org/3/library/functions.html#round

Estados:

Número de retorno redondeado a precisión ndigits después del punto decimal. Si se omite ndigits o es None, devuelve el entero más cercano a su entrada.

Para los tipos integrados que admiten round (), los valores se redondean al múltiplo más cercano de 10 a la potencia menos ndigits; Si dos múltiplos están igualmente cerca, el redondeo se realiza hacia la opción par (por ejemplo, tanto la ronda (0.5) como la ronda (-0.5) son 0, y la ronda (1.5) es 2). Cualquier valor entero es válido para ndigits (positivo, cero o negativo). El valor de retorno es un entero si se omiten ndigits o Ninguno. De lo contrario, el valor de retorno tiene el mismo tipo que número.

Para un número de objeto general de Python, redondee los delegados a número. ronda .

Nota El comportamiento de round () para flotadores puede ser sorprendente: por ejemplo, round (2.675, 2) da 2.67 en lugar del esperado 2.68. Esto no es un error: es el resultado del hecho de que la mayoría de las fracciones decimales no se pueden representar exactamente como un flotante. Consulte Aritmética de coma flotante: problemas y limitaciones para obtener más información.

Teniendo en cuenta esta idea, puede usar algunas matemáticas para resolverlo

import math
def my_round(i):
  f = math.floor(i)
  return f if i - f < 0.5 else f+1

ahora puede ejecutar la misma prueba con my_round en lugar de round.

['{} => {}'.format(x + 0.5, my_round(x+0.5)) for x in range(10)]
['0.5 => 1', '1.5 => 2', '2.5 => 3', '3.5 => 4', '4.5 => 5', '5.5 => 6', '6.5 => 7', '7.5 => 8', '8.5 => 9', '9.5 => 10']
Fallenreaper
fuente
-2

La forma más fácil de redondear en Python 3.x como se enseña en la escuela es usar una variable auxiliar:

n = 0.1 
round(2.5 + n)

Y estos serán los resultados de la serie 2.0 a 3.0 (en pasos de 0.1):

>>> round(2 + n)
>>> 2

>>> round(2.1 + n)
>>> 2

>>> round(2.2 + n)
>>> 2

>>> round(2.3 + n)
>>> 2

>>> round(2.4 + n)
>>> 2

>>> round(2.5 + n)
>>> 3

>>> round(2.6 + n)
>>> 3

>>> round(2.7 + n)
>>> 3

>>> round(2.8 + n)
>>> 3

>>> round(2.9 + n)
>>> 3

>>> round(3 + n)
>>> 3
baermathias
fuente
-2

Puede controlar el redondeo utilizando el módulo math.ceil:

import math
print(math.ceil(2.5))
> 3
Eds_k
fuente
Eso siempre devolverá el número sin su parte decimal, esto no es redondeo. ceil (2.5) = 2, ceil (2.99) = 2
krafter
1
en python3 +, si el argumento numérico es un número positivo o negativo, la función ceil devuelve el valor máximo.
Eds_k
En [14]: math.ceil (2.99) Fuera [14]: 3
Eds_k
Sí, lo siento, me equivoqué. Ceil () devuelve el valor del techo, mientras que floor () devuelve el valor del que estaba hablando. Pero aún así, en mi opinión, este no es el comportamiento de redondeo (ambas funciones)
krafter
-4

Prueba este código:

def roundup(input):   
   demo = input  if str(input)[-1] != "5" else str(input).replace("5","6")
   place = len(demo.split(".")[1])-1
   return(round(float(demo),place))

El resultado será:

>>> x = roundup(2.5)
>>> x
3.0  
>>> x = roundup(2.05)
>>> x
2.1 
>>> x = roundup(2.005)
>>> x
2.01 

Salida que puede consultar aquí: https://i.stack.imgur.com/QQUkS.png

Anil Kumar Sahoo
fuente