round () no parece estar redondeando correctamente


123

La documentación para la función round () indica que le pasa un número y las posiciones pasan del decimal a round. Por lo tanto, debería hacer esto:

n = 5.59
round(n, 1) # 5.6

Pero, en realidad, la vieja rareza de punto flotante se arrastra y obtienes:

5.5999999999999996

A los efectos de la interfaz de usuario, necesito mostrar 5.6. Busqué en Internet y encontré algo de documentación que depende de mi implementación de Python. Desafortunadamente, esto ocurre tanto en mi máquina de desarrollo de Windows como en cada servidor Linux que he probado. Ver aquí también .

A falta de crear mi propia biblioteca redonda, ¿hay alguna forma de evitar esto?


44
Intenté esto con Python 2.7.11 round (5.59) y está dando un resultado de 5.6 en Windows y Linux x86 de 64 bits, Cython (supongo que el enlace de documentación mencionado ahora cambia)
Alex Punnen

2
Donde realmente no funciona correctamente es round(5.55, 1) = 5.5.
Dmitry

Respuestas:


101

No puedo evitar la forma en que está almacenado, pero al menos el formateo funciona correctamente:

'%.1f' % round(n, 1) # Gives you '5.6'

11
lo intenté print '%.2f' % 655.665pero vuelve 655.66, debería serlo655.67
Liza

1
@ Kyrie ver stackoverflow.com/questions/9301690/… . Aquí la culpa es la inexactitud del punto flotante: "5.665 -> 5.67" pero "15.665 -> 15.66". Use decimales si necesita precisión exacta.
Jimmy

77
esto funciona después de buscar :) from decimal import Decimal, ROUND_HALF_UP, ROUND_HALF_DOWN# uso para redondear números flotantes Decimal(str(655.665)).quantize(Decimal('1.11'), rounding=ROUND_HALF_UP)# Problemas y limitaciones en puntos flotantes
Liza

102

El formateo funciona correctamente incluso sin tener que redondear:

"%.1f" % n

18
Según los documentos , este estilo de formato de cadena eventualmente desaparecerá. El formato de nuevo estilo sería"{:.1f}".format(n)
whereswalden

2
No se redondea correctamente: '%.5f' % 0.988625da0.98862
schlamar

@schlamar: Ese también es el comportamiento de round (): round (0.988625,5) también da 0.98862. ronda (0.988626,5) así como "% .5f"% 0.988626 dar 0.98863
Vinko Vrsalovic

desafortunadamente "% .2f"% 2.675 devolverá 2.67, lo que podría ser una respuesta inesperada para aquellos que usan este método y esperan 2.68
Dion

30

Si usa el módulo Decimal, puede aproximarse sin el uso de la función 'redonda'. Esto es lo que he estado usando para redondear, especialmente al escribir aplicaciones monetarias:

Decimal(str(16.2)).quantize(Decimal('.01'), rounding=ROUND_UP)

Esto devolverá un número decimal que es 16.20.


44
Esta es la respuesta canónica , donde la precisión importa, de todos modos, que está prácticamente en todas partes. Claro: es un poco detallado . Pero agregue ese imbécil en una función auxiliar y estará listo para formatear y listo.
Cecil Curry

2
rounding='ROUND_UP'
LMc

Si obtiene este error NameError: global name 'ROUND_UP' is not definedes necesario importar su función de redondeo: from decimal import Decimal, ROUND_UP. Otras funciones de redondeo
Stephen Blair

Su ejemplo todavía parece peligroso: confía en el redondeo proporcionado por str ().
YvesgereY

21

round(5.59, 1)está funcionando bien El problema es que 5.6 no se puede representar exactamente en coma flotante binaria.

>>> 5.6
5.5999999999999996
>>> 

Como dice Vinko, puede usar el formato de cadena para redondear para mostrar.

Python tiene un módulo para aritmética decimal si lo necesita.


1
Esto ya no es un problema con Python 2.7 o Python 3.5
vy32


10

Puede cambiar el tipo de datos a un entero:

>>> n = 5.59
>>> int(n * 10) / 10.0
5.5
>>> int(n * 10 + 0.5)
56

Y luego muestre el número insertando el separador decimal de la localidad.

