Comportement d'arrondi Python 3.x

176

Je lisais juste les nouveautés de Python 3.0 et il dit:

La stratégie d'arrondi de la fonction round () et le type de retour ont changé. Les observations à mi-chemin exactes sont désormais arrondies au résultat pair le plus proche au lieu de s'éloigner de zéro. (Par exemple, round (2.5) renvoie désormais 2 au lieu de 3.)

et la documentation pour le tour :

Pour les types intégrés prenant en charge round (), les valeurs sont arrondies au multiple le plus proche de 10 à la puissance moins n; si deux multiples sont également proches, l'arrondi est effectué vers le choix pair

Donc, sous v2.7.3 :

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

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

comme je m'y attendais. Cependant, maintenant sous la v3.2.3 :

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

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

Cela semble contre-intuitif et contraire à ce que je comprends de l'arrondi (et ne peut que faire trébucher les gens). L'anglais n'est pas ma langue maternelle, mais jusqu'à ce que je lise ceci, je pensais savoir ce que signifiait l'arrondi: - / Je suis sûr qu'au moment où la v3 a été introduite, il doit y avoir eu une discussion à ce sujet, mais je n'ai pas pu trouver une bonne raison dans ma recherche.

  1. Quelqu'un a-t-il une idée de pourquoi cela a été changé en cela?
  2. Y a-t-il d'autres langages de programmation traditionnels (par exemple, C, C ++, Java, Perl, ..) qui font ce genre d'arrondi (pour moi incohérent)?

Qu'est-ce que j'oublie ici?

MISE À JOUR: Le commentaire de @ Li-aungYip concernant "l'arrondi du banquier" m'a donné le bon terme de recherche / mots-clés à rechercher et j'ai trouvé cette question SO: Pourquoi .NET utilise-t-il l'arrondi du banquier par défaut? , donc je vais lire cela attentivement.

Levon
la source
28
Je n'ai pas le temps de chercher cela, mais je crois que c'est ce qu'on appelle «l'arrondissement des banquiers». Je pense que c'est courant dans le secteur financier.
Li-aung Yip
2
@sberry eh bien, oui, son comportement est conforme à sa propre description. Donc, s'il disait que "arrondi" double sa valeur et qu'il le faisait, il serait également cohérent :) .. mais cela semble contraire à ce que signifie généralement l'arrondi . Je cherche donc une meilleure compréhension.
Levon
1
@ Li-aungYip Merci pour le lead re "Arrondi du banquier" .. Je vais chercher.
Levon
1
En relation: stackoverflow.com/questions/10093783/…
Sven Marnach
3
Juste une note: les arrondis des banquiers ne sont pas courants uniquement dans la finance. C'est ainsi qu'on m'a appris à
rentrer

Réponses:

160

La méthode de Python 3.0 est considérée comme la méthode d'arrondi standard de nos jours, bien que certaines implémentations de langage ne soient pas encore sur le bus.

La technique simple "toujours arrondir 0,5 vers le haut" entraîne un léger biais vers le nombre le plus élevé. Avec un grand nombre de calculs, cela peut être significatif. L'approche Python 3.0 élimine ce problème.

Il existe plus d'une méthode d'arrondi couramment utilisée. IEEE 754, la norme internationale pour les mathématiques en virgule flottante, définit cinq méthodes d'arrondi différentes (celle utilisée par Python 3.0 est la méthode par défaut). Et il y en a d'autres.

Ce comportement n'est pas aussi connu qu'il devrait l'être. AppleScript a été, si je me souviens bien, l'un des premiers à adopter cette méthode d'arrondi. La roundcommande dans AppleScript offre en fait plusieurs options, mais round-vers-even est la valeur par défaut, comme c'est le cas dans IEEE 754. Apparemment, l'ingénieur qui a implémenté la roundcommande en a eu tellement marre de toutes les demandes pour "le faire fonctionner comme je l'ai appris dans école "qu'il a implémenté juste que: round 2.5 rounding as taught in schoolest une commande AppleScript valide. :-)

