Python 3.x comportamento di arrotondamento


176

Stavo solo rileggendo le novità di Python 3.0 e afferma:

La strategia di arrotondamento della funzione round () e il tipo di ritorno sono cambiati. I casi a metà esatta sono ora arrotondati al risultato pari più vicino anziché a zero. (Ad esempio, round (2.5) ora restituisce 2 anziché 3.)

e la documentazione per round :

Per i tipi integrati che supportano round (), i valori sono arrotondati al multiplo più vicino di 10 alla potenza meno n; se due multipli sono ugualmente vicini, l'arrotondamento viene effettuato verso la scelta pari

Quindi, sotto v2.7.3 :

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

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

come mi sarei aspettato. Tuttavia, ora sotto v3.2.3 :

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

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

Questo sembra contro-intuitivo e contrario a quello che capisco sull'arrotondamento (e destinato a inciampare). L'inglese non è la mia lingua madre, ma fino a quando non ho letto questo ho pensato di sapere cosa significasse arrotondamento: - / Sono sicuro che al momento dell'introduzione della v3 ci fosse stata qualche discussione su questo, ma non sono riuscito a trovare una buona ragione in la mia ricerca.

  1. Qualcuno ha un'idea del perché questo è stato cambiato in questo?
  2. Esistono altri linguaggi di programmazione tradizionali (ad es. C, C ++, Java, Perl, ..) che eseguono questo tipo di arrotondamento (per me incoerente)?

Cosa mi sto perdendo qui?

AGGIORNAMENTO: Il commento di @ Li-aungYip su "Arrotondamento del banco" mi ha dato il termine di ricerca / le parole chiave giuste da cercare e ho trovato questa domanda SO: Perché .NET utilizza l'arrotondamento del banco come predefinito? , quindi lo leggerò attentamente.


28
Non ho tempo di cercarlo, ma credo che questo sia chiamato "arrotondamento del banchiere". Credo che sia comune nel settore finanziario.
Li-aung Yip,

2
@sberry bene, sì, il suo comportamento è coerente con la sua stessa descrizione. Quindi se dicesse che "arrotondamento" sta raddoppiando il suo valore e lo ha fatto, sarebbe anche coerente :) .. ma sembra contrario a ciò che comunemente significa arrotondamento . Quindi sto cercando una migliore comprensione.
Levon,

1
@ Li-aungYip Grazie per il lead re "arrotondamento del banco" .. Lo cercherò.
Levon,


3
Solo una nota: l'arrotondamento dei banchieri non è comune solo nella finanza. È così che mi hanno insegnato a frequentare la scuola elementare già negli anni '70 :-)
Lennart Regebro

Risposte:


160

Il metodo di Python 3.0 è attualmente considerato il metodo di arrotondamento standard, sebbene alcune implementazioni del linguaggio non siano ancora sul bus.

La semplice tecnica "arrotondare sempre 0,5 in su" provoca una leggera deviazione verso il numero più alto. Con un gran numero di calcoli, questo può essere significativo. L'approccio Python 3.0 elimina questo problema.

Esiste più di un metodo di arrotondamento di uso comune. IEEE 754, lo standard internazionale per la matematica in virgola mobile, definisce cinque diversi metodi di arrotondamento (quello utilizzato da Python 3.0 è l'impostazione predefinita). E ce ne sono altri.

Questo comportamento non è così conosciuto come dovrebbe essere. Se ricordo bene, AppleScript è stato uno dei primi ad adottare questo metodo di arrotondamento. Il roundcomando in AppleScript in realtà offre diverse opzioni, ma round-into-even è l'impostazione predefinita come in IEEE 754. Apparentemente l'ingegnere che ha implementato il roundcomando è stato così stufo di tutte le richieste di "farlo funzionare come ho imparato in scuola "che ha implementato proprio questo: round 2.5 rounding as taught in schoolè un comando AppleScript valido. :-)


4
Non ero a conoscenza di questo "metodo di arrotondamento standard predefinito praticamente universalmente in questi giorni", tu (o chiunque altro) sapresti se C / C ++ / Java / Perl o altri linguaggi "flusso principale" implementano l'arrotondamento allo stesso modo?
Levon,

3
Lo fa Ruby. I linguaggi .NET di Microsoft lo fanno. Java non sembra, però. Non riesco a rintracciarlo per ogni lingua possibile, ma immagino che sia più comune nelle lingue progettate abbastanza di recente. Immagino che C e C ++ siano abbastanza vecchi da non farlo.
kindall

