Troncare i float in Python


110

Voglio rimuovere le cifre da un float per avere un numero fisso di cifre dopo il punto, come:

1.923328437452 -> 1.923

Devo eseguire l'output come stringa in un'altra funzione, non stampare.

Inoltre voglio ignorare le cifre perse, non arrotondarle.


4
-1,233 dovrebbe essere troncato a -1,23 o -1,24?
Antony Hatchkins,

Risposte:


117

Innanzitutto, la funzione, per coloro che desiderano solo un codice copia e incolla:

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

Questo è valido in Python 2.7 e 3.1+. Per le versioni precedenti, non è possibile ottenere lo stesso effetto di "arrotondamento intelligente" (almeno, non senza molto codice complicato), ma l'arrotondamento a 12 cifre decimali prima del troncamento funzionerà per la maggior parte del tempo:

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

Spiegazione

Il nucleo del metodo sottostante è convertire il valore in una stringa con la massima precisione e quindi tagliare tutto oltre il numero di caratteri desiderato. L'ultimo passaggio è facile; può essere fatto sia con la manipolazione delle stringhe

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

o il decimalmodulo

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

Il primo passo, la conversione in una stringa, è abbastanza difficile perché ci sono alcune coppie di letterali in virgola mobile (cioè ciò che scrivi nel codice sorgente) che producono entrambi la stessa rappresentazione binaria e tuttavia dovrebbero essere troncati in modo diverso. Ad esempio, considera 0,3 e 0,29999999999999998. Se scrivi 0.3in un programma Python, il compilatore lo codifica utilizzando il formato a virgola mobile IEEE nella sequenza di bit (assumendo un float a 64 bit)

0011111111010011001100110011001100110011001100110011001100110011

Questo è il valore più vicino a 0,3 che può essere rappresentato con precisione come un float IEEE. Ma se scrivi 0.29999999999999998in un programma Python, il compilatore lo traduce esattamente nello stesso valore . In un caso, volevi che fosse troncato (a una cifra) come 0.3, mentre nell'altro caso lo volevi troncato come 0.2, ma Python può dare solo una risposta. Questa è una limitazione fondamentale di Python, o addirittura di qualsiasi linguaggio di programmazione senza una valutazione pigra. La funzione di troncamento ha accesso solo al valore binario archiviato nella memoria del computer, non alla stringa che hai effettivamente digitato nel codice sorgente. 1

Se decodifichi la sequenza di bit in un numero decimale, sempre utilizzando il formato a virgola mobile IEEE a 64 bit, ottieni

0.2999999999999999888977697537484345957637...

quindi un'implementazione ingenua verrebbe fuori 0.2anche se probabilmente non è quello che vuoi. Per ulteriori informazioni sull'errore di rappresentazione in virgola mobile, vedere il tutorial di Python .

È molto raro lavorare con un valore a virgola mobile che è così vicino a un numero tondo e tuttavia non è intenzionalmente uguale a quel numero tondo. Quindi, durante il troncamento, probabilmente ha senso scegliere la rappresentazione decimale "più gradevole" tra tutte quelle che potrebbero corrispondere al valore in memoria. Python 2.7 e versioni successive (ma non 3.0) include un sofisticato algoritmo per fare proprio questo , a cui possiamo accedere tramite l'operazione di formattazione delle stringhe predefinita.

'{}'.format(f)

L'unico avvertimento è che questo agisce come una gspecifica di formato, nel senso che usa la notazione esponenziale ( 1.23e+4) se il numero è abbastanza grande o abbastanza piccolo. Quindi il metodo deve catturare questo caso e gestirlo in modo diverso. Ci sono alcuni casi in cui l'utilizzo di una fspecifica di formato causa invece un problema, come il tentativo di troncare 3e-10a 28 cifre di precisione (produce 0.0000000002999999999999999980), e non sono ancora sicuro di come gestirle al meglio.

Se effettivamente sta lavorando con floats che sono molto vicino a cifra tonda, ma volutamente non uguale a loro (come 0,29999999999999998 o 99,959999999999994), questo produrrà alcuni falsi positivi, vale a dire che sarà cifra tonda che non volevano arrotondata. In tal caso la soluzione è specificare una precisione fissa.

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

