Python'da kayan sayıların kırpılması


110

Noktadan sonra sabit sayıda basamak olması için kayan noktadan basamakları kaldırmak istiyorum, örneğin:

1.923328437452 -> 1.923

Başka bir işleve bir dize olarak çıktı vermem gerekiyor, yazdırmam gerekiyor.

Ayrıca kayıp rakamları yuvarlamak değil, görmezden gelmek istiyorum.


4
-1.233, -1.23 veya -1.24 olarak mı kesilmelidir?
Antony Hatchkins

Yanıtlar:


117

İlk olarak, sadece biraz kopyala ve yapıştır kodu isteyenler için işlev:

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

Bu, Python 2.7 ve 3.1+ sürümlerinde geçerlidir. Daha eski sürümler için, aynı "akıllı yuvarlama" efektini elde etmek mümkün değildir (en azından, çok fazla karmaşık kod olmadan), ancak kesmeden önce 12 ondalık basamağa yuvarlama çoğu zaman işe yarayacaktır:

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

açıklama

Temel alınan yöntemin özü, değeri tam hassasiyetle bir dizeye dönüştürmek ve ardından istenen karakter sayısının ötesinde her şeyi kesmektir. İkinci adım kolaydır; dize manipülasyonu ile yapılabilir

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

veya decimalmodül

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

Bir dizgeye dönüştürmek olan ilk adım oldukça zordur, çünkü hem aynı ikili gösterimi üreten hem de farklı şekilde kesilmesi gereken bazı kayan nokta değişmezleri (yani kaynak kodda yazdıklarınız) vardır. Örneğin, 0.3 ve 0.29999999999999998'i düşünün. 0.3Bir Python programında yazarsanız , derleyici IEEE kayan nokta formatını kullanarak bit dizisine kodlar (64 bitlik kayan nokta varsayılarak)

0011111111010011001100110011001100110011001100110011001100110011

Bu, bir IEEE kayan nokta olarak doğru bir şekilde temsil edilebilen 0,3'e en yakın değerdir. Ancak 0.29999999999999998bir Python programında yazarsanız , derleyici onu tamamen aynı değere çevirir . Bir durumda, kısaltılmasını (bir haneye) 0.3kastettiniz, oysa diğer durumda kısaltılmasını kastettiniz 0.2, ancak Python yalnızca bir cevap verebilir. Bu, Python'un veya gerçekten de tembel değerlendirme içermeyen herhangi bir programlama dilinin temel bir sınırlamasıdır. Kesme işlevi, kaynak koda gerçekten yazdığınız dizeye değil, yalnızca bilgisayarın belleğinde depolanan ikili değere erişebilir. 1

Bit dizisini tekrar ondalık sayıya çözerseniz, yine IEEE 64 bit kayan nokta biçimini kullanarak,

0.2999999999999999888977697537484345957637...

bu yüzden saf bir uygulama, 0.2muhtemelen istediğiniz şey bu olmasa bile ortaya çıkacaktır . Kayan nokta gösterimi hatası hakkında daha fazla bilgi için Python eğitimine bakın .

Yuvarlak bir sayıya çok yakın olan ve kasıtlı olarak bu yuvarlak sayıya eşit olmayan bir kayan nokta değeriyle çalışmak çok nadirdir . Bu yüzden, keserken, bellekteki değere karşılık gelebilecek her şeyden "en güzel" ondalık gösterimi seçmek muhtemelen mantıklıdır. Python 2.7 ve üstü (ancak 3.0 değil), tam da bunu yapmak için, varsayılan dize biçimlendirme işlemi aracılığıyla erişebileceğimiz gelişmiş bir algoritma içerir .

'{}'.format(f)

Tek uyarı, sayı yeterince büyük veya küçükse güstel gösterimi ( 1.23e+4) kullanması anlamında bunun bir format belirtimi gibi davranmasıdır . Dolayısıyla yöntemin bu durumu yakalaması ve farklı şekilde ele alması gerekir. fBunun yerine bir biçim belirtimi kullanmanın bir soruna neden olduğu birkaç durum vardır , örneğin 3e-1028 basamaklı kesinliğe kadar kısaltmaya çalışmak (üretir 0.0000000002999999999999999980) ve bunları en iyi nasıl ele alacağımdan henüz emin değilim.

