round () ne semble pas arrondir correctement

123

La documentation de la fonction round () indique que vous lui transmettez un nombre et que les positions après la virgule sont arrondies. Ainsi, il devrait faire ceci:

n = 5.59
round(n, 1) # 5.6

Mais, en réalité, la bonne vieille bizarrerie en virgule flottante s'infiltre et vous obtenez:

5.5999999999999996

Pour les besoins de l'interface utilisateur, je dois afficher 5.6. J'ai fouillé sur Internet et j'ai trouvé de la documentation indiquant que cela dépend de mon implémentation de Python. Malheureusement, cela se produit à la fois sur ma machine de développement Windows et sur chaque serveur Linux que j'ai essayé. Voir aussi ici .

À moins de créer ma propre bibliothèque ronde, y a-t-il un moyen de contourner cela?

swilliams
la source
4
J'ai essayé cela avec python 2.7.11 round (5.59) et cela donne le résultat comme 5.6 dans les deux Windows et Linux x86 64 bits machine, Cython? (Le lien de documentation mentionné est changé maintenant je suppose)
Alex Punnen
2
Là où cela ne fonctionne pas correctement, c'est round(5.55, 1) = 5.5.
Dmitry

Réponses:

102

Je ne peux pas m'empêcher de savoir comment il est stocké, mais au moins le formatage fonctionne correctement:

'%.1f' % round(n, 1) # Gives you '5.6'
Jimmy
la source
11
J'ai essayé print '%.2f' % 655.665mais ça revient 655.66, ça devrait être655.67
Liza
1
@Kyrie voir stackoverflow.com/questions/9301690/… . L'imprécision en virgule flottante est à blâmer ici - "5,665 -> 5,67" mais "15,665 -> 15,66". Utilisez des décimales si vous avez besoin d'une précision exacte.
Jimmy
7
cela fonctionne après la recherche :) from decimal import Decimal, ROUND_HALF_UP, ROUND_HALF_DOWN# utiliser pour arrondir les nombres flottants Decimal(str(655.665)).quantize(Decimal('1.11'), rounding=ROUND_HALF_UP)# Problèmes et limitations en virgule flottante
Liza
102

Le formatage fonctionne correctement même sans avoir à arrondir:

"%.1f" % n
Vinko Vrsalovic
la source
18
Selon la documentation , ce style de formatage de chaîne finira par disparaître. Le nouveau format de style serait"{:.1f}".format(n)
whereswalden
2
'%.5f' % 0.9886250.98862
N'arrondit
@schlamar: C'est aussi le comportement de round (): round (0.988625,5) donne également 0.98862. round (0.988626,5) ainsi que "% .5f"% 0.988626 donnent 0.98863
Vinko Vrsalovic
Malheureusement, "% .2f"% 2.675 renverra 2,67 - ce qui pourrait être une réponse inattendue pour ceux qui utilisent cette méthode et attendent 2,68
Dion
30

Si vous utilisez le module Decimal, vous pouvez effectuer une approximation sans utiliser la fonction «round». Voici ce que j'ai utilisé pour arrondir en particulier lors de l'écriture d'applications monétaires:

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

Cela renverra un nombre décimal qui est 16,20.

Robert Griesmeyer
la source
4
C'est la réponse canonique - là où la précision compte, de toute façon, ce qui est à peu près partout. Bien sûr: c'est un peu bavard . Mais jetez cette ventouse dans une fonction d'aide et vous êtes prêt à formater et à partir.
Cecil Curry
2
rounding='ROUND_UP'
LMc
Si vous obtenez cette erreur , NameError: global name 'ROUND_UP' is not definedvous devez importer votre fonction d' arrondi: from decimal import Decimal, ROUND_UP. Autres fonctions d'arrondi
Stephen Blair
Votre exemple semble toujours dangereux: vous vous fiez à l'arrondi fourni par str ().
YvesgereY
21

round(5.59, 1)fonctionne bien. Le problème est que 5.6 ne peut pas être représenté exactement en virgule flottante binaire.

>>> 5.6
5.5999999999999996
>>> 

Comme le dit Vinko, vous pouvez utiliser le formatage de chaîne pour arrondir l'affichage.

Python a un module pour l'arithmétique décimale si vous en avez besoin.

Will Harris
la source
1
Ce n'est plus un problème avec Python 2.7 ou Python 3.5
vy32
15

Vous obtenez «5,6» si vous le faites str(round(n, 1))au lieu de simplement round(n, 1).

Tomi Kyöstilä
la source
10

Vous pouvez changer le type de données en un entier:

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