Il numero di cifre di precisione da usare qui non ha molta importanza, deve solo essere abbastanza grande da garantire che qualsiasi arrotondamento eseguito nella conversione di stringa non "aumenti" il valore alla sua bella rappresentazione decimale. Penso che sys.float_info.dig + n + 2possa essere sufficiente in tutti i casi, ma in caso contrario 2potrebbe essere necessario aumentare, e non fa male farlo.

Nelle versioni precedenti di Python (fino a 2.6 o 3.0), la formattazione del numero in virgola mobile era molto più rozza e produceva regolarmente cose come

>>> 1.1
1.1000000000000001

Se questa è la tua situazione, se non vuole usare "belle" rappresentazioni decimali per troncamento, tutto si può fare (per quanto ne so) è prendere un numero di cifre, meno del rappresentabile massima precisione da una float, e intorno al numero a quel numero di cifre prima di troncarlo. Una scelta tipica è 12,

'%.12f' % f

ma puoi modificarlo per adattarlo ai numeri che stai utilizzando.


1 Beh ... ho mentito. Tecnicamente, puoi istruire Python a rianalizzare il proprio codice sorgente ed estrarre la parte corrispondente al primo argomento passato alla funzione di troncamento. Se quell'argomento è un letterale a virgola mobile, puoi semplicemente tagliarlo fuori un certo numero di posizioni dopo il punto decimale e restituirlo. Tuttavia questa strategia non funziona se l'argomento è una variabile, il che lo rende abbastanza inutile. Quanto segue è presentato solo a scopo di intrattenimento:

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

Generalizzare questo per gestire il caso in cui passi una variabile sembra una causa persa, dal momento che dovresti risalire all'indietro attraverso l'esecuzione del programma fino a trovare il valore letterale a virgola mobile che ha dato alla variabile il suo valore. Se ce n'è anche uno. La maggior parte delle variabili verrà inizializzata dall'input dell'utente o da espressioni matematiche, nel qual caso la rappresentazione binaria è tutto ciò che c'è.


Come possiamo applicare questa funzione a un dataframe?
signore dei codici

@RohithRNair Dalla parte superiore della mia testa, allo stesso modo in cui applicheresti qualsiasi altra funzione che opera su singoli elementi (cioè applymap()). Forse c'è un modo per rendere l'intera operazione più efficiente, ma sarebbe una questione a parte.
David Z

applymap () impiega molto tempo poiché i miei dataframe sono davvero grandi. Sto cercando di confrontare due frame di dati per le differenze, ma la precisione in virgola mobile distorce il mio output da quello desiderato. Come hai detto, solleverò una domanda separata per lo stesso. Grazie.
signore dei codici

@RohithRNair Ah, beh, se stai cercando di confrontare due dataframe per differenze, chiedi invece quello. Troncare i valori (di cui tratta questa domanda) non è il modo migliore per farlo.
David Z

Solo una nota, il tuo codice sembra tagliare i numeri negativi a zero negativo, il che può creare confusione ...
user541686

152
round(1.923328437452, 3)

Vedi la documentazione di Python sui tipi standard . Avrai bisogno di scorrere un po 'verso il basso per arrivare alla funzione round. Essenzialmente il secondo numero dice a quante cifre decimali arrotondarlo.


49
Volevo dire che l'arrotondamento non è ciò di cui ho bisogno. Ho bisogno di troncare, che è diverso.
Joan Venge

1
Ahhh, abbastanza giusto. Errore mio, scusa.
Teifion

22
Sono molti voti positivi per una soluzione errata! Una di quelle strane rarità Stackoverflow. Mi chiedo se ci sia un distintivo per questo ...
tumultous_rooster

5
È semplicemente spaventoso quante risposte sbagliate (e voti per le risposte sbagliate) ci sono per questa domanda.
nullstellensatz

6
Molte persone verranno in questa pagina alla ricerca di arrotondamenti;)
janjackson

33

