Obcinanie pływaków w Pythonie


110

Chcę usunąć cyfry z pływaka, aby mieć stałą liczbę cyfr po kropce, na przykład:

1.923328437452 -> 1.923

Muszę wyprowadzić jako ciąg do innej funkcji, a nie drukować.

Chcę też zignorować utracone cyfry, a nie je zaokrąglić.


4
Czy należy skrócić -1,233 do -1,23 czy -1,24?
Antony Hatchkins

Odpowiedzi:


117

Po pierwsze, funkcja dla tych, którzy chcą po prostu kopiować i wklejać kod:

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

Dotyczy to Pythona 2.7 i 3.1+. W przypadku starszych wersji nie można uzyskać tego samego efektu „inteligentnego zaokrąglania” (przynajmniej nie bez mnóstwa skomplikowanego kodu), ale zaokrąglanie do 12 miejsc po przecinku przed obcięciem będzie działać przez większość czasu:

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

Wyjaśnienie

Podstawową metodą jest przekonwertowanie wartości na ciąg z pełną precyzją, a następnie odcięcie wszystkiego poza wymaganą liczbę znaków. Drugi krok jest łatwy; można to zrobić za pomocą manipulacji na łańcuchach

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

lub decimalmoduł

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

Pierwszy krok, konwersja na łańcuch, jest dość trudny, ponieważ istnieje kilka par literałów zmiennoprzecinkowych (tj. Tego, co piszesz w kodzie źródłowym), które tworzą tę samą reprezentację binarną, a mimo to powinny być obcięte w inny sposób. Na przykład rozważmy 0,3 i 0,29999999999999998. Jeśli piszesz 0.3w programie w języku Python, kompilator koduje go przy użyciu formatu zmiennoprzecinkowego IEEE do sekwencji bitów (zakładając 64-bitową liczbę zmiennoprzecinkową)

0011111111010011001100110011001100110011001100110011001100110011

Jest to wartość najbliższa 0,3, którą można dokładnie przedstawić jako wartość zmiennoprzecinkową IEEE. Ale jeśli piszesz 0.29999999999999998w programie w Pythonie, kompilator tłumaczy to na dokładnie tę samą wartość . W jednym przypadku miałeś na myśli obcięcie go (do jednej cyfry) jako 0.3, podczas gdy w innym przypadku chciałeś, aby został skrócony jako 0.2, ale Python może udzielić tylko jednej odpowiedzi. Jest to podstawowe ograniczenie Pythona, a właściwie każdego języka programowania bez leniwej oceny. Funkcja obcinania ma dostęp tylko do wartości binarnej przechowywanej w pamięci komputera, a nie do ciągu, który faktycznie wpisałeś w kodzie źródłowym. 1

Jeśli zdekodujesz sekwencję bitów z powrotem do liczby dziesiętnej, ponownie używając 64-bitowego formatu zmiennoprzecinkowego IEEE, otrzymasz

0.2999999999999999888977697537484345957637...

więc wpadłaby naiwna implementacja, 0.2nawet jeśli prawdopodobnie nie tego chcesz. Aby uzyskać więcej informacji na temat błędu reprezentacji zmiennoprzecinkowej, zobacz samouczek języka Python .

Bardzo rzadko można pracować z wartością zmiennoprzecinkową, która jest tak bliska okrągłej liczbie, ale celowo nie jest równa tej okrągłej liczbie. Dlatego podczas obcinania prawdopodobnie sensowne jest wybranie „najładniejszej” reprezentacji dziesiętnej spośród wszystkich, które mogą odpowiadać wartości w pamięci. Python 2.7 i nowsze (ale nie 3.0) zawierają wyrafinowany algorytm, który to robi , do którego możemy uzyskać dostęp poprzez domyślną operację formatowania ciągu.

'{}'.format(f)

Jedynym zastrzeżeniem jest to, że działa to jak gspecyfikacja formatu, w tym sensie, że używa notacji wykładniczej ( 1.23e+4), jeśli liczba jest wystarczająco duża lub wystarczająco mała. Więc metoda musi wychwycić ten przypadek i obsłużyć go inaczej. Jest kilka przypadków, w których użycie fspecyfikacji formatu zamiast tego powoduje problem, na przykład próba skrócenia 3e-10dokładności do 28 cyfr (daje to 0.0000000002999999999999999980), i nie jestem jeszcze pewien, jak najlepiej sobie z nimi poradzić.