Sin embargo, la respuesta de Jimmy es mejor.


5

La matemática de punto flotante es vulnerable a imprecisiones de precisión leves pero molestas. Si puede trabajar con un número entero o fijo, se le garantizará la precisión.


5

Echa un vistazo al módulo Decimal

Decimal "se basa en un modelo de punto flotante que fue diseñado pensando en las personas y necesariamente tiene un principio rector fundamental: las computadoras deben proporcionar una aritmética que funcione de la misma manera que la aritmética que las personas aprenden en la escuela". - extracto de la especificación aritmética decimal.

y

Los números decimales se pueden representar exactamente. En contraste, números como 1.1 y 2.2 no tienen representaciones exactas en coma flotante binaria. Los usuarios finales generalmente no esperarían que 1.1 + 2.2 se muestre como 3.3000000000000003 como lo hace con punto flotante binario.

Decimal proporciona el tipo de operaciones que facilitan la escritura de aplicaciones que requieren operaciones de punto flotante y también necesitan presentar esos resultados en un formato legible para humanos, por ejemplo, contabilidad.



4

Es un gran problema de hecho. Prueba este código:

print "%.2f" % (round((2*4.4+3*5.6+3*4.4)/8,2),)

Muestra 4.85. Entonces haces:

print "Media = %.1f" % (round((2*4.4+3*5.6+3*4.4)/8,1),)

y muestra 4.8. ¿Hacen cálculos a mano? La respuesta exacta es 4.85, pero si intentan:

print "Media = %.20f" % (round((2*4.4+3*5.6+3*4.4)/8,20),)

puedes ver la verdad: el punto flotante se almacena como la suma finita más cercana de fracciones cuyos denominadores son potencias de dos.


3

Puede usar el operador de formato de cadena %, similar a sprintf.

mystring = "%.2f" % 5.5999


2

Estoy haciendo:

int(round( x , 0))

En este caso, primero redondeamos adecuadamente a nivel de unidad, luego lo convertimos a entero para evitar imprimir un flotante.

entonces

>>> int(round(5.59,0))
6

Creo que esta respuesta funciona mejor que formatear la cadena, y también tiene más sentido para mí usar la función redonda.


2

Evitaría confiar round()en absoluto en este caso. Considerar

print(round(61.295, 2))
print(round(1.295, 2))

saldrá

61.3
1.29

que no es una salida deseada si necesita redondeo sólido al entero más cercano. Para evitar este comportamiento, vaya con math.ceil()(o math.floor()si desea redondear hacia abajo):

from math import ceil
decimal_count = 2
print(ceil(61.295 * 10 ** decimal_count) / 10 ** decimal_count)
print(ceil(1.295 * 10 ** decimal_count) / 10 ** decimal_count)

salidas

61.3
1.3

Espero que ayude.


1

Código:

x1 = 5.63
x2 = 5.65
print(float('%.2f' % round(x1,1)))  # gives you '5.6'
print(float('%.2f' % round(x2,1)))  # gives you '5.7'

Salida:

5.6
5.7

0

Aquí es donde veo que falla la ronda. ¿Qué pasaría si quisieras redondear estos 2 números a un decimal? 23.45 23.55 Mi educación fue que al redondear estos debería obtener: 23.4 23.6 la "regla" es que debe redondear si el número anterior era impar, no redondear si el número anterior era par. La función redonda en python simplemente trunca el 5.


1
De lo que estás hablando es de "redondeo bancario" , una de las muchas formas diferentes de realizar redondeo.
Simon MᶜKenzie

0

El problema es solo cuando el último dígito es 5. Ej. 0.045 se almacena internamente como 0.044999999999999 ... Simplemente puede incrementar el último dígito a 6 y redondear. Esto te dará los resultados deseados.

import re


