İ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 decimal
modü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.3
Bir 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.29999999999999998
bir Python programında yazarsanız , derleyici onu tamamen aynı değere çevirir . Bir durumda, kısaltılmasını (bir haneye) 0.3
kastettiniz, 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.2
muhtemelen 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. f
Bunun yerine bir biçim belirtimi kullanmanın bir soruna neden olduğu birkaç durum vardır , örneğin 3e-10
28 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 float
yuvarlak 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 + 2
her durumda yeterli olabilir, ancak değilse 2
bunun 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 float
ve 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.