Jeśli rzeczywiście pracy z floatS, które są bardzo zbliżone do okrągłych liczb, ale celowo nie równa się do nich (jak 0.29999999999999998 lub 99.959999999999994), to będzie produkować kilka fałszywych alarmów, to znaczy będziemy okrągłych liczb, które nie chcą zaokrąglone. W takim przypadku rozwiązaniem jest określenie stałej precyzji.

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

Liczba precyzyjnych cyfr, których należy tu użyć, nie ma tak naprawdę znaczenia, musi być tylko wystarczająco duża, aby zapewnić, że jakiekolwiek zaokrąglenia wykonywane podczas konwersji ciągów nie „podbijają” wartości do jej ładnej reprezentacji dziesiętnej. Myślę, że sys.float_info.dig + n + 2może wystarczyć we wszystkich przypadkach, ale jeśli nie, 2może to wymagać zwiększenia i nie zaszkodzi to zrobić.

We wcześniejszych wersjach Pythona (do 2.6 lub 3.0) formatowanie liczb zmiennoprzecinkowych było dużo bardziej prymitywne i regularnie powodowało takie rzeczy, jak

>>> 1.1
1.1000000000000001

Jeśli to jest Twoja sytuacja, jeśli nie chcesz korzystać z „nice” reprezentacje dziesiętne do obcinania, wszystko można zrobić (o ile wiem), to wybrać pewną liczbę cyfr, mniej niż pełnej precyzji reprezentowana przez floati Round numer do tylu cyfr przed skróceniem go. Typowy wybór to 12,

'%.12f' % f

ale możesz to dostosować do używanych liczb.


1 Cóż ... skłamałem. Technicznie, można polecić Python, aby ponownie przeanalizować swój własny kod źródłowy i wyodrębnić część odpowiadającą pierwszego argumentu możesz przekazać do funkcji przycinania. Jeśli ten argument jest literałem zmiennoprzecinkowym, możesz po prostu odciąć go o określoną liczbę miejsc po przecinku i zwrócić to. Jednak ta strategia nie działa, jeśli argument jest zmienną, co czyni ją dość bezużyteczną. Poniższe informacje przedstawiono wyłącznie w celach rozrywkowych:

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

Uogólnienie tego, aby obsłużyć przypadek, w którym przekazujesz zmienną, wydaje się być utraconą przyczyną, ponieważ musiałbyś prześledzić wstecz przez wykonanie programu, aż znajdziesz literał zmiennoprzecinkowy, który nadał zmiennej jej wartość. Jeśli w ogóle istnieje. Większość zmiennych zostanie zainicjowana na podstawie danych wejściowych użytkownika lub wyrażeń matematycznych, w takim przypadku wystarczy reprezentacja binarna.


Jak możemy zastosować tę funkcję do ramki danych?
codelord

@RohithRNair Z góry mi z głowy, tak samo jak każdą inną funkcję działającą na poszczególnych elementach (tj applymap().). Może jest sposób, aby cała operacja była bardziej wydajna, ale to byłaby kwestia osobnego pytania.
David Z

applymap () zajmuje dużo czasu, ponieważ moje ramki danych są naprawdę duże. Próbuję porównać dwie ramki danych pod kątem różnic, ale precyzja zmiennoprzecinkowa wypacza moje dane wyjściowe od pożądanych. Jak powiedziałeś, zadam osobne pytanie w tej sprawie. Dzięki.
codelord

@RohithRNair Ah, cóż, jeśli próbujesz porównać dwie ramki danych pod kątem różnic, zapytaj o to. Obcinanie wartości (o co chodzi w tym pytaniu) nie jest najlepszym sposobem na zrobienie tego.
David Z,

Uwaga, twój kod wydaje się przycinać liczby ujemne do zera ujemnego, co może być mylące ...
user541686

152
round(1.923328437452, 3)

Zobacz dokumentację Pythona na temat standardowych typów . Musisz trochę przewinąć w dół, aby przejść do funkcji rundy. Zasadniczo druga liczba mówi, do ilu miejsc po przecinku należy ją zaokrąglić.


49
Miałem na myśli, że zaokrąglanie nie jest tym, czego potrzebuję. Potrzebuję obcięcia, co jest inne.
Joan Venge

1
Ahhh, w porządku. Przepraszam, mój błąd.
Teifion

22
To dużo pozytywnych opinii za niewłaściwe rozwiązanie! Jedna z tych dziwnych rzadkości w Stackoverflow. Ciekawe, czy jest za to odznaka ...
tumultous_rooster

5
To po prostu przerażające, ile błędnych odpowiedzi (i głosów pozytywnych za złe odpowiedzi) jest na to pytanie.
nullstellensatz