Il risultato di roundè un float, quindi fai attenzione (l'esempio è da Python 2.6):

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

Starai meglio quando usi una stringa formattata:

>>> "%.3f" % 1.923328437452
'1.923'
>>> "%.3f" % 1.23456
'1.235'

8
Sul mio Python, questo arrotonda: '% .3f'% 1.23456 == '1.235'
David Z

Questo è molto più elegante dell'assurdità di formattazione manuale delle stringhe, buon post!
rsethc

round(1.23456, 3)è 1.235e non1.2350000000000001
Ahmad

1
@ Ahmad non necessariamente. L'esempio qui è da Python 2.6 (annotare la data della risposta). La formattazione delle stringhe è stata migliorata in Python 2.7 / 3.1, questo è probabilmente il motivo per cui ottieni risultati diversi. Tuttavia, i numeri in virgola mobile avranno spesso rappresentazioni di stringhe inaspettate, vedere: docs.python.org/3.6/tutorial/floatingpoint.html
Ferdinand Beyer

21
n = 1.923328437452
str(n)[:4]

3
Semplice e pitonico. Tuttavia, 4 è la dimensione dell'intero numero, non solo le cifre dopo il punto.
GaTTaCa

4
Quindi, se l'utente inserisce ad esempio 2, avrai un punto decimale .alla fine della stringa - non credo sia una buona soluzione.
Zelphir Kaltstahl

Questo è specifico per un caso a questo numero. Come si generalizzerebbe a 11.923328437452?
polarizzare il

Migliore risposta! potresti anche aggiungere float () per restituire un numero: float (str (n) [: 4])
justSaid

14

Al mio prompt di Python 2.7:

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


11

Script Python semplice -

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

3
Risposta pulita. Ti manca solo un passaggio, per riconvertirti in float prima di dividere per 1000. Altrimenti, otterrai 1.
Yohan Obadia

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

Questo dovrebbe funzionare. Dovrebbe darti il ​​troncamento che stai cercando.


9

Il modo veramente pitonico di farlo è

from decimal import *

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

o più breve:

from decimal import Decimal as D, ROUND_DOWN

D('1.923328437452').quantize(D('0.001'), rounding=ROUND_DOWN)

Aggiornare

Di solito il problema non sta nel troncare i float stessi, ma nell'uso improprio dei numeri float prima dell'arrotondamento.

Per esempio: int(0.7*3*100)/100 == 2.09 .

Se sei costretto a usare float (diciamo, stai accelerando il tuo codice con numba), è meglio usare i centesimi come "rappresentazione interna" dei prezzi: ( 70*3 == 210) e moltiplicare / dividere gli input / output.


Parson me per averlo chiesto, ma ... perché?
markroxor

@markroxor, non sono sicuro di cosa stai chiedendo esattamente. Come nota a margine, di solito il problema non è con l'arrotondamento in sé, ma con l'uso improprio di numeri float prima dell'arrotondamento. Ad esempio int(0.7*3*100)/100 == 2.09. Dove è finito il mio 1 centesimo?
Antony Hatchkins

ha senso, puoi modificare la tua risposta con questa spiegazione? Grazie.
markroxor

Ottenendo ImportError: cannot import name 'D', credo che volessi fare un'importazione con nome no?
Overdrivr

8

Molte delle risposte fornite per questa domanda sono completamente sbagliate. Arrotondano i float (anziché troncare) o non funzionano per tutti i casi.

Questo è il miglior risultato di Google quando cerco "Python truncate float", un concetto che è davvero semplice e che merita risposte migliori. Sono d'accordo con Hatchkins che l'utilizzo del decimalmodulo è il modo pitonico di farlo, quindi fornisco qui una funzione che penso risponda correttamente alla domanda e che funzioni come previsto per tutti i casi.

Come nota a margine, i valori frazionari, in generale, non possono essere rappresentati esattamente da variabili binarie a virgola mobile (vedi qui per una discussione di questo), motivo per cui la mia funzione restituisce una stringa.

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

4

Ho fatto qualcosa di simile:

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

Tu puoi fare:

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

test:

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

Questo tronca solo con numeri positivi, i numeri negativi verranno arrotondati per difetto (lontano da zero).
Aaron D

3

Se hai voglia di un po 'di matematica, funziona per + ve numeri:

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

A quanto ho capito, 1e-3 verrà troncato a 3 cifre dopo il punto. Mi è piaciuta questa risposta ma non sembra funzionare per 4 e 5.
egvo

2

Quando si utilizza un panda df questo ha funzionato per me

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)

