web-dev-qa-db-fra.com

Comportement d'arrondissement de Python 3.x

Je lisais simplement What's New In Python 3.0 et il est écrit:

La stratégie d'arrondi de la fonction round () et le type de retour ont changé. Les cas à mi-chemin exacts sont maintenant arrondis au résultat pair le plus proche au lieu d'être à l'abri de zéro. (Par exemple, le tour (2.5) renvoie maintenant 2 plutôt que 3.)

et la documentation pour round :

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 n; si deux multiples sont égaux, l'arrondi est fait vers le même choix

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 l'aurais prévu. Cependant, maintenant sous 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 au risque de 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 arrondir: -/Je suis sûr qu'au moment où la v3 a été introduite, il devait y avoir une discussion à ce sujet, mais je n'ai pas trouvé de bonne raison de ma recherche.

  1. Quelqu'un a-t-il compris pourquoi cela a été changé pour cela?
  2. Existe-t-il d'autres langages de programmation traditionnels (par exemple, C, C++, Java, Perl, ..) qui effectuent ce type d'arrondi (pour moi incohérent)?

Qu'est-ce que j'oublie ici?

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

128
Levon

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

La technique simple "toujours arrondi à 0,5" produit un léger biais en faveur du nombre le plus élevé. Avec un grand nombre de calculs, cela peut être important. L'approche Python 3.0 élimine ce problème.

Il y a plus d'une méthode d'arrondissement couramment utilisée. IEEE 754, la norme internationale pour les calculs 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, un des premiers à adopter cette méthode d’arrondi. La commande round dans AppleScript offre en fait plusieurs options, mais la valeur par défaut est identique à celle utilisée dans IEEE 754. Apparemment, l’ingénieur qui a implémenté la commande round en a marre de toutes les demandes visant à le "faire fonctionner de la même manière" appris à l'école "qu'il a mis en œuvre que: round 2.5 rounding as taught in school est une commande AppleScript valide. :-)

119
kindall

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

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 floats peut être surprenant: par exemple, tour (2,675, 2) donne 2,67 au lieu des 2,68 attendus. Ceci n'est pas un bug: c’est le résultat du fait que la plupart des fractions décimales ne peuvent pas être représenté exactement comme un flotteur. Voir Arithmétique en virgule flottante: problèmes et Limitations pour plus d'informations.

Alors ne soyez pas surpris d'obtenir les résultats suivants en 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)
7
skif1979

J'ai récemment eu des problèmes avec cela aussi. Par conséquent, j'ai développé un module python 3 qui a 2 fonctions, trueround () et trueround_precision (), qui répondent à cela et donnent le même comportement d'arrondi que celui utilisé depuis l'école primaire (pas l'arrondi de banquier). Voici le module. Sauvegardez simplement le code et copiez-le ou importez-le. Remarque: le module trueround_precision peut modifier le comportement d'arrondi en fonction des besoins en fonction des paramètres ROUND_CEILING, ROUND_DOWN, ROUND_FLOOR, ROUND_HALF_DOWN, ROUND_HALF_EVEN (voir cette documentation) Pour les fonctions ci-dessous, consultez la documentation ou utilisez help (trueround) et help (trueround_precision) si elles sont copiées 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

4
narnie

Comportement d'arrondi de Python 2 dans Python 3.

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

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

Python 3.x arrondit les valeurs .5 à un voisin qui est même

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

toutefois, vous pouvez modifier l’arrondi décimal "retour" pour toujours arrondir .5, 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
1
kares

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 le correctif:

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 devriez ajouter (+ 0.0000001).

Travaille pour moi.

0
Virako

Exemple de reproduction:

['{} => {}'.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 à la précision de ndigits après le point décimal. Si ndigits est omis ou est None, il renvoie l'entier le plus proche de son contribution.

Pour les types intégrés prenant en charge round (), les valeurs sont arrondies au multiple le plus proche de 10 de la puissance moins ndigits; si deux multiples sont également proches, l’arrondi est fait vers le choix pair (ainsi, par exemple, les arrondis (0.5) et arrondis (-0.5) valent 0 et arrondis (1.5) vaut 2) . Toute valeur entière est valide pour ndigits (positif, nul 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 à nombre .round.

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

Compte tenu de cette idée, vous pouvez utiliser quelques 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']
0
Fallenreaper

L'opérateur round arrondit la valeur à la valeur entière la plus proche. 

Par exemple

Si la valeur est supérieure à 0,5, elle sera arrondie à 1

print(round(211.5554, 2)) // output is 211.56

Si la valeur est inférieure à 0,5, elle sera arrondie à 0

print(round(211.5544, 2)) // output is 211.55

Modifier :

L'opérateur // mentionné précédemment n'est pas utilisé pour arrondir, il sert par exemple à gérer la sortie de la division.

 print(10//3) // output is 3 instead of 3.3333333333333335
0
Asad Manzoor