6
Wiele osób będzie przychodzić na tę stronę w poszukiwaniu zaokrąglenia;)
janjackson

33

Wynik roundjest float, więc uważaj (przykład pochodzi z Pythona 2.6):

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

Lepiej będzie, jeśli użyjesz sformatowanego ciągu:

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

8
W moim Pythonie zaokrągla się to: '% .3f'% 1.23456 == '1.235'
David Z

Jest to o wiele bardziej eleganckie niż bzdury z ręcznym formatowaniem ciągów, dobry post!
rsethc

round(1.23456, 3)jest 1.235i nie jest1.2350000000000001
Ahmad

1
@Ahmad niekoniecznie. Przykład pochodzi z Pythona 2.6 (zwróć uwagę na datę odpowiedzi). Formatowanie ciągów zostało ulepszone w Pythonie 2.7 / 3.1, prawdopodobnie dlatego otrzymujesz inne wyniki. Niemniej jednak liczby zmiennoprzecinkowe często mają nieoczekiwane reprezentacje ciągów, patrz: docs.python.org/3.6/tutorial/floatingpoint.html
Ferdinand Beyer

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

3
Prosty i pytoniczny. 4 to jednak wielkość całej liczby, a nie tylko cyfr po kropce.
GaTTaCa

4
Więc jeśli użytkownik wejdzie na przykład 2, będziesz mieć kropkę dziesiętną .na końcu ciągu - myślę, że nie jest to dobre rozwiązanie.
Zelphir Kaltstahl

Jest to specyficzne dla sprawy o tym numerze. Jak to uogólnia się na 11.923328437452?
polaryzuj

Najlepsza odpowiedź! możesz również dodać float (), aby zwrócić liczbę: float (str (n) [: 4])
justSaid

14

Na mój znak zachęty Pythona 2.7:

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


11

Prosty skrypt w Pythonie -

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

3
Czysta odpowiedź. Po prostu przegapisz jeden krok, aby przekonwertować z powrotem na zmiennoprzecinkowy przed podzieleniem przez 1000. W przeciwnym razie otrzymasz 1.
Yohan Obadia

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

To powinno działać. Powinien dać ci skrócenie, którego szukasz.


9

Prawdziwie pytoniczny sposób to zrobić

from decimal import *

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

lub krócej:

from decimal import Decimal as D, ROUND_DOWN

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

Aktualizacja

Zwykle problemem nie jest samo obcinanie liczb zmiennoprzecinkowych, ale niewłaściwe użycie liczb zmiennoprzecinkowych przed zaokrągleniem.

Na przykład: int(0.7*3*100)/100 == 2.09.

Jeśli jesteś zmuszony używać pływaków (powiedzmy, że przyspieszasz swój kod numba), lepiej użyć centów jako „wewnętrznej reprezentacji” cen: ( 70*3 == 210) i pomnożyć / podzielić dane wejściowe / wyjściowe.


Przepraszam, że pytam, ale ... dlaczego?
markroxor

@markroxor, nie wiem, o co dokładnie pytasz. Na marginesie, zwykle problem nie dotyczy samego zaokrąglania, ale niewłaściwego użycia liczb zmiennoprzecinkowych przed zaokrągleniem. Np int(0.7*3*100)/100 == 2.09. Gdzie poszedł mój 1 cent?
Antony Hatchkins,

to ma sens, czy możesz edytować swoją odpowiedź z tym wyjaśnieniem? dzięki.
markroxor

Pierwsze ImportError: cannot import name 'D', wierzę, że chciał zrobić o nazwie import nie?
Overdrivr

8

Tak wiele odpowiedzi udzielonych na to pytanie jest po prostu całkowicie błędnych. Albo zaokrąglają liczby zmiennoprzecinkowe w górę (zamiast skracać), albo nie działają we wszystkich przypadkach.

To jest najlepszy wynik Google, kiedy wyszukuję „Python truncate float”, koncepcja, która jest naprawdę prosta i zasługuje na lepsze odpowiedzi. Zgadzam się z Hatchkinsem, że użycie decimalmodułu jest pythonowym sposobem na zrobienie tego, więc podaję tutaj funkcję, która moim zdaniem poprawnie odpowiada na pytanie i która działa zgodnie z oczekiwaniami we wszystkich przypadkach.

Na marginesie, wartości ułamkowe na ogół nie mogą być dokładnie reprezentowane przez binarne zmienne zmiennoprzecinkowe (zobacz tutaj omówienie tego), dlatego moja funkcja zwraca ciąg.

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