kindall
la source
4
Je n'étais pas au courant de cette "méthode d'arrondi standard par défaut à peu près universellement ces jours-ci", sauriez-vous (ou quelqu'un d'autre) si C / C ++ / Java / Perl ou tout autre langage "main-stream" implémente l'arrondi de la même manière?
Levon
3
Ruby le fait. Les langages .NET de Microsoft le font. Java ne semble pas le faire. Je ne peux pas le retrouver pour toutes les langues possibles, mais je suppose que c'est plus courant dans des langues assez récentes. J'imagine que C et C ++ sont assez vieux pour ne pas le faire.
kindall
5
ruby revient 3pour2.5.round
jfs
14
J'ai ajouté un peu sur la gestion de cela par AppleScript parce que j'aime la façon sarcastique dont le "vieux" comportement est implémenté.
kindall
2
@kindall Cette méthode est le mode d'arrondi par défaut de l'IEEE depuis 1985 (date à laquelle IEEE 754-1985 a été publié). C'est aussi le mode d'arrondi par défaut en C depuis au moins C89 (et donc aussi en C ++), cependant , depuis C99 (et C ++ 11 avec un support sporadique avant cela) une fonction "round ()" est disponible qui utilise liens arrondis à partir de zéro à la place. L'arrondi en virgule flottante interne et la famille de fonctions rint () obéissent toujours au paramètre du mode d'arrondi, qui par défaut arrondit les liens à pair.
Wlerin
41

Vous pouvez contrôler l'arrondi obtenu dans Py3000 à l'aide du module Décimal :