Aslında Eğer edilir ile çalışan floatyuvarlak sayılara çok yakın ama kasıtlı (0.29999999999999998 veya 99.959999999999994 gibi) onlara eşit değil s, bu yani yuvarlak sayılar yuvarlak istemediğini gerekenler, bazı yanlış pozitif üretecektir. Bu durumda çözüm, sabit bir kesinlik belirlemektir.

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

Burada kullanılacak kesinlik basamaklarının sayısı gerçekten önemli değildir, sadece dize dönüşümünde gerçekleştirilen herhangi bir yuvarlamanın değeri hoş ondalık gösterimine "çarpmamasını" sağlayacak kadar büyük olması gerekir. Bence sys.float_info.dig + n + 2her durumda yeterli olabilir, ancak değilse 2bunun artırılması gerekebilir ve bunu yapmak zarar vermez.

Python'un önceki sürümlerinde (2.6 veya 3.0'a kadar), kayan nokta numarası biçimlendirmesi çok daha kabaydı ve düzenli olarak

>>> 1.1
1.1000000000000001

Eğer bu sizin durum ise do kesilmesi için "güzel" ondalık temsillerini kullanmak istiyorum, bütün kendinizi (bildiğim kadarıyla) yapabileceği bir tam hassas gösterilebilen daha az, basamak bazı numara seçmek floatve yuvarlak kesmeden önce bu kadar basamağa kadar sayı. Tipik bir seçim 12'dir.

'%.12f' % f

ancak bunu kullandığınız numaralara uyacak şekilde ayarlayabilirsiniz.


1 Şey ... yalan söyledim. Teknik olarak, Python'a kendi kaynak kodunu yeniden ayrıştırması ve kesme işlevine ilettiğiniz ilk argümana karşılık gelen kısmı çıkarması talimatını verebilirsiniz . Bu argüman bir kayan nokta değişmezi ise, ondalık noktadan sonra belirli sayıda basamak kesip geri döndürebilirsiniz. Ancak argüman bir değişken ise bu strateji işe yaramaz, bu da onu oldukça kullanışsız hale getirir. Aşağıdakiler yalnızca eğlence değeri için sunulmuştur:

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

Değişkene geçtiğiniz durumu ele almak için bunu genellemek kayıp bir neden gibi görünür, çünkü değişkene değerini veren kayan noktalı değişmezi bulana kadar programın yürütülmesi boyunca geriye doğru izleme yapmanız gerekir. Bir tane bile varsa. Değişkenlerin çoğu, kullanıcı girdisinden veya matematiksel ifadelerden ilklendirilecektir, bu durumda ikilik gösterim mevcut olan tek şeydir.


Bu işlevi bir veri çerçevesine nasıl uygulayabiliriz?
codelord

@RohithRNair Kafamın tepesinden, tek tek elemanlar (yani applymap()) üzerinde çalışan başka herhangi bir işlevi uyguladığınız gibi . Belki tüm operasyonu daha verimli hale getirmenin bir yolu vardır, ancak bu ayrı bir soru olabilir.
David Z

applymap (), veri çerçevelerim gerçekten büyük olduğu için çok zaman alıyor. Farklılıklar için iki veri çerçevesini karşılaştırmaya çalışıyorum, ancak kayan nokta hassasiyeti çıktımı istenen değerden saptırıyor. Dediğin gibi, aynısı için ayrı bir soru soracağım. Teşekkürler.
codelord

@RohithRNair Ah, eğer iki veri çerçevesini farklılıklar için karşılaştırmaya çalışıyorsanız, bunun yerine bunu sorun. Değerleri kısaltmak (bu sorunun konusu da budur) bunu yapmanın en iyi yolu değildir.
David Z

Sadece bir not, kodunuz negatif sayıları negatif sıfıra doğruyor gibi görünüyor, bu kafa karıştırıcı olabilir ...
user541686

152
round(1.923328437452, 3)

Python'un standart türler hakkındaki belgelerine bakın . Yuvarlak fonksiyona geçmek için biraz aşağı kaydırmanız gerekecek. Esasen ikinci sayı, kaç ondalık basamağa yuvarlanacağını söylüyor.


49
Demek istediğim yuvarlama ihtiyacım olan şey değil. Kesmeye ihtiyacım var, bu farklı.
Joan Venge

1
Ahhh, yeterince adil. Benim hatam özür dilerim.
Teifion

22
Yanlış bir çözüm için çok fazla olumlu oy var! Bu garip Stackoverflow nadirliklerinden biri. Bunun için bir rozet olup olmadığını merak ediyorum ...
tumultous_rooster