Zrobiłem coś takiego:

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

Możesz to zrobić:

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

testowanie:

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

To obcina tylko z liczbami dodatnimi, liczby ujemne zostaną zaokrąglone w dół (od zera).
Aaron D

3

Jeśli masz ochotę na matematykę, działa to dla liczb + ve:

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

Jak rozumiem, 1e-3 zostanie obcięte do 3 cyfr po kropce. Podobała mi się ta odpowiedź, ale wydaje się, że nie działa dla 4 i 5.
egvo

2

Podczas używania pandy df to działało dla mnie

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

Chciałem tylko wspomnieć, że stara sztuczka "make round () z floor ()"

round(f) = floor(f+0.5)

można odwrócić, aby podłoga () z rundy ()

floor(f) = round(f-0.5)

Chociaż obie te zasady łamią liczby ujemne, użycie ich jest mniej niż idealne:

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); da to liczbę całkowitą 16, tj. obcięcie, nie będzie w stanie podać liczb dziesiętnych, ale myślę, że możesz to zrobić przez

import math;

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

1

Oto prosty sposób:

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

dla num = 1,923328437452, daje to 1,923



1

Ogólna i prosta funkcja w użyciu:

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

Wspaniały! Rzutowanie na int obcina zarówno liczby dodatnie, jak i ujemne.
Aaron D

1

W Pythonie 3 jest łatwe obejście problemu. Gdzie wyciąć, zdefiniowałem za pomocą zmiennej pomocy decPlace, aby ułatwić dostosowanie.

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

Wynik:

f = 1.1234

Mam nadzieję, że to pomoże.


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


Fajnie, ale nie zaokrąglasz.
Alex,

1

Wariant krótki i łatwy

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

Moim zdaniem większość odpowiedzi jest zbyt skomplikowana, a co powiesz na to?

digits = 2  # Specify how many digits you want

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

>>> 122.48

Po prostu wyszukuję indeks „.” i obcinaj według potrzeb (bez zaokrąglania). Konwertuj ciąg na zmiennoprzecinkowy jako ostatni krok.

Lub w twoim przypadku, jeśli otrzymujesz zmiennoprzecinkowy jako dane wejściowe i chcesz, aby łańcuch jako wyjście:

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

Twoja propozycja jest problematyczna, jeśli obcinana liczba jest mała, ponieważ marnujesz dużo precyzji z początkowym 0 po prawej stronie przecinka. Ale ten problem jest endemiczny dla problemu, jak stwierdzono. Chcę powiedzieć, że prawdziwą odpowiedzią są liczby znaczące.
overcoil

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

# podziel i pomnóż przez 10 ** żądanych cyfr


0

użyj numpy.round

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

0

Coś na tyle prostego, aby zmieściło się w liście, bez bibliotek ani innych zewnętrznych zależności. W przypadku Pythona> = 3.6 bardzo łatwo jest pisać za pomocą f-strings.

Chodzi o to, aby konwersja ciągów zaokrągliła do jednego miejsca więcej niż potrzebujesz, a następnie odcięła ostatnią cyfrę.

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

Oczywiście zachodzi tutaj zaokrąglanie (mianowicie dla czwartej cyfry), ale zaokrąglenia w pewnym momencie nie da się uniknąć. Jeśli przejście między obcinaniem a zaokrąglaniem jest istotne, oto nieco lepszy przykład:

>>> 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: usunięcie zer po prawej stronie

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

Podana tutaj podstawowa idea wydaje mi się najlepszym podejściem do tego problemu. Niestety, otrzymała mniej głosów, a późniejsza odpowiedź, która ma więcej głosów, nie jest kompletna (jak zauważono w komentarzach). Miejmy nadzieję, że poniższa implementacja zapewnia krótkie i kompletne rozwiązanie do obcięcia .

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

który powinien zająć się wszystkimi narożnymi skrzynkami znalezionymi tutaj i tutaj .


-1

Jestem również nowicjuszem w Pythonie i po skorzystaniu z kilku kawałków tutaj, oferuję moje dwa centy

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

str (int (time.time ())) weźmie epokę czasu jako int i skonwertuje ją na łańcuch i połączy z ... str (datetime.now (). microsecond) [: 3], która zwraca tylko mikrosekundy, konwertuje ciągnąć i obcinać do pierwszych 3 znaków


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

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

-3

Jeśli masz na myśli drukowanie, to powinno działać:

print '%.3f' % number

2
To zaokrągla liczbę, ale nie jest obcięta.
David Z
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.