5
ruby ritorna 3per2.5.round
jfs

14
Ho aggiunto qualcosa sulla gestione di questo AppleScript perché amo il modo sarcastico in cui viene implementato il comportamento "vecchio".
kindall

2
@kindall Questo metodo è la modalità di arrotondamento IEEE predefinita dal 1985 (quando è stato pubblicato IEEE 754-1985). È stata anche la modalità di arrotondamento predefinita in C da almeno C89 (e quindi anche in C ++), tuttavia , poiché C99 (e C ++ 11 con supporto sporadico prima) era disponibile una funzione "round ()" che utilizza i legami si arrotondano invece da zero. L'arrotondamento interno in virgola mobile e la famiglia di funzioni rint () obbediscono ancora all'impostazione della modalità di arrotondamento, che per impostazione predefinita è arrotondare i legami con pari.
Wlerin,

41

Puoi controllare l'arrotondamento che ottieni in Py3000 usando il modulo Decimale :

>>> 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')

Grazie .. Non avevo familiarità con questo modulo. Qualche idea su come avrei ottenuto il comportamento di Python v 2.x? Gli esempi che mostri non sembrano farlo. Solo curioso se ciò sarebbe possibile.
Levon,

1
@Levon: la costante ROUND_HALF_UPè la stessa del vecchio comportamento di Python 2.X.
dawg

2
Puoi anche impostare un contesto per il modulo Decimale che lo fa implicitamente per te. Vedi la setcontext()funzione
kindall

Questo è esattamente quello che stavo cercando oggi. Funziona come previsto in Python 3.4.3. Vale anche la pena notare che puoi controllare quanto arrotonda cambiando quantize(decimal.Decimal('1')in quantize(decimal.Decimal('0.00')se vuoi arrotondare ai 100 più vicini come per soldi.
Igor,

Questa soluzione funziona in sostituzione round(number, ndigits)finché ndigitsè positiva, ma fastidiosamente non puoi usarla per sostituire qualcosa del genere round(5, -1).
Pekka Klärck,

15

Solo per aggiungere qui una nota importante dalla documentazione:

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

Nota

Il comportamento di round () per i float può essere sorprendente: ad esempio round (2.675, 2) fornisce 2,67 invece del previsto 2,68. Questo non è un bug: è il risultato del fatto che la maggior parte delle frazioni decimali non può essere rappresentata esattamente come un float. Vedi Aritmetica in virgola mobile: problemi e limitazioni per ulteriori informazioni.

Quindi non essere sorpreso di ottenere i seguenti risultati in 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)

L'ho visto. E la mia prima reazione: chi sta usando una CPU a 16 bit che non è in grado di rappresentare tutte le permutazioni di "2.67x"? Dire che le frazioni non possono essere espresse in float sembra un capro espiatorio qui: nessuna CPU moderna è così imprecisa, in QUALSIASI lingua (tranne Python?)
Adam

9
@Adam: penso che tu sia frainteso. Il formato binario (IEEE 754 binary64) utilizzato per memorizzare i float non può rappresentare 2.675esattamente: il più vicino che il computer può ottenere è 2.67499999999999982236431605997495353221893310546875. È abbastanza vicino, ma non è esattamente uguale a 2.675: è leggermente più vicino a 2.67che a 2.68. Quindi la roundfunzione fa la cosa giusta e la arrotonda al valore più vicino di 2 cifre dopo il punto, vale a dire 2.67. Questo non ha nulla a che fare con Python e tutto a che fare con il virgola mobile binario.
Mark Dickinson,

3
Non è "la cosa giusta" perché è stata data una costante del codice sorgente :), ma vedo il tuo punto.
Adam,

@Adam: mi sono imbattuto in questa stessa stranezza in JS prima, quindi non è specifico della lingua.
Igor,

5

Di recente ho avuto anche problemi con questo. Quindi, ho sviluppato un modulo Python 3 che ha 2 funzioni trueround () e trueround_precision () che affrontano questo problema e danno lo stesso comportamento di arrotondamento a cui erano abituati dalla scuola primaria (non l'arrotondamento del banchiere). Ecco il modulo. Basta salvare il codice e copiarlo o importarlo. Nota: il modulo trueround_precision può modificare il comportamento di arrotondamento in base alle esigenze in base alle indicazioni ROUND_CEILING, ROUND_DOWN, ROUND_FLOOR, ROUND_FALOR, ROUND_HALF_DOWN, ROUND_HALF_EVEN, ROUND_HALF_UP, ROUND_UP e ROUND_05UP per ulteriori informazioni. Per le funzioni di seguito, consultare la documentazione o utilizzare help (trueround) e help (trueround_precision) se copiato in un interprete per ulteriore documentazione.

#! /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 <signupnarnie@gmail.com>
    '''
    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 <signupnarnie@gmail.com>
    '''
    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)

