web-dev-qa-db-fra.com

Tronquer des flotteurs en Python

Je veux supprimer les chiffres d'un nombre flottant pour avoir un nombre fixe de chiffres après le point, comme:

1.923328437452 -> 1.923

J'ai besoin de générer une chaîne vers une autre fonction, pas d'imprimer.

Aussi, je veux ignorer les chiffres perdus, pas les contourner.

88
Joan Venge

Tout d’abord, la fonction, pour ceux qui veulent juste du code copier-coller:

def truncate(f, n):
    '''Truncates/pads a float f to n decimal places without rounding'''
    s = '{}'.format(f)
    if 'e' in s or 'E' in s:
        return '{0:.{1}f}'.format(f, n)
    i, p, d = s.partition('.')
    return '.'.join([i, (d+'0'*n)[:n]])

Ceci est valable dans Python 2.7 et 3.1+. Pour les versions plus anciennes, il n'est pas possible d'obtenir le même effet "d'arrondi intelligent" (du moins, pas sans beaucoup de code compliqué), mais arrondir à 12 décimales avant la troncature fonctionnera la plupart du temps:

def truncate(f, n):
    '''Truncates/pads a float f to n decimal places without rounding'''
    s = '%.12f' % f
    i, p, d = s.partition('.')
    return '.'.join([i, (d+'0'*n)[:n]])

Explication

Le cœur de la méthode sous-jacente consiste à convertir la valeur en chaîne avec une précision absolue, puis à tout supprimer du nombre de caractères souhaité. La dernière étape est facile; cela peut être fait soit avec une manipulation de chaîne

i, p, d = s.partition('.')
'.'.join([i, (d+'0'*n)[:n]])

ou le module decimal

str(Decimal(s).quantize(Decimal((0, (1,), -n)), rounding=ROUND_DOWN))

La première étape, la conversion en chaîne, est assez difficile car il existe quelques paires de littéraux à virgule flottante (c’est-à-dire ce que vous écrivez dans le code source) qui produisent tous deux la même représentation binaire et doivent pourtant être tronqués différemment. Par exemple, considérons 0.3 et 0.29999999999999998. Si vous écrivez 0.3 dans un programme Python, le compilateur le code en utilisant le format à virgule flottante IEEE dans la séquence de bits (en supposant un flottant de 64 bits).

0011111111010011001100110011001100110011001100110011001100110011

C'est la valeur la plus proche de 0,3 qui peut être représentée avec précision comme un float IEEE. Mais si vous écrivez 0.29999999999999998 dans un programme Python, le compilateur le traduit en exactement la même valeur . Dans un cas, vous vouliez le tronquer (à un chiffre) sous la forme 0.3, tandis que dans l'autre cas, vous vouliez le tronquer sous la forme 0.2, mais Python ne peut que donner une réponse. C'est une limitation fondamentale de Python, voire de tout langage de programmation sans évaluation paresseuse. La fonction de troncature n'a accès qu'à la valeur binaire stockée dans la mémoire de l'ordinateur, pas à la chaîne que vous avez réellement saisie dans le code source.1

Si vous décodez la séquence de bits en un nombre décimal, en utilisant à nouveau le format à virgule flottante IEEE 64 bits, vous obtenez

0.2999999999999999888977697537484345957637...

donc une implémentation naïve aboutirait avec 0.2 même si ce n'est probablement pas ce que vous voulez. Pour plus d'informations sur l'erreur de représentation en virgule flottante, consultez le didacticiel Python =.

Il est très rare de travailler avec une valeur à virgule flottante qui est si proche d'un nombre arrondi tout en étant intentionnellement différente de ce nombre arrondi. Ainsi, lors de la troncature, il est probablement judicieux de choisir la représentation décimale "la plus belle" parmi toutes celles pouvant correspondre à la valeur en mémoire. Python 2.7 et supérieur (mais pas 3.0) inclut un algorithme sophistiqué pour faire exactement cela , auquel nous pouvons accéder via l'opération de formatage de chaîne par défaut.

'{}'.format(f)

Le seul inconvénient est que cela agit comme une spécification de format g, en ce sens qu'elle utilise une notation exponentielle (1.23e+4) si le nombre est suffisamment grand ou trop petit. La méthode doit donc intercepter ce cas et le traiter différemment. Il existe quelques cas où l’utilisation d’une spécification de format f pose un problème, tel que tenter de tronquer 3e-10 à 28 chiffres de précision (cela produit 0.0000000002999999999999999980), et je ne le suis pas. encore sûr comment mieux gérer ceux-ci.

