Python 3.x comportamiento de redondeo

Acabo de volver 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 exactos a mitad de camino 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 incorporados 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 par

Entonces, bajo v2.7.3 :

In [85]: round(2.5) Out[85]: 3.0 In [86]: round(3.5) Out[86]: 4.0 

como lo 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 contraintuitivo y contrario a lo que entiendo sobre el redondeo (y está obligado a hacer tropezar a las personas). El inglés no es mi idioma nativo, pero hasta que leí esto, pensé que sabía lo que significaba redondear: – / Estoy seguro de que en el momento en que se introdujo v3, debió haber una discusión sobre esto, pero no pude encontrar una buena razón en mi busqueda

  1. ¿Alguien tiene una idea de por qué esto se cambió a esto?
  2. ¿Hay otros lenguajes de progtwigción principales (p. Ej., 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 de banqueros” me dio el término de búsqueda / palabras clave correctas para buscar y encontré esta pregunta SO: ¿Por qué .NET usa el redondeo de banqueros como predeterminado? , así que estaré leyendo eso con cuidado.

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 “siempre redondear 0.5 arriba” da como resultado un ligero sesgo hacia el número más alto. Con un gran número 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 las matemáticas de punto flotante, define cinco métodos de redondeo diferentes (el que utiliza Python 3.0 es el predeterminado). Y hay otros.

Este comportamiento no es tan conocido como debería ser. AppleScript fue, si recuerdo correctamente, uno de los primeros en adoptar este método de redondeo. El comando round en AppleScript en realidad ofrece varias opciones, pero round-to-even es la opción predeterminada, como lo es en IEEE 754. Aparentemente, el ingeniero que implementó el comando round se hartó de todas las solicitudes para “hacer que funcione como yo”. aprendió en la escuela “que implementó justo eso: el round 2.5 rounding as taught in school es un comando válido de AppleScript. 🙂

Puede controlar el redondeo que obtiene en Py3000 usando 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') 

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 flotador. Consulte Aritmética de punto flotante: problemas y limitaciones para obtener más información.

Entonces, 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) 

Recientemente tuve problemas con esto, también. Por lo tanto, he desarrollado un módulo de python 3 que tiene 2 funciones: trueround () y trueround_precision () que abordan esto y dan el mismo comportamiento de redondeo al que están acostumbrados desde la escuela primaria (no al redondeo de banqueros). 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 ROUND_CEILING, ROUND_DOWN, ROUND_FLOOR, ROUND_HALF_DOWN, ROUND_HALF_EVEN, ROUND_HALF_UP, ROUND_UP, y ROUND_05UP en el módulo de datos ( Para ver las funciones a continuación, consulte las cadenas de documentación o use help (trueround) y help (trueround_precision) si se copian 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  ''' 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  ''' 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

Python 3.x redondea valores de .5 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, se puede cambiar el redondeo decimal “atrás” para redondear siempre .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 

Python 2 comportamiento de redondeo en python 3.

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

 round2=lambda x,y=None: round(x+1e-15,y) 

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 arreglarlo:

 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.

Reproducción de la 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:

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

Para los tipos incorporados 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 número entero si se omite ndigits o Ninguno. De lo contrario, el valor de retorno tiene el mismo tipo que el número.

Para un número de objeto de Python general, redondee los delegates a número. redondo

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 flotador. Consulte Aritmética de punto flotante: problemas y limitaciones para obtener más información.

Teniendo en cuenta esta información, puedes 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'] 

El operador de la ronda redondeará el valor al valor entero más cercano.

Por ejemplo :

Si el valor es mayor que o.5, se redondeará a 1

 print(round(211.5554, 2)) // output is 211.56 

Si el valor es inferior a 0,5, se redondeará a 0

 print(round(211.5544, 2)) // output is 211.55 

Editar :

El operador // mencionado anteriormente no se usa para redondear, se usa, por ejemplo, para manejar la salida flotante de la división.

  print(10//3) // output is 3 instead of 3.3333333333333335