Et puis affichez le nombre en insérant le séparateur décimal des paramètres régionaux.

Cependant, la réponse de Jimmy est meilleure.

Frank Krueger
la source
5

Les calculs en virgule flottante sont vulnérables aux imprécisions de précision légères mais ennuyeuses. Si vous pouvez travailler avec des nombres entiers ou des virgules fixes, vous aurez une précision garantie.

Spoulson
la source
5

Jetez un œil au module Decimal

Decimal «est basé sur un modèle à virgule flottante qui a été conçu avec les gens à l'esprit, et a nécessairement un principe directeur primordial - les ordinateurs doivent fournir une arithmétique qui fonctionne de la même manière que l'arithmétique que les gens apprennent à l'école.» - extrait de la spécification arithmétique décimale.

et

Les nombres décimaux peuvent être représentés exactement. En revanche, les nombres comme 1.1 et 2.2 n'ont pas de représentation exacte en virgule flottante binaire. Les utilisateurs finaux ne s'attendent généralement pas à ce que 1,1 + 2,2 s'affiche comme 3,3000000000000003 comme il le fait avec une virgule flottante binaire.

Decimal fournit le type d'opérations qui facilitent l'écriture d'applications qui nécessitent des opérations en virgule flottante et qui doivent également présenter ces résultats dans un format lisible par l'homme, par exemple la comptabilité.

Jesse Dhillon
la source
4

printf la ventouse.

print '%.1f' % 5.59  # returns 5.6
Jason Navarrete
la source
4

C'est un gros problème en effet. Essayez ce code:

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

Il affiche 4,85. Ensuite, vous faites:

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

et cela montre 4,8. Calculez-vous à la main, la réponse exacte est 4,85, mais si vous essayez:

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

vous pouvez voir la vérité: le point flottant est stocké comme la somme finie la plus proche de fractions dont les dénominateurs sont des puissances de deux.

Alexandre Lymberopoulos
la source
3

Vous pouvez utiliser l'opérateur de format de chaîne %, similaire à sprintf.

mystring = "%.2f" % 5.5999
nlucaroni
la source
2

Fonctionne parfaitement

format(5.59, '.1f') # to display
float(format(5.59, '.1f')) #to round
Станислав Повышев
la source
2

Je fais:

int(round( x , 0))

Dans ce cas, nous arrondissons d'abord correctement au niveau de l'unité, puis nous convertissons en entier pour éviter d'imprimer un flottant.

alors

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

Je pense que cette réponse fonctionne mieux que le formatage de la chaîne, et cela me rend également plus sensé d'utiliser la fonction round.

Gildas
la source
2

J'éviterais de me fier du round()tout dans ce cas. Considérer

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

sortira

61.3
1.29

qui n'est pas une sortie souhaitée si vous avez besoin d'un arrondi solide à l'entier le plus proche. Pour contourner ce comportement, allez avec math.ceil()(ou math.floor()si vous voulez arrondir):

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)

les sorties

61.3
1.3

J'espère que cela pourra aider.

Avoine Tali
la source
1

Code:

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'

Production:

5.6
5.7
Dondon Jie
la source
0

Voici où je vois l'échec du cycle. Et si vous vouliez arrondir ces 2 nombres à une décimale? 23,45 23,55 J'ai appris qu'en arrondissant ces chiffres, vous devriez obtenir: 23,4 23,6 la «règle» étant que vous devez arrondir si le nombre précédent est impair, et non arrondir si le nombre précédent est pair. La fonction round en python tronque simplement le 5.

Gregory Pittman
la source
1
Ce dont vous parlez, c'est «l'arrondissement des banquiers» , l'une des nombreuses façons différentes d'effectuer l'arrondi.
Simon MᶜKenzie
0

Le problème est seulement lorsque le dernier chiffre est 5. Par exemple. 0,045 est stocké en interne sous la forme 0,044999999999999 ... Vous pouvez simplement incrémenter le dernier chiffre à 6 et arrondir. Cela vous donnera les résultats souhaités.

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
Syed est Saqlain
la source
0

Une autre option potentielle est:

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)
conmak
la source
-4

Qu'en est-il de:

round(n,1)+epsilon
ima
la source
Cela ne fonctionnerait que si l'arrondi était systématiquement différent du nombre rond par epsilon. Si epsilon = .000001alors round(1.0/5.0, 1) + epsilonprendrait la représentation précise 0,2 et la rendrait 0,00001. Des problèmes tout aussi graves se produiraient si l'epsilon était à l'intérieur de la fonction round.
Michael Scott Cuthbert