1

Volevo solo menzionare che il vecchio trucco "make round () with floor ()" di

round(f) = floor(f+0.5)

può essere girato per creare floor () da round ()

floor(f) = round(f-0.5)

Sebbene entrambe queste regole violino i numeri negativi, quindi utilizzarlo è tutt'altro che ideale:

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

int (16.5); questo darà un valore intero di 16, cioè trunc, non sarà in grado di specificare i decimali, ma immagino che tu possa farlo con

import math;

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

1

Ecco un modo semplice:

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

per num = 1.923328437452, questo restituisce 1.923



1

Una funzione generale e semplice da usare:

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

Grande! Il cast su int tronca sia i numeri positivi che quelli negativi.
Aaron D

1

C'è una facile soluzione alternativa in Python 3. Dove tagliare Ho definito con una variabile di aiuto decPlace per renderlo facile da adattare.

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

Produzione:

f = 1.1234

Spero che sia d'aiuto.


1
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


Bello ma non stai arrotondando.
Alex

1

Variante breve e 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

1

La maggior parte delle risposte sono troppo complicate secondo me, che ne dici?

digits = 2  # Specify how many digits you want

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

>>> 122.48

Semplicemente cercando l'indice di "." e troncare come desiderato (senza arrotondamenti). Converti stringa in float come passaggio finale.

O nel tuo caso se ottieni un float come input e vuoi una stringa come output:

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

La tua proposta è problematica se il numero che viene troncato è piccolo poiché sprecheresti molta precisione con lo 0 iniziale a destra del punto decimale. Ma questo problema è endemico del problema come affermato. Quello che sto cercando di dire è che le cifre significative sono la vera risposta.
overcoil

1
>>> floor((1.23658945) * 10**4) / 10**4
1.2365

# dividere e moltiplicare per 10 ** numero di cifre desiderate


0

usa numpy.round

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

0

Qualcosa di abbastanza semplice da rientrare in una lista di comprensione, senza librerie o altre dipendenze esterne. Per Python> = 3.6, è molto semplice scrivere con le stringhe f.

L'idea è di lasciare che la conversione della stringa faccia l'arrotondamento a un punto in più di quello che ti serve e poi tagli l'ultima cifra.

>>> 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']

Ovviamente, qui si sta verificando un arrotondamento (vale a dire per la quarta cifra), ma l'arrotondamento a un certo punto è inevitabile. Nel caso in cui la transizione tra troncamento e arrotondamento sia rilevante, ecco un esempio leggermente migliore:

>>> 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: rimozione degli zeri a destra

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

L'idea centrale data qui mi sembra essere l'approccio migliore per questo problema. Purtroppo ha ricevuto meno voti mentre la risposta successiva che ha più voti non è completa (come osservato nei commenti). Si spera che l'implementazione di seguito fornisca una soluzione breve e completa per il troncamento .

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

che dovrebbe occuparsi di tutti i casi d'angolo trovati qui e qui .


-1

Sono anche un principiante di Python e dopo aver usato alcuni pezzi qui, offro i miei due centesimi

print str(int(time.time()))+str(datetime.now().microsecond)[:3]

str (int (time.time ())) prenderà il tempo epoch come int e lo convertirà in stringa e si unirà con ... str (datetime.now (). microsecond) [: 3] che restituisce solo i microsecondi, converti stringere e troncare ai primi 3 caratteri


-1
# value  value to be truncated
# n  number of values after decimal

value = 0.999782
n = 3
float(int(value*1en))*1e-n

-3

Se intendi durante la stampa, dovrebbe funzionare quanto segue:

print '%.3f' % number

2
Questo arrotonda il numero, non lo tronca.
David Z
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.