Sé que las preguntas sobre el redondeo en Python ya se han hecho varias veces, pero las respuestas no me ayudaron. Estoy buscando un método que redondea un número flotante a la mitad y devuelve un número flotante. El método también debe aceptar un parámetro que defina el lugar decimal a redondear. Escribí un método que implementa este tipo de redondeo. Sin embargo, creo que no se ve elegante en absoluto.
def round_half_up(number, dec_places):
s = str(number)
d = decimal.Decimal(s).quantize(
decimal.Decimal(10) ** -dec_places,
rounding=decimal.ROUND_HALF_UP)
return float(d)
No me gusta, que tengo que convertir flotante a una cadena (para evitar imprecisiones de coma flotante) y luego trabajar con el módulo decimal . ¿Tienes alguna solución mejor?
Editar: como se señala en las respuestas a continuación, la solución a mi problema no es tan obvia ya que el redondeo correcto requiere la representación correcta de los números en primer lugar y este no es el caso con el flotador. Así que esperaría que el siguiente código
def round_half_up(number, dec_places):
d = decimal.Decimal(number).quantize(
decimal.Decimal(10) ** -dec_places,
rounding=decimal.ROUND_HALF_UP)
return float(d)
(que difiere del código anterior solo por el hecho de que el número flotante se convierte directamente en un número decimal y no en una cadena primero) para devolver 2.18 cuando se usa de esta manera: round_half_up(2.175, 2)
Pero no lo hace porque Decimal(2.175)
regresará Decimal('2.17499999999999982236431605997495353221893310546875')
, de la forma en que flota El número está representado por la computadora. Sorprendentemente, el primer código devuelve 2,18 porque el número flotante se convierte primero en una cadena. Parece que la función str () realiza un redondeo implícito al número que inicialmente debía redondearse. Entonces hay dos redondeos que tienen lugar. Aunque este es el resultado que esperaría, es técnicamente incorrecto.
fuente
Respuestas:
El redondeo es sorprendentemente difícil de hacer bien , porque debes manejar los cálculos de punto flotante con mucho cuidado. Si está buscando una solución elegante (corta, fácil de entender), lo que le gusta es un buen punto de partida. Para ser correcto, debe reemplazarlo
decimal.Decimal(str(number))
con la creación del decimal a partir del número mismo, lo que le dará una versión decimal de su representación exacta:Decimal(str(number))
efectivamente se redondea dos veces , ya que formatear el flotador en la representación de cadena realiza su propio redondeo. Esto se debe astr(float value)
que no intentará imprimir la representación decimal completa del flotante, solo imprimirá suficientes dígitos para garantizar que obtenga el mismo flotante si pasa esos dígitos exactos alfloat
constructor.Si desea conservar el redondeo correcto, pero evite depender del
decimal
módulo grande y complejo , ciertamente puede hacerlo, pero aún necesitará alguna forma de implementar la aritmética exacta necesaria para el redondeo correcto. Por ejemplo, puedes usar fracciones :Tenga en cuenta que la única parte difícil en el código anterior es la conversión precisa de un punto flotante a una fracción, y que puede descargarse al
as_integer_ratio()
método flotante, que es lo que hacen internamente los decimales y las fracciones. Entonces, si realmente desea eliminar la dependenciafractions
, puede reducir la aritmética fraccional a aritmética entera pura; permanece dentro del mismo recuento de líneas a expensas de cierta legibilidad:Tenga en cuenta que probar estas funciones resalta las aproximaciones realizadas al crear números de punto flotante. Por ejemplo, se
print(round_half_up(2.175, 2))
imprime2.17
porque el número decimal2.175
no se puede representar exactamente en binario, por lo que se reemplaza por una aproximación que es ligeramente más pequeña que el decimal 2.175. La función recibe ese valor, lo encuentra más pequeño que la fracción real correspondiente al decimal 2.175 y decide redondearlo hacia abajo . Esto no es una peculiaridad de la implementación; el comportamiento se deriva de las propiedades de los números de coma flotante y también está presente en laround
versión incorporada de Python 3 y 2 .fuente
d = Decimal(number).quantize(...)
es que no hay una representación exacta de números flotantes. EntoncesDecimal(2.175)
le dará Decimal ('2.17499999 ...') (y por lo tanto el redondeo será incorrecto) mientras queDecimal('2.175')
es Decimal ('2.175'). Es por eso que estoy convirtiendo a cadena primero.as_integer_ratio()
(en el caso de 0,1, que sería 3602879701896397/36028797018963968). Eldecimal.Decimal(float)
constructor usa esa representación y el redondeo posterior será correcto y se realizará exactamente.round_half_up(2.175, 2)
y me devolvió 2.17, lo cual es incorrecto. ¿Quizás hay algo más que estoy haciendo mal?'%.20f' % 2.175
que se evalúa'2.17499999999999982236'
). Por ejemplo, en Python 2.7, que usa redondeo a medias, round (2.175) devuelve 2.17, y este tipo de resultado se documenta como una limitación de punto flotante.round
(que implementa el redondeo a la mitad) tampoco es lo suficientemente bueno. Edite la pregunta para especificar el caso de uso real; tal vez se satisfaga evitando flotadores y utilizando decimales.Si; use
Decimal
para representar sus números en todo su programa, si necesita representar números como 2.675 exactamente y redondearlos a 2.68 en lugar de 2.67.No hay otra manera. El número de coma flotante que se muestra en la pantalla como 2.675 no es el número real 2.675; de hecho, es muy ligeramente inferior a 2.675, por lo que se redondea a 2.67:
Solo se muestra en forma de cadena
'2.675'
porque es la cadena más corta como esafloat(s) == 2.6749999999999998
. Tenga en cuenta que esta representación más larga (con muchos 9s) tampoco es exacta.Independientemente de cómo escriba su función de redondeo, no es posible
my_round(2.675, 2)
redondear hacia arriba2.68
y tampocomy_round(2 + 0.6749999999999998, 2)
redondear hacia abajo2.67
; porque las entradas son en realidad el mismo número de coma flotante.Entonces, si su número 2.675 alguna vez se convierte en un flotador y regresa, ya ha perdido la información sobre si debe redondearse hacia arriba o hacia abajo. La solución no es hacer que flote en primer lugar.
fuente
Después de intentar durante mucho tiempo producir una elegante función de una línea, terminé obteniendo algo comparable a un diccionario de tamaño.
Yo diría que la forma más sencilla de hacer esto es simplemente
Reconocería que esto no es exacto en todos los casos, pero debería funcionar si solo desea una solución rápida y simple.
fuente
round_half_up(1.499999999, 0)
devuelve 2.0 en lugar de 1.0.