round () non sembra arrotondare correttamente


123

La documentazione per la funzione round () afferma che gli si passa un numero e le posizioni oltre il decimale da arrotondare. Quindi dovrebbe fare questo:

n = 5.59
round(n, 1) # 5.6

Ma, in realtà, la buona vecchia stranezza in virgola mobile si insinua e ottieni:

5.5999999999999996

Ai fini dell'interfaccia utente, ho bisogno di visualizzare 5.6. Ho cercato in Internet e ho trovato della documentazione che dipende dalla mia implementazione di Python. Sfortunatamente, questo si verifica sia sulla mia macchina di sviluppo Windows che su ogni server Linux che ho provato. Vedi anche qui .

A meno di creare la mia libreria rotonda, c'è un modo per aggirare questo?


4
Ho provato questo con Python 2.7.11 round (5.59) e sta dando il risultato come 5.6 sia su Windows che su Linux x86 a 64 bit, Cython? (Il collegamento alla documentazione menzionato è cambiato ora immagino)
Alex Punnen

2
Dove in realtà non funziona correttamente è round(5.55, 1) = 5.5.
Dmitry

Risposte:


102

Non posso aiutare il modo in cui è memorizzato, ma almeno la formattazione funziona correttamente:

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

11
ho provato print '%.2f' % 655.665ma ritorna 655.66, dovrebbe essere655.67
Liza

1
@Kyrie vedere stackoverflow.com/questions/9301690/... . Qui la colpa è dell'imprecisione in virgola mobile: "5.665 -> 5.67" ma "15.665 -> 15.66". Usa i decimali se hai bisogno di una precisione esatta.
Jimmy

7
questo funziona dopo la ricerca :) from decimal import Decimal, ROUND_HALF_UP, ROUND_HALF_DOWN# da usare nell'arrotondare i numeri fluttuanti Decimal(str(655.665)).quantize(Decimal('1.11'), rounding=ROUND_HALF_UP)# Problemi e limitazioni in virgola mobile
Liza

102

La formattazione funziona correttamente anche senza dover arrotondare:

"%.1f" % n

18
Secondo i documenti , questo stile di formattazione delle stringhe alla fine scomparirà. Il formato del nuovo stile sarebbe"{:.1f}".format(n)
Whereeswalden

2
Non arrotonda correttamente: '%.5f' % 0.9886250.98862
schlamar

@schlamar: Anche questo è il comportamento di round (): round (0.988625,5) dà anche 0.98862. round (0.988626,5) e "% .5f"% 0.988626 danno 0.98863
Vinko Vrsalovic

sfortunatamente "% .2f"% 2.675 restituirà 2.67 - che potrebbe essere una risposta inaspettata per coloro che utilizzano questo metodo e si aspettano 2.68
Dion

30

Se usi il modulo Decimal puoi approssimare senza usare la funzione 'round'. Ecco cosa ho usato per l'arrotondamento, soprattutto durante la scrittura di applicazioni monetarie:

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

Ciò restituirà un numero decimale che è 16,20.


4
Questa è la risposta canonica - dove l'accuratezza conta, comunque, che è praticamente ovunque. Certo: è un po 'prolisso . Ma getta quella ventosa in una funzione di aiuto e sei pronto per formattare e andare.
Cecil Curry

2
rounding='ROUND_UP'
LMc

Se si ottiene questo errore NameError: global name 'ROUND_UP' is not definedè necessario importare la funzione di arrotondamento: from decimal import Decimal, ROUND_UP. Altre funzioni di arrotondamento
Stephen Blair

Il tuo esempio sembra ancora pericoloso: ti affidi all'arrotondamento fornito da str ().
YvesgereY

21

round(5.59, 1)funziona bene. Il problema è che 5.6 non può essere rappresentato esattamente in virgola mobile binaria.

>>> 5.6
5.5999999999999996
>>> 

Come dice Vinko, puoi utilizzare la formattazione delle stringhe per eseguire l'arrotondamento per la visualizzazione.