>>> 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')
dawg
la source
Merci .. Je n'étais pas familier avec ce module. Une idée de la façon dont j'obtiendrais le comportement de Python v 2.x? Les exemples que vous montrez ne semblent pas le faire. Juste curieux de savoir si cela serait possible.
Levon
1
@Levon: La constante ROUND_HALF_UPest la même que l'ancien comportement de Python 2.X.
dawg
2
Vous pouvez également définir un contexte pour le module Decimal qui le fait implicitement pour vous. Voir la setcontext()fonction.
kindall
C'est exactement ce que je recherchais aujourd'hui. Fonctionnement comme prévu dans Python 3.4.3. Il convient également de noter que vous pouvez contrôler combien il arrondit en changeant quantize(decimal.Decimal('1')en quantize(decimal.Decimal('0.00')si vous voulez arrondir aux 100 plus proches, par exemple pour de l'argent.
Igor
Cette solution fonctionne comme un remplacement round(number, ndigits)aussi longtemps que cela ndigitsest positif, mais vous ne pouvez malheureusement pas l'utiliser pour remplacer quelque chose comme round(5, -1).
Pekka Klärck
15

Juste pour ajouter ici une note importante de la documentation:

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

Remarque

Le comportement de round () pour les flottants peut être surprenant: par exemple, round (2.675, 2) donne 2,67 au lieu des 2,68 attendus. Ce n'est pas un bogue: c'est le résultat du fait que la plupart des fractions décimales ne peuvent pas être représentées exactement comme un flottant. Voir Arithmétique à virgule flottante: problèmes et limitations pour plus d'informations.

Ne soyez donc pas surpris d'obtenir les résultats suivants dans 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)
skif1979
la source
J'ai vu ça. Et ma première réaction: qui utilise un processeur 16 bits incapable de représenter toutes les permutations de "2.67x"? Dire que les fractions ne peuvent pas être exprimées en float semble être un bouc émissaire ici: aucun processeur moderne n'est aussi inexact, dans N'IMPORTE QUELLE langue (sauf Python?)
Adam
9
@Adam: Je pense que vous vous trompez. Le format binaire (IEEE 754 binary64) utilisé pour stocker les flottants ne peut pas représenter 2.675exactement: le plus proche que l'ordinateur peut obtenir est 2.67499999999999982236431605997495353221893310546875. C'est assez proche, mais ce n'est pas exactement égal à 2.675: c'est très légèrement plus proche de 2.67que de 2.68. Ainsi, la roundfonction fait la bonne chose et l'arrondit à la valeur la plus proche à 2 chiffres après le point, à savoir 2.67. Cela n'a rien à voir avec Python, et tout à voir avec la virgule flottante binaire.
Mark Dickinson
3
Ce n'est pas "la bonne chose" parce qu'on lui a donné une constante de code source :), mais je vois votre point.
Adam
@Adam: J'ai déjà rencontré cette même bizarrerie dans JS, donc ce n'est pas spécifique à la langue.
Igor
5

J'ai récemment eu des problèmes avec ça. Par conséquent, j'ai développé un module python 3 qui a 2 fonctions trueround () et trueround_precision () qui abordent ce problème et donnent le même comportement d'arrondi que celui utilisé à partir de l'école primaire (pas l'arrondi du banquier). Voici le module. Enregistrez simplement le code et copiez-le ou importez-le. Remarque: le module trueround_precision peut changer le comportement d'arrondi en fonction des besoins en fonction des indicateurs ROUND_CEILING, ROUND_DOWN, ROUND_FLOOR, ROUND_HALF_DOWN, ROUND_HALF_EVEN, ROUND_HALF_UP, ROUND_UP et ROUND_05UP dans la documentation du module décimal (voir info sur les modules). Pour les fonctions ci-dessous, consultez les docstrings ou utilisez help (trueround) et help (trueround_precision) si copié dans un interpréteur pour plus de documentation.

#! /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 <[email protected]>
    '''
    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 <[email protected]>
    '''
    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)

J'espère que cela t'aides,

Narnie

Narnie
la source
5

Python 3.x arrondit les valeurs .5 à un voisin qui est pair

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

cependant, on peut changer l'arrondi décimal "en arrière" pour toujours arrondir 0,5 vers le haut, si nécessaire:

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
kares
la source
1

Comportement d'arrondi de Python 2 en python 3.

Ajout de 1 à la 15e décimale. Précision jusqu'à 15 chiffres.

round2=lambda x,y=None: round(x+1e-15,y)
SmartManoj
la source
3
Pourriez-vous expliquer l'intuition derrière cette formule?
Hadi
2
D'après ce que je comprends, les fractions qui ne peuvent pas être représentées avec précision auront jusqu'à 15 9, puis l'imprécision. Par exemple, 2.675est 2.67499999999999982236431605997495353221893310546875. L'ajout de 1e-15 le fera basculer au-dessus de 2,675 et l'arrondira correctement. si la fraction est déjà au-dessus de la constante de code, l'ajout de 1e-15 ne changera rien à l'arrondi.
Benoit Dufresne
1

Certains cas:

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

Pour réparer:

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 vous voulez plus de décimales, par exemple 4, vous devez ajouter (+ 0.0000001).

Travaille pour moi.

Virako
la source
C'était la seule solution qui a fonctionné pour moi, merci d'avoir posté. Tout le monde semble vouloir arrondir à 0,5, donc je ne pouvais pas gérer les problèmes d'arrondi à plusieurs décimales.
Gayathri
-1

Reproduction d'échantillon:

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

États:

Renvoie le nombre arrondi à ndigits avec précision après la virgule décimale. Si ndigits est omis ou vaut None, il renvoie l'entier le plus proche à son entrée.

Pour les types intégrés prenant en charge round (), les valeurs sont arrondies au multiple de 10 le plus proche de la puissance moins ndigits; si deux multiples sont également proches, l'arrondi est effectué vers le choix pair (ainsi, par exemple, arrondi (0,5) et arrondi (-0,5) valent 0 et arrondi (1,5) vaut 2). Toute valeur entière est valide pour ndigits (positif, zéro ou négatif). La valeur de retour est un entier si ndigits est omis ou None. Sinon, la valeur de retour a le même type que nombre.

Pour un numéro d'objet Python général, arrondissez les délégués au nombre. rond .

Remarque Le comportement de round () pour les flottants peut être surprenant: par exemple, round (2.675, 2) donne 2,67 au lieu des 2,68 attendus. Ce n'est pas un bogue: c'est le résultat du fait que la plupart des fractions décimales ne peuvent pas être représentées exactement comme un flottant. Voir Arithmétique à virgule flottante: problèmes et limitations pour plus d'informations.

Compte tenu de ces informations, vous pouvez utiliser des mathématiques pour le résoudre

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

maintenant vous pouvez exécuter le même test avec my_round au lieu 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']
Fallenreaper
la source
-2

Le moyen le plus simple d'arrondir en Python 3.x comme enseigné à l'école consiste à utiliser une variable auxiliaire:

n = 0.1 
round(2.5 + n)

Et ce seront les résultats de la série 2.0 à 3.0 (par pas de 0,1):

>>> 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
baermathies
la source
-2

Vous pouvez contrôler l'arrondi que vous utilisez à l'aide du module math.ceil:

import math
print(math.ceil(2.5))
> 3
Eds_k
la source
Cela renverra toujours le nombre sans sa partie décimale, ce n'est pas un arrondi. ceil (2,5) = 2, ceil (2,99) = 2
krafter le
1
en python3 +, si l'argument numérique est un nombre positif ou négatif, la fonction ceil renvoie la valeur plafond.
Eds_k
In [14]: math.ceil (2.99) Out [14]: 3
Eds_k
Oui, je suis désolé d'avoir eu tort. Ceil () renvoie la valeur plafond tandis que floor () renvoie celle dont je parlais. Mais encore, à mon avis, ce n'est pas tout à fait le comportement d'arrondi (ces deux fonctions)
krafter
-4

Essayez ce code:

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

Le résultat sera:

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

Vous pouvez vérifier la sortie ici: https://i.stack.imgur.com/QQUkS.png

Anil Kumar Sahoo
la source