5
Bu soru için kaç tane yanlış cevap (ve yanlış cevaplar için olumlu oylar) olması şaşırtıcı.
nullstellensatz

6
Bir çok insan bu sayfaya yuvarlama yapmak için gelecek;)
janjackson

33

Sonuç roundbir float'tır, bu yüzden dikkatli olun (örnek Python 2.6'dan):

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

Biçimlendirilmiş bir dize kullandığınızda daha iyi olacaksınız:

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

8
Python'umda bu yuvarlar: '% .3f'% 1.23456 == '1.235'
David Z

Bu, manuel dize biçimlendirme saçmalıklarından çok daha zarif, iyi gönderi!
rsethc

round(1.23456, 3)olduğunu 1.235ve değil1.2350000000000001
Ahmed

1
@Ahmad mutlaka değil. Buradaki örnek Python 2.6'dandır (cevabın tarihini not edin). Dize biçimlendirme Python 2.7 / 3.1'de iyileştirildi, muhtemelen bu yüzden farklı sonuçlar alıyorsunuz. Yine de, kayan noktalı sayıların genellikle beklenmedik dize temsilleri olacaktır, bakınız: docs.python.org/3.6/tutorial/floatingpoint.html
Ferdinand Beyer

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

3
Basit ve pitonik. 4 tam sayının boyutudur, yalnızca noktadan sonraki rakamlar değil.
GaTTaCa

4
Dolayısıyla, örneğin kullanıcı girerse , dizenin sonunda 2bir ondalık nokta olacak .- bence gerçekten iyi bir çözüm değil.
Zelphir Kaltstahl

Bu, bu numaraya özgü bir durumdur. 11.923328437452'ye nasıl genellenebilir?
kutuplaşın

En iyi cevap! ayrıca bir sayı döndürmek için float () ekleyebilirsiniz: float (str (n) [: 4])
justSaid

14

Python 2.7 istemimde:

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


11

Basit python komut dosyası -

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

3
Temiz cevap. 1000'e bölmeden önce tekrar
float'a

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

Bu çalışmalı. Size aradığınız kısaltmayı vermelidir.


9

Bunu yapmanın gerçekten pitonik yolu

from decimal import *

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

veya daha kısa:

from decimal import Decimal as D, ROUND_DOWN

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

Güncelleme

Genellikle sorun, kayan noktaların kesilmesinde değil, yuvarlamadan önce kayan sayıların yanlış kullanımında ortaya çıkar .

Örneğin: int(0.7*3*100)/100 == 2.09 .

Float kullanmak zorunda kalırsanız (diyelim ki kodunuzu hızlandırıyorsanız numba), fiyatların "dahili temsili" olarak sent kullanmak daha iyidir: ( 70*3 == 210) ve girdileri / çıktıları çarpın / bölün.


Bunu sorduğum için bana parson ver, ama ... neden?
markroxor

@markroxor, tam olarak ne sorduğundan emin değilim. Bir yan not olarak, genellikle sorun yuvarlamanın kendisinde değil, yuvarlamadan önce kayan sayıların yanlış kullanılmasıdır . Örn int(0.7*3*100)/100 == 2.09. 1 sentim nereye gitti?
Antony Hatchkins

bu mantıklı, cevabınızı bu açıklamayla düzenleyebilir misiniz? Teşekkürler.
markroxor

Başlarken ImportError: cannot import name 'D', sanırım adlandırılmış bir ithalat no yapmak istediniz?
Overdrivr

8

Bu soruya verilen cevapların çoğu tamamen yanlış. Ya yüzer sayıları yuvarlarlar (kısaltmak yerine) ya da tüm durumlar için çalışmazlar.

Bu, gerçekten basit olan ve daha iyi yanıtları hak eden bir kavram olan 'Python truncate float' araması yaptığımda en iyi Google sonucudur. Hatchkins'e katılıyorum.decimalModül bunu yapmanın , bu yüzden burada soruyu doğru yanıtladığını düşündüğüm ve her durumda beklendiği gibi çalışan bir işlev veriyorum.

Bir yan not olarak, kesirli değerler, genel olarak, tam olarak ikili kayan nokta değişkenleriyle temsil edilemez (bununla ilgili bir tartışma için buraya bakın ), bu yüzden benim fonksiyonum bir dize döndürür.

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

Bunun gibi bir şey yaptım:

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