Python ha un modulo per l'aritmetica decimale se ne hai bisogno.


1
Questo non è più un problema con Python 2.7 o Python 3.5
vy32


10

Puoi cambiare il tipo di dati in un numero intero:

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

Quindi visualizza il numero inserendo il separatore decimale della lingua.

Tuttavia, la risposta di Jimmy è migliore.


5

La matematica in virgola mobile è vulnerabile a piccole, ma fastidiose, imprecisioni di precisione. Se puoi lavorare con numeri interi o punti fissi, ti sarà garantita la precisione.


5

Dai un'occhiata al modulo Decimal

Decimal "si basa su un modello a virgola mobile che è stato progettato pensando alle persone e ha necessariamente un principio guida fondamentale: i computer devono fornire un'aritmetica che funzioni allo stesso modo dell'aritmetica che le persone imparano a scuola". - estratto dalla specifica aritmetica decimale.

e

I numeri decimali possono essere rappresentati esattamente. Al contrario, numeri come 1.1 e 2.2 non hanno una rappresentazione esatta in virgola mobile binaria. Gli utenti finali in genere non si aspettano che 1.1 + 2.2 venga visualizzato come 3.3000000000000003 come avviene con la virgola mobile binaria.

Decimal fornisce il tipo di operazioni che semplificano la scrittura di app che richiedono operazioni in virgola mobile e devono anche presentare tali risultati in un formato leggibile dall'uomo, ad esempio la contabilità.



4

È davvero un grosso problema. Prova questo codice:

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

Visualizza 4.85. Quindi fai:

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

e mostra 4.8. Calcoli a mano la risposta esatta è 4.85, ma se provi:

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

puoi vedere la verità: il float point è memorizzato come la somma finita più vicina di frazioni i cui denominatori sono potenze di due.


3

È possibile utilizzare l'operatore di formato stringa %, simile a sprintf.

mystring = "%.2f" % 5.5999


2

Sto facendo:

int(round( x , 0))

In questo caso, prima arrotondiamo correttamente a livello di unità, quindi convertiamo in numero intero per evitare di stampare un float.

così

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

Penso che questa risposta funzioni meglio della formattazione della stringa e mi ha anche più sensato usare la funzione round.


2

In round()questo caso eviterei di fare affidamento su tutto. Tener conto di

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

verrà prodotto

61.3
1.29

che non è un output desiderato se è necessario un arrotondamento continuo al numero intero più vicino. Per aggirare questo comportamento vai con math.ceil()(o math.floor()se vuoi arrotondare per difetto):

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)

uscite

61.3
1.3

Spero che aiuti.


1

Codice:

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'

Produzione:

5.6
5.7

0

Ecco dove vedo che il round fallisce. E se volessi arrotondare questi 2 numeri a una cifra decimale? 23.45 23.55 La mia educazione era che dall'arrotondare questi dovresti ottenere: 23.4 23.6 la "regola" era che dovresti arrotondare per eccesso se il numero precedente era dispari, non arrotondare per eccesso se il numero precedente era pari. La funzione round in python tronca semplicemente il 5.


1
Quello di cui stai parlando è "arrotondamento bancario" , uno dei tanti modi diversi per eseguire l'arrotondamento.
Simon MᶜKenzie,

0

Il problema è solo quando l'ultima cifra è 5. Ad es. 0,045 viene memorizzato internamente come 0,044999999999999 ... Puoi semplicemente incrementare l'ultima cifra a 6 e arrotondare. Questo ti darà i risultati desiderati.

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

Un'altra potenziale opzione è:

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

Che dire:

round(n,1)+epsilon

Ciò funzionerebbe solo se l'arrotondamento fosse costantemente diverso dal numero tondo di epsilon. Se epsilon = .000001allora round(1.0/5.0, 1) + epsilonprenderebbe la rappresentazione precisa 0.2 e la renderebbe 0.00001. Problemi altrettanto gravi si verificherebbero se l'epsilon fosse all'interno della funzione round.
Michael Scott Cuthbert,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.