Si vous travaillez avec floats qui sont très proches des nombres ronds mais qui ne leur correspondent pas intentionnellement (comme 0.29999999999999998 ou 99.959999999999994), cela produira des faux positifs, c'est-à-dire qu'il arrondira les nombres que vous ne voulez pas arrondir. Dans ce cas, la solution consiste à spécifier une précision fixe.

'{0:.{1}f}'.format(f, sys.float_info.Dig + n + 2)

Le nombre de chiffres de précision à utiliser ici n'a pas vraiment d'importance, il doit simplement être suffisamment grand pour garantir que tout arrondi effectué dans la conversion de chaîne ne "gonfle" pas la valeur à sa représentation décimale de Nice. Je pense que sys.float_info.Dig + n + 2 peut être suffisant dans tous les cas, mais sinon, il faudrait peut-être augmenter 2, et cela ne fait pas de mal de le faire.

Dans les versions antérieures de Python (jusqu'à la version 2.6 ou 3.0), le formatage des nombres en virgule flottante était beaucoup plus grossier et produisait régulièrement des éléments tels que

>>> 1.1
1.1000000000000001

Si tel est votre cas, si voulez utiliser des représentations décimales "sympas" pour la troncature, tout ce que vous pouvez faire (à ma connaissance) consiste à prendre un certain nombre de chiffres, inférieure à la précision complète représentable par un float, et arrondissez le nombre à autant de chiffres avant de le tronquer. Un choix typique est 12,

'%.12f' % f

mais vous pouvez ajuster cela en fonction des chiffres que vous utilisez.


1Eh bien ... j'ai menti. Techniquement, vous pouvez demander à Python de ré-analyser son propre code source et d'extraire la partie correspondant au premier argument transmis à la fonction de troncature. Si cet argument est un littéral à virgule flottante, vous pouvez simplement le couper d'un certain nombre de positions après le point décimal et le renvoyer. Cependant, cette stratégie ne fonctionne pas si l'argument est une variable, ce qui le rend assez inutile. Ce qui suit est présenté pour la valeur de divertissement seulement:

def trunc_introspect(f, n):
    '''Truncates/pads the float f to n decimal places by looking at the caller's source code'''
    current_frame = None
    caller_frame = None
    s = inspect.stack()
    try:
        current_frame = s[0]
        caller_frame = s[1]
        gen = tokenize.tokenize(io.BytesIO(caller_frame[4][caller_frame[5]].encode('utf-8')).readline)
        for token_type, token_string, _, _, _ in gen:
            if token_type == tokenize.NAME and token_string == current_frame[3]:
                next(gen) # left parenthesis
                token_type, token_string, _, _, _ = next(gen) # float literal
                if token_type == tokenize.NUMBER:
                    try:
                        cut_point = token_string.index('.') + n + 1
                    except ValueError: # no decimal in string
                        return token_string + '.' + '0' * n
                    else:
                        if len(token_string) < cut_point:
                            token_string += '0' * (cut_point - len(token_string))
                        return token_string[:cut_point]
                else:
                    raise ValueError('Unable to find floating-point literal (this probably means you called {} with a variable)'.format(current_frame[3]))
                break
    finally:
        del s, current_frame, caller_frame

Généraliser ceci pour gérer le cas où vous transmettez une variable semble être une cause perdue, car il vous faudrait remonter en arrière dans l'exécution du programme jusqu'à trouver le littéral à virgule flottante qui a donné sa valeur à la variable. S'il y en a même un. La plupart des variables seront initialisées à partir d'une entrée utilisateur ou d'expressions mathématiques, auquel cas la représentation binaire est tout ce qui existe.

104
David Z
round(1.923328437452, 3)

Voir la documentation de Python sur les types standard . Vous devrez faire défiler un peu pour accéder à la fonction round. Le deuxième nombre indique essentiellement le nombre de décimales à arrondir.

139
Teifion

Le résultat de round est un float, alors faites attention (l'exemple provient de Python 2.6):

>>> round(1.923328437452, 3)
1.923
>>> round(1.23456, 3)
1.2350000000000001

Vous serez mieux à l'aide d'une chaîne formatée:

>>> "%.3f" % 1.923328437452
'1.923'
>>> "%.3f" % 1.23456
'1.235'
30
Ferdinand Beyer
n = 1.923328437452
str(n)[:4]
18
john_dough

A mon invite Python 2.7:

>>> int(1.923328437452 * 1000)/1000.0 1.923

11
Bill
def trunc(num, digits):
   sp = str(num).split('.')
   return '.'.join([sp[0], sp[1][:digits]])

Cela devrait marcher. Il devrait vous donner la troncature que vous recherchez.

8
Matt

La vraie façon de le faire est de le faire

from decimal import *

with localcontext() as ctx:
    ctx.rounding = ROUND_DOWN
    print Decimal('1.923328437452').quantize(Decimal('0.001'))
8
Antony Hatchkins

Script python simple -

n = 1.923328437452
n = float(int(n * 1000))
n /=1000
7
markroxor

Tant de réponses à cette question sont tout à fait fausses. Ils arrondissent les flottants (plutôt que de les tronquer) ou ne fonctionnent pas dans tous les cas.

C'est le meilleur résultat de Google lorsque je recherche 'Python truncate float', un concept très simple qui mérite de meilleures réponses. Je conviens avec Hatchkins que l’utilisation du module decimal est la méthode pythonique utilisée. C’est pourquoi je donne ici une fonction qui répond correctement à la question et qui fonctionne comme prévu dans tous les cas.

En remarque, les valeurs fractionnaires ne peuvent généralement pas être représentées exactement par des variables binaires à virgule flottante (voir ici pour une discussion à ce sujet), raison pour laquelle ma fonction renvoie une chaîne. 

from decimal import Decimal, localcontext, ROUND_DOWN

def truncate(number, places):
    if not isinstance(places, int):
        raise ValueError("Decimal places must be an integer.")
    if places < 1:
        raise ValueError("Decimal places must be at least 1.")
    # If you want to truncate to 0 decimal places, just do int(number).

    with localcontext() as context:
        context.rounding = ROUND_DOWN
        exponent = Decimal(str(10 ** - places))
        return Decimal(str(number)).quantize(exponent).to_eng_string()
7
nullstellensatz

J'ai fait quelque chose comme ça:

from math import trunc


def truncate(number, decimals=0):
    if decimals < 0:
        raise ValueError('truncate received an invalid value of decimals ({})'.format(decimals))
    Elif decimals == 0:
        return trunc(number)
    else:
        factor = float(10**decimals)
        return trunc(number*factor)/factor
4
Alvaro

Tu peux faire:

def truncate(f, n):
    return math.floor(f * 10 ** n) / 10 ** n

essai:

>>> f=1.923328437452
>>> [truncate(f, n) for n in range(5)]
[1.0, 1.9, 1.92, 1.923, 1.9233]
4
user648852

Si vous avez envie de mathémagique, cela fonctionne pour les numéros + ve:

>>> v = 1.923328437452
>>> v - v % 1e-3
1.923
3
coldspeed

utilisez numpy.round

import numpy as np
precision = 3
floats = [1.123123123, 2.321321321321]
new_float = np.round(floats, precision)
1
rafaelvalle

int (16.5); cela donnera une valeur entière de 16, c'est-à-dire trunc, ne pourra pas spécifier de décimales, mais supposez que vous pouvez le faire en 

import math;

def trunc(invalue, digits):
    return int(invalue*math.pow(10,digits))/math.pow(10,digits);
1
Pieter

Une fonction générale et simple à utiliser:

def truncate_float(number, length):
    """Truncate float numbers, up to the number specified
    in length that must be an integer"""

    number = number * pow(10, length)
    number = int(number)
    number = float(number)
    number /= pow(10, length)
    return number
1
Yohan Obadia
def trunc(f,n):
  return ('%.16f' % f)[:(n-16)]
1
Ross Cartlidge

Je voulais juste mentionner que le vieux tour "make round () with floor ()"

round(f) = floor(f+0.5)

peut être retourné pour faire le plancher () du tour ()

floor(f) = round(f-0.5)

Bien que ces deux règles séparent les nombres négatifs, son utilisation n’est donc pas idéale:

def trunc(f, n):
    if f > 0:
        return "%.*f" % (n, (f - 0.5*10**-n))
    Elif f == 0:
        return "%.*f" % (n, f)
    Elif f < 0:
        return "%.*f" % (n, (f + 0.5*10**-n))
1
itsadok
def precision(value, precision):
    """
    param: value: takes a float
    param: precision: int, number of decimal places
    returns a float
    """
    x = 10.0**precision
    num = int(value * x)/ x
    return num
precision(1.923328437452, 3)

1,923

1
Andrew Olson

>>> sol ((1.23658945) * 10 ** 4)/10 ** 4
1.2365

# diviser et multiplier par 10 ** nombre de chiffres souhaités

0
JohnA

Variante courte et facile

def truncate_float(value, digits_after_point=2):
    pow_10 = 10 ** digits_after_point
    return (float(int(value * pow_10))) / pow_10

>>> truncate_float(1.14333, 2)
>>> 1.14

>>> truncate_float(1.14777, 2)
>>> 1.14


>>> truncate_float(1.14777, 4)
>>> 1.1477
0
megajoe

L'idée centrale donnée ici me semble être la meilleure approche pour résoudre ce problème. Malheureusement, il a reçu moins de votes alors que le réponse ultérieure qui a plus de votes n'est pas complet (comme observé dans les commentaires). Espérons que la mise en oeuvre ci-dessous fournit une solution complète et complète pour troncation.

def trunc(num, digits):
    l = str(float(num)).split('.')
    digits = min(len(l[1]), digits)
    return (l[0]+'.'+l[1][:digits])

qui devrait prendre en charge tous les cas de coin trouvés ici et ici .

0
aak318

Voici un moyen facile:

def truncate(num, res=3):
    return (floor(num*pow(10, res)+0.5))/pow(10, res)

pour num = 1.923328437452, cette sortie 1.923

0
Sarang

Lorsque vous utilisez un pandas df, cela a fonctionné pour moi

import math
def truncate(number, digits) -> float:
    stepper = 10.0 ** digits
    return math.trunc(stepper * number) / stepper

df['trunc'] = df['float_val'].apply(lambda x: truncate(x,1))
df['trunc']=df['trunc'].map('{:.1f}'.format)
0
bart cubrich

Il existe une solution de contournement facile dans python 3. Où couper J'ai défini avec une variable d'aide decPlace afin de faciliter son adaptation.

f = 1.12345
decPlace= 4
f_cut = int(f * 10**decPlace) /10**decPlace

Sortie:

f = 1.1234

J'espère que ça aide.

0
MBreg

Quelque chose d'assez simple pour tenir dans une liste de compréhension, sans bibliothèques ni autres dépendances externes. Pour Python> = 3.6, écrire avec des chaînes de caractères est très simple.

L'idée est de laisser la conversion de chaîne arrondir le à un endroit de plus que nécessaire, puis couper le dernier chiffre.

>>> nout = 3  # desired number of digits in output
>>> [f'{x:.{nout+1}f}'[:-1] for x in [2/3, 4/5, 8/9, 9/8, 5/4, 3/2]]
['0.666', '0.800', '0.888', '1.125', '1.250', '1.500']

Bien sûr, il y a est arrondi qui se passe ici (notamment pour le quatrième chiffre), mais arrondir à un moment donné est inévitable. Si la transition entre la troncature et l'arrondi est pertinente, voici un exemple légèrement meilleur:

>>> nacc = 6  # desired accuracy (maximum 15!)
>>> nout = 3  # desired number of digits in output
>>> [f'{x:.{nacc}f}'[:-(nacc-nout)] for x in [2.9999, 2.99999, 2.999999, 2.9999999]]
>>> ['2.999', '2.999', '2.999', '3.000']

Bonus: supprimer les zéros à droite

>>> nout = 3  # desired number of digits in output
>>> [f'{x:.{nout+1}f}'[:-1].rstrip('0') for x in [2/3, 4/5, 8/9, 9/8, 5/4, 3/2]]
['0.666', '0.8', '0.888', '1.125', '1.25', '1.5']
0
Axel

La plupart des réponses sont bien trop compliquées à mon avis, qu'en est-il de cela?

digits = 2  # Specify how many digits you want

fnum = '122.485221'
truncated_float = float(fnum[:fnum.find('.') + digits + 1])

>>> 122.48

Il suffit de rechercher l’index de '.' et tronquer comme vous le souhaitez (pas d'arrondi). Convertir la chaîne en float comme dernière étape.

Ou dans votre cas si vous obtenez un float en entrée et souhaitez une chaîne en sortie:

fnum = str(122.485221)  # convert float to string first
truncated_float = fnum[:fnum.find('.') + digits + 1]  # string output
0
Henry