def custom_round(num, precision=0):
    # Get the type of given number
    type_num = type(num)
    # If the given type is not a valid number type, raise TypeError
    if type_num not in [int, float, Decimal]:
        raise TypeError("type {} doesn't define __round__ method".format(type_num.__name__))
    # If passed number is int, there is no rounding off.
    if type_num == int:
        return num
    # Convert number to string.
    str_num = str(num).lower()
    # We will remove negative context from the number and add it back in the end
    negative_number = False
    if num < 0:
        negative_number = True
        str_num = str_num[1:]
    # If number is in format 1e-12 or 2e+13, we have to convert it to
    # to a string in standard decimal notation.
    if 'e-' in str_num:
        # For 1.23e-7, e_power = 7
        e_power = int(re.findall('e-[0-9]+', str_num)[0][2:])
        # For 1.23e-7, number = 123
        number = ''.join(str_num.split('e-')[0].split('.'))
        zeros = ''
        # Number of zeros = e_power - 1 = 6
        for i in range(e_power - 1):
            zeros = zeros + '0'
        # Scientific notation 1.23e-7 in regular decimal = 0.000000123
        str_num = '0.' + zeros + number
    if 'e+' in str_num:
        # For 1.23e+7, e_power = 7
        e_power = int(re.findall('e\+[0-9]+', str_num)[0][2:])
        # For 1.23e+7, number_characteristic = 1
        # characteristic is number left of decimal point.
        number_characteristic = str_num.split('e+')[0].split('.')[0]
        # For 1.23e+7, number_mantissa = 23
        # mantissa is number right of decimal point.
        number_mantissa = str_num.split('e+')[0].split('.')[1]
        # For 1.23e+7, number = 123
        number = number_characteristic + number_mantissa
        zeros = ''
        # Eg: for this condition = 1.23e+7
        if e_power >= len(number_mantissa):
            # Number of zeros = e_power - mantissa length = 5
            for i in range(e_power - len(number_mantissa)):
                zeros = zeros + '0'
            # Scientific notation 1.23e+7 in regular decimal = 12300000.0
            str_num = number + zeros + '.0'
        # Eg: for this condition = 1.23e+1
        if e_power < len(number_mantissa):
            # In this case, we only need to shift the decimal e_power digits to the right
            # So we just copy the digits from mantissa to characteristic and then remove
            # them from mantissa.
            for i in range(e_power):
                number_characteristic = number_characteristic + number_mantissa[i]
            number_mantissa = number_mantissa[i:]
            # Scientific notation 1.23e+1 in regular decimal = 12.3
            str_num = number_characteristic + '.' + number_mantissa
    # characteristic is number left of decimal point.
    characteristic_part = str_num.split('.')[0]
    # mantissa is number right of decimal point.
    mantissa_part = str_num.split('.')[1]
    # If number is supposed to be rounded to whole number,
    # check first decimal digit. If more than 5, return
    # characteristic + 1 else return characteristic
    if precision == 0:
        if mantissa_part and int(mantissa_part[0]) >= 5:
            return type_num(int(characteristic_part) + 1)
        return type_num(characteristic_part)
    # Get the precision of the given number.
    num_precision = len(mantissa_part)
    # Rounding off is done only if number precision is
    # greater than requested precision
    if num_precision <= precision:
        return num
    # Replace the last '5' with 6 so that rounding off returns desired results
    if str_num[-1] == '5':
        str_num = re.sub('5$', '6', str_num)
    result = round(type_num(str_num), precision)
    # If the number was negative, add negative context back
    if negative_number:
        result = result * -1
    return result

0

Otra opción potencial es:

def hard_round(number, decimal_places=0):
    """
    Function:
    - Rounds a float value to a specified number of decimal places
    - Fixes issues with floating point binary approximation rounding in python
    Requires:
    - `number`:
        - Type: int|float
        - What: The number to round
    Optional:
    - `decimal_places`:
        - Type: int 
        - What: The number of decimal places to round to
        - Default: 0
    Example:
    ```
    hard_round(5.6,1)
    ```
    """
    return int(number*(10**decimal_places)+0.5)/(10**decimal_places)

-4

Qué pasa:

round(n,1)+epsilon

Eso solo funcionaría si el redondeo fuera consistentemente del número de ronda por epsilon. Si epsilon = .000001entonces round(1.0/5.0, 1) + epsilontomaría la representación precisa 0.2 y la haría 0.00001. Problemas igualmente malos ocurrirían si el épsilon estuviera dentro de la función redonda.
Michael Scott Cuthbert
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.