Yapabilirsin:

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

test yapmak:

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

Bu yalnızca pozitif sayılarla kesilir, negatif sayılar aşağı yuvarlanır (sıfırdan uzağa).
Aaron D

3

Biraz matematik sihirinden hoşlanıyorsanız, bu + ve sayıları için işe yarar:

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

Anladığım kadarıyla 1e-3, noktadan sonra 3 haneye kesilecek. Bu cevabı beğendim ama 4 ve 5 için işe yaramıyor gibi görünüyor
egvo

2

Pandalar df kullanırken bu benim için çalıştı

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

Eski "floor () ile yuvarlak () yap" hilesinin

round(f) = floor(f+0.5)

() yuvarlağından () zemin yapmak için çevrilebilir

floor(f) = round(f-0.5)

Bu kuralların her ikisi de negatif sayıları bozsa da, onu kullanmak idealden daha azdır:

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); bu 16 tamsayı değerini verecektir, yani kesik ondalık sayıları belirtemez, ancak tahmin edin bunu şu şekilde yapabilirsiniz:

import math;

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

1

İşte kolay bir yol:

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

num = 1.923328437452 için bu, 1.923 sonucunu verir



1

Kullanılacak genel ve basit bir işlev:

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

Harika! İnt'e çevrim, hem pozitif hem de negatif sayıları keser.
Aaron D

1

Python 3'te kolay bir geçici çözüm var. Nereden kesmeli Uyarlamayı kolaylaştırmak için decPlace yardım değişkeniyle tanımladım.

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

Çıktı:

f = 1.1234

Umarım yardımcı olur.


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


Güzel ama yuvarlamıyorsun.
Alex

1

Kısa ve kolay varyant

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

Bence çoğu cevap çok karmaşık, buna ne dersiniz?

digits = 2  # Specify how many digits you want

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

>>> 122.48

Basitçe '.' Dizini için tarama yapın. ve istediğiniz gibi kırpın (yuvarlama yok). Dizeyi son adım olarak float'a dönüştürün.

Veya sizin durumunuzda, girdi olarak bir float alırsanız ve çıktı olarak bir dize istiyorsanız:

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

Kesilen sayı küçükse, ondalık ayırıcının sağındaki 0 ​​ile çok fazla kesinlik harcayacağınız için teklifiniz sorunludur. Ancak bu sorun, belirtildiği gibi soruna özgüdür. Söylemeye çalıştığım şey, önemli rakamların gerçek cevap olduğu.
OVERCOIL

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

# bölme ve 10 ile çarpma ** istenen basamak sayısı


0

numpy.round kullanın

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

0

Kitaplık veya diğer harici bağımlılıklar olmadan bir liste anlamaya sığacak kadar basit bir şey. Python> = 3.6 için, f dizeleriyle yazmak çok basittir.

Buradaki fikir, dizgi dönüşümünün ihtiyacınız olandan daha fazla yere yuvarlama yapmasına izin vermek ve ardından son rakamı kesmektir.

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

Tabii ki, orada olan (yani dördüncü basamak için) Buradaki yuvarlama, ancak yuvarlama bir noktada unvoidable olduğunu. Kesme ve yuvarlama arasındaki geçişin alakalı olması durumunda, işte biraz daha iyi bir örnek:

>>> 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: sağdaki sıfırları kaldırmak

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

Burada verilen ana fikir , bana bu soruna en iyi yaklaşım gibi görünüyor. Ne yazık ki, daha fazla oy alan sonraki cevap tamamlanmadığı halde daha az oy aldı (yorumlarda görüldüğü gibi). Umarım aşağıdaki uygulama , kısaltma için kısa ve eksiksiz bir çözüm sağlar .

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

Bu, burada ve burada bulunan tüm köşe vakalarına dikkat etmelidir .


-1

Ben de bir python acemisiyim ve burada bazı ufak tefek parçaları kullandıktan sonra iki sentimi veriyorum

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

str (int (time.time ())), zaman aralığını int olarak alacak ve dizeye dönüştürecek ve sadece mikrosaniyeleri döndüren ... str (datetime.now (). mikrosaniye) [: 3] ile birleştirilecektir. İlk 3 karakter dizmek ve kesmek


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

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

-3

Yazdırırken demek istiyorsanız, aşağıdakiler çalışmalıdır:

print '%.3f' % number

2
Bu sayıyı yuvarlar, kesmez.
David Z
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.