Spero che questo ti aiuti,

Narnie


5

Python 3.x arrotonda 0,5 valori a un vicino che è pari

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

tuttavia, è possibile modificare l'arrotondamento decimale "indietro" per arrotondare sempre 0,5 in su, se necessario:

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

1

Comportamento di arrotondamento di Python 2 in Python 3.

Aggiunta di 1 al 15o decimale. Precisione fino a 15 cifre.

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

3
Potresti spiegare l'intuizione dietro questa formula?
Hadi,

2
Da quello che ho capito, le frazioni che non possono essere rappresentate con precisione avranno fino a 15 9, quindi l'imprecisione. Ad esempio, lo 2.675è 2.67499999999999982236431605997495353221893310546875. L'aggiunta di 1e-15 lo inclinerà su 2.675 e lo arrotonderà correttamente. se la frazione è già sopra la costante di codice, l'aggiunta di 1e-15 non cambierà nulla all'arrotondamento.
Benoit Dufresne,

1

Alcuni casi:

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

Per la correzione:

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

Se si desidera un numero maggiore di decimali, ad esempio 4, è necessario aggiungere (+ 0,0000001).

Lavora per me.


Questa è stata l'unica soluzione che ha funzionato per me, grazie per la pubblicazione. Tutti sembrano intenzionati a arrotondare 0,5 su / giù, quindi non sono riuscito a gestire i problemi di arrotondamento multi decimale.
Gayathri,

-1

Riproduzione campione:

['{} => {}'.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

Stati:

Restituisce il numero arrotondato per specificare la precisione dopo il punto decimale. Se ndigits viene omesso o è None, restituisce il numero intero più vicino al suo input.

Per i tipi predefiniti che supportano round (), i valori sono arrotondati al multiplo più vicino di 10 alla potenza meno ndigits; se due multipli sono ugualmente vicini, l'arrotondamento viene eseguito verso la scelta pari (quindi, per esempio, sia il giro (0,5) che il giro (-0,5) sono 0, e il giro (1,5) è 2). Qualsiasi valore intero è valido per ndigits (positivo, zero o negativo). Il valore restituito è un numero intero se ndigits è omesso o Nessuno. In caso contrario, il valore restituito ha lo stesso tipo di numero.

Per un numero oggetto Python generale, arrotondare i delegati al numero. il giro .

Nota Il comportamento di round () per i float può essere sorprendente: ad esempio, round (2.675, 2) fornisce 2,67 invece del previsto 2,68. Questo non è un bug: è il risultato del fatto che la maggior parte delle frazioni decimali non può essere rappresentata esattamente come un float. Vedi Aritmetica in virgola mobile: problemi e limitazioni per ulteriori informazioni.

Data questa intuizione, puoi usare un po 'di matematica per risolverlo

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

ora puoi eseguire lo stesso test con my_round anziché 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']

-2

Il modo più semplice per arrotondare in Python 3.x come insegnato a scuola è usare una variabile ausiliaria:

n = 0.1 
round(2.5 + n)

E questi saranno i risultati delle serie da 2.0 a 3.0 (in 0.1 passi):

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

-2

Puoi controllare l'arrotondamento usando il modulo math.ceil:

import math
print(math.ceil(2.5))
> 3

Che restituirà sempre il numero senza la sua parte decimale, questo non è arrotondamento. ceil (2.5) = 2, ceil (2.99) = 2
krafter

1
in python3 +, Se l'argomento numero è un numero positivo o negativo, la funzione ceil restituisce il valore del soffitto.
Eds_k,

In [14]: math.ceil (2.99) Out [14]: 3
Eds_k

Sì, mi dispiace di aver sbagliato. Ceil () restituisce il valore del soffitto mentre floor () restituisce quello di cui stavo parlando. Tuttavia, secondo me questo non è proprio il comportamento di arrotondamento (entrambe queste funzioni)
krafter

-4

Prova questo codice:

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

Il risultato sarà:

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

Ooutput è possibile controllare qui: https://i.stack.imgur.com/QQUkS.png

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.