98 votes

Arrondir un flottant à x décimales ?

Existe-t-il un moyen d'arrondir un flottant en python à x décimales ? Par exemple :

>>> x = roundfloat(66.66666666666, 4)
66.6667
>>> x = roundfloat(1.29578293, 6)
1.295783

J'ai trouvé des moyens de les couper/tronquer (66.666666666 --> 66.6666), mais pas de les arrondir (66.666666666 --> 66.6667).

152voto

Mark Dickinson Points 6780

Je me sens obligé d'apporter un contrepoint à la réponse d'Ashwini Chaudhary. Malgré les apparences, la forme à deux arguments de la round fonction n'est pas arrondir un flottant Python à un nombre donné de décimales, et ce n'est souvent pas la solution que vous souhaitez, même lorsque vous pensez que c'est le cas. Laissez-moi vous expliquer...

La possibilité d'arrondir un flottant (Python) à un certain nombre de décimales est quelque chose de fréquemment demandé, mais qui s'avère être rarement ce dont on a réellement besoin. La réponse est d'une simplicité déconcertante round(x, number_of_places) est une sorte de nuisance attrayante : elle regarde comme s'il faisait ce que vous voulez, mais grâce au fait que les flottants Python sont stockés en interne en binaire, il fait quelque chose de plus subtil. Prenons l'exemple suivant :

>>> round(52.15, 1)
52.1

Avec une compréhension naïve de ce que round mais cela ne semble pas correct : il faudrait sûrement arrondir en haut a 52.2 plutôt que en bas a 52.1 ? Pour comprendre pourquoi on ne peut pas se fier à de tels comportements, il faut savoir que si cette opération ressemble à une simple opération de décimal à décimal, elle est loin d'être simple.

Donc, voici ce qui est vraiment dans l'exemple ci-dessus. ( une profonde respiration ) Nous affichons un décimal représentation du plus proche binaire nombre à virgule flottante au plus proche n -digits-after-the-point décimal à un numéro binaire approximation en virgule flottante d'un littéral numérique écrit en décimal . Ainsi, pour passer du littéral numérique d'origine à la sortie affichée, la machinerie sous-jacente a fait quatre des conversions séparées entre les formats binaire et décimal, deux dans chaque sens. En décomposant (et avec les avertissements habituels concernant l'hypothèse du format binaire IEEE 754, l'arrondi à la paire, et les règles IEEE 754) :

  1. D'abord le littéral numérique 52.15 est analysé et converti en un flottant Python. Le nombre réel stocké est 7339460017730355 * 2**-47 ou 52.14999999999999857891452847979962825775146484375 .

  2. En interne, comme première étape de la round Python calcule la chaîne décimale à un chiffre après la virgule la plus proche du nombre stocké. Puisque ce nombre stocké est un peu en dessous de la valeur originale de 52.15 on finit par arrondir à la baisse et obtenir une chaîne de caractères 52.1 . Cela explique pourquoi nous avons 52.1 comme sortie finale au lieu de 52.2 .

  3. Ensuite, dans la deuxième étape de la round Python retransforme cette chaîne en flottant, en obtenant le nombre binaire à virgule flottante le plus proche de 52.1 qui est maintenant 7332423143312589 * 2**-47 ou 52.10000000000000142108547152020037174224853515625 .

  4. Enfin, dans le cadre de la boucle de lecture-évaluation-impression (REPL) de Python, la valeur en virgule flottante est affichée (en décimal). Cela implique de reconvertir la valeur binaire en une chaîne décimale, d'obtenir 52.1 comme résultat final.

Dans Python 2.7 et plus, nous avons l'agréable situation que les deux conversions des étapes 3 et 4 s'annulent. Cela est dû au choix par Python de l'option repr qui produit la valeur décimale la plus courte garantissant un arrondi correct à la valeur flottante réelle. Une conséquence de ce choix est que si vous commencez avec une valeur décimale (ni trop grande, ni trop petite) comportant 15 chiffres significatifs ou moins, la valeur flottante correspondante sera affichée avec ces mêmes chiffres :

>>> x = 15.34509809234
>>> x
15.34509809234

Malheureusement, cela renforce l'illusion que Python stocke les valeurs en décimal. Ce n'est pas le cas dans Python 2.6 ! Voici l'exemple original exécuté en Python 2.6 :

>>> round(52.15, 1)
52.200000000000003

Non seulement nous tournons dans la direction opposée, en obtenant 52.2 au lieu de 52.1 mais la valeur affichée ne s'imprime même pas en tant que 52.2 ! Ce comportement a donné lieu à de nombreux rapports sur le système de suivi des bogues de Python, du type "round is broken !". Mais ce n'est pas le cas round qui est cassé, ce sont les attentes des utilisateurs. (Ok, ok, round es un petit un peu cassé dans Python 2.6, dans la mesure où il n'utilise pas d'arrondis corrects).

Version courte : si vous utilisez un tour à deux arguments, et que vous attendez un comportement prévisible de la part d'une binaire approximation d'un décimal tour d'un binaire approximation d'un décimal à mi-chemin, vous cherchez les ennuis.

Alors assez avec l'argument "le tour à deux arguments est mauvais". Qu'est-ce que devrait que vous utilisez à la place ? Il y a plusieurs possibilités, en fonction de ce que vous essayez de faire.

  • Si vous arrondissez à des fins d'affichage, vous ne voulez pas du tout d'un résultat à virgule flottante, mais d'une chaîne de caractères. Dans ce cas, la solution consiste à utiliser le formatage des chaînes de caractères :

    >>> format(66.66666666666, '.4f')
    '66.6667'
    >>> format(1.29578293, '.6f')
    '1.295783'

    Même dans ce cas, il faut être conscient de la représentation binaire interne pour ne pas être surpris par le comportement des demi-cas décimaux apparents.

    >>> format(52.15, '.1f')
    '52.1'
  • Si vous travaillez dans un contexte où il est important de savoir dans quel sens les demi-cadres décimaux sont arrondis (par exemple, dans certains contextes financiers), vous voudrez peut-être représenter vos nombres à l'aide du symbole Decimal type. En effectuant un arrondi décimal sur le Decimal a beaucoup plus de sens que sur un type binaire (de même, l'arrondi à un nombre fixe de positions binaires est parfaitement logique sur un type binaire). De plus, le decimal vous permet de mieux contrôler le mode d'arrondi. En Python 3, round fait directement le travail. En Python 2, vous avez besoin de l'option quantize méthode.

    >>> Decimal('66.66666666666').quantize(Decimal('1e-4'))
    Decimal('66.6667')
    >>> Decimal('1.29578293').quantize(Decimal('1e-6'))
    Decimal('1.295783')
  • Dans de rares cas, la version à deux arguments de l'option round vraiment es ce que vous voulez : peut-être que vous regroupez des flottants dans des bacs de taille 0.01 et vous ne vous souciez pas particulièrement de l'issue des affaires frontalières. Cependant, ces cas sont rares, et il est difficile de justifier l'existence de la version à deux arguments de l'option round builtin basé sur ces seuls cas.

107voto

Ashwini Chaudhary Points 94431

Utilisez la fonction intégrée round() :

In [23]: round(66.66666666666,4)
Out[23]: 66.6667

In [24]: round(1.29578293,6)
Out[24]: 1.295783

aide sur round() :

round(nombre[, ndigits]) -> nombre à virgule flottante

Arrondir un nombre à une précision donnée en chiffres décimaux (par défaut 0) chiffres). Cela renvoie toujours un nombre à virgule flottante. La précision peut être négative.

0voto

NMC Points 431

La réponse de Mark Dickinson, bien que complète, ne fonctionnait pas avec le cas float(52.15). Après quelques tests, voici la solution que j'utilise :

import decimal

def value_to_decimal(value, decimal_places):
    decimal.getcontext().rounding = decimal.ROUND_HALF_UP  # define rounding method
    return decimal.Decimal(str(float(value))).quantize(decimal.Decimal('1e-{}'.format(decimal_places)))

(La conversion de la "valeur" en flottant puis en chaîne de caractères est très importante, de sorte que la "valeur" peut être de type flottant, décimal, entier ou chaîne de caractères).

J'espère que cela vous aidera.

0voto

Canute S Points 194

Arrondi par défaut dans python et numpy :

In: [round(i) for i in np.arange(10) + .5]
Out: [0, 2, 2, 4, 4, 6, 6, 8, 8, 10]

Je l'ai utilisé pour que l'arrondi des nombres entiers soit appliqué à une série pandas :

import decimal

et utilisez cette ligne pour définir l'arrondi à la "moitié supérieure", c'est-à-dire l'arrondi enseigné à l'école : decimal.getcontext().rounding = decimal.ROUND_HALF_UP

Enfin, j'ai créé cette fonction pour l'appliquer à un objet série de Pandas.

def roundint(value):
    return value.apply(lambda x: int(decimal.Decimal(x).to_integral_value()))

Donc maintenant vous pouvez faire roundint(df.columnname)

Et pour les chiffres :

In: [int(decimal.Decimal(i).to_integral_value()) for i in np.arange(10) + .5]
Out: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Crédit : kares

0voto

jedema Points 4416

J'ai codé une fonction (utilisée dans le projet Django pour DecimalField) mais elle peut être utilisée dans un projet Python :

Ce code :

  • Gérer nombres entiers chiffres pour éviter un nombre trop élevé
  • Gérer décimales chiffres pour éviter un nombre trop faible
  • Gérer signé et non signé numéros

Code avec tests :

def convert_decimal_to_right(value, max_digits, decimal_places, signed=True):

    integer_digits = max_digits - decimal_places
    max_value = float((10**integer_digits)-float(float(1)/float((10**decimal_places))))

    if signed:
        min_value = max_value*-1
    else:
        min_value = 0

    if value > max_value:
        value = max_value

    if value < min_value:
        value = min_value

    return round(value, decimal_places)

value = 12.12345
nb = convert_decimal_to_right(value, 4, 2)
# nb : 12.12

value = 12.126
nb = convert_decimal_to_right(value, 4, 2)
# nb : 12.13

value = 1234.123
nb = convert_decimal_to_right(value, 4, 2)
# nb : 99.99

value = -1234.123
nb = convert_decimal_to_right(value, 4, 2)
# nb : -99.99

value = -1234.123
nb = convert_decimal_to_right(value, 4, 2, signed = False)
# nb : 0

value = 12.123
nb = convert_decimal_to_right(value, 8, 4)
# nb : 12.123

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X