पहला, फ़ंक्शन, उन लोगों के लिए जो सिर्फ कुछ कॉपी-एंड-पेस्ट कोड चाहते हैं:
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]])
यह पायथन 2.7 और 3.1+ में मान्य है। पुराने संस्करणों के लिए, एक ही "बुद्धिमान गोलाई" प्रभाव प्राप्त करना संभव नहीं है (कम से कम, बहुत सारे जटिल कोड के बिना नहीं), लेकिन छंटनी से पहले 12 दशमलव स्थानों पर चक्कर लगाना अधिक समय तक काम करेगा:
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]])
व्याख्या
अंतर्निहित विधि का मूल मूल्य को पूर्ण सटीकता पर एक स्ट्रिंग में परिवर्तित करना है और फिर वर्णों की वांछित संख्या से परे सब कुछ काट देना है। बाद वाला कदम आसान है; यह या तो स्ट्रिंग हेरफेर के साथ किया जा सकता है
i, p, d = s.partition('.')
'.'.join([i, (d+'0'*n)[:n]])
या decimal
मॉड्यूल
str(Decimal(s).quantize(Decimal((0, (1,), -n)), rounding=ROUND_DOWN))
पहला कदम, एक स्ट्रिंग में परिवर्तित करना, काफी मुश्किल है क्योंकि फ़्लोटिंग पॉइंट शाब्दिक के कुछ जोड़े हैं (यानी आप स्रोत कोड में क्या लिखते हैं) जो दोनों एक ही द्विआधारी प्रतिनिधित्व का उत्पादन करते हैं और फिर भी अलग तरह से काट दिया जाना चाहिए। उदाहरण के लिए, 0.3 और 0.29999999999999998 पर विचार करें। यदि आप 0.3
पायथन प्रोग्राम में लिखते हैं , तो संकलक इसे बिट के अनुक्रम में IEEE फ़्लोटिंग-पॉइंट फॉर्मेट का उपयोग करके एनकोड करता है (64-बिट फ्लोट मानते हुए)
0011111111010011001100110011001100110011001100110011001100110011
यह 0.3 का निकटतम मूल्य है जिसे आईईईई फ्लोट के रूप में सटीक रूप से दर्शाया जा सकता है। लेकिन अगर आप 0.29999999999999998
पायथन प्रोग्राम में लिखते हैं , तो कंपाइलर इसका ठीक उसी मूल्य में अनुवाद करता है । एक मामले में, आप इसका मतलब के रूप में (एक अंक के लिए) काट दिया जाना चाहिए 0.3
, जबकि दूसरे मामले में आप इसे के रूप में छोटा किया जाना था 0.2
, लेकिन पायथन केवल एक ही जवाब दे सकता है। यह पायथन की एक मौलिक सीमा है, या वास्तव में आलसी मूल्यांकन के बिना किसी भी प्रोग्रामिंग भाषा। ट्रंकेशन फ़ंक्शन में केवल कंप्यूटर की मेमोरी में संग्रहीत बाइनरी मूल्य तक पहुंच होती है, न कि स्ट्रिंग जिसे आपने वास्तव में स्रोत कोड में टाइप किया था। 1
यदि आप बिट्स के अनुक्रम को एक दशमलव संख्या में वापस करते हैं, तो फिर से IEEE 64-बिट फ़्लोटिंग-पॉइंट प्रारूप का उपयोग करके, आपको मिलता है
0.2999999999999999888977697537484345957637...
इतनी भोली कार्यान्वयन के साथ आएंगे 0.2
, हालांकि यह संभव नहीं है कि आप क्या चाहते हैं। फ्लोटिंग-पॉइंट प्रतिनिधित्व त्रुटि पर अधिक के लिए, पायथन ट्यूटोरियल देखें ।
यह एक फ़्लोटिंग-पॉइंट मान के साथ काम करने के लिए बहुत दुर्लभ है जो एक गोल संख्या के करीब है और फिर भी जानबूझकर उस गोल संख्या के बराबर नहीं है। इसलिए जब काट-छाँट की जाती है, तो संभवत: यह समझ में आता है कि "निकस्ट" दशमलव प्रतिनिधित्व को सभी में से चुनें जो स्मृति में मूल्य के अनुरूप हो। पायथन 2.7 और ऊपर (लेकिन 3.0 नहीं) में बस करने के लिए एक परिष्कृत एल्गोरिथ्म शामिल है , जिसे हम डिफ़ॉल्ट स्ट्रिंग स्वरूपण ऑपरेशन के माध्यम से एक्सेस कर सकते हैं।
'{}'.format(f)
एकमात्र चेतावनी यह है कि यह g
प्रारूप विनिर्देश की तरह काम करता है, इस अर्थ में कि यह घातीय संकेतन ( 1.23e+4
) का उपयोग करता है यदि संख्या बड़ी या छोटी है। तो विधि को इस मामले को पकड़ना होगा और इसे अलग तरीके से संभालना होगा। ऐसे कुछ मामले हैं जहां f
प्रारूप विनिर्देश का उपयोग करने के बजाय एक समस्या पैदा होती है, जैसे कि 3e-10
परिशुद्धता के 28 अंकों को कम करने की कोशिश करना (यह पैदा करता है 0.0000000002999999999999999980
), और मुझे अभी तक यकीन नहीं है कि उन को कैसे संभालना सबसे अच्छा है।
यदि आप वास्तव में ऐसेfloat
s के साथ काम कर रहे हैं जो गोल संख्याओं के बहुत करीब हैं, लेकिन जानबूझकर उनके बराबर नहीं हैं (जैसे 0.29999999999999998 या 99.9599999999994), तो यह कुछ गलत सकारात्मक का उत्पादन करेगा, अर्थात यह उन गोल संख्याओं का निर्माण कर सकता है जिन्हें आप गोल नहीं करना चाहते थे। उस मामले में समाधान एक निश्चित परिशुद्धता निर्दिष्ट करना है।
'{0:.{1}f}'.format(f, sys.float_info.dig + n + 2)
यहां उपयोग करने के लिए सटीक अंकों की संख्या वास्तव में मायने नहीं रखती है, यह केवल यह सुनिश्चित करने के लिए पर्याप्त होना चाहिए कि स्ट्रिंग रूपांतरण में किए गए किसी भी गोलाई को इसके अच्छे दशमलव प्रतिनिधित्व के लिए "टक्कर" नहीं होती है। मुझे लगता है कि sys.float_info.dig + n + 2
सभी मामलों में पर्याप्त हो सकता है, लेकिन यदि ऐसा नहीं 2
किया जा सकता है, तो इसे बढ़ाना पड़ सकता है, और ऐसा करने में दुख नहीं होता।
पायथन के पहले के संस्करणों में (2.6, या 3.0 तक), फ़्लोटिंग पॉइंट संख्या स्वरूपण बहुत अधिक क्रूड था, और नियमित रूप से इस तरह की चीजें
>>> 1.1
1.1000000000000001
यदि यह आपकी स्थिति है, यदि आप छंटनी के लिए "अच्छा" दशमलव अभ्यावेदन का उपयोग करना चाहते हैं , तो आप जो कर सकते हैं (जहां तक मुझे पता है) अंकों की कुछ संख्या चुन सकते हैं, पूर्ण परिशुद्धता से कम float
, और पूर्णांक से इसे रौंदने से पहले कई अंकों की संख्या। एक विशिष्ट पसंद 12 है,
'%.12f' % f
लेकिन आप इसे उन संख्याओं के अनुरूप समायोजित कर सकते हैं जो आप उपयोग कर रहे हैं।
1 अच्छा ... मैंने झूठ बोला। तकनीकी रूप से, आप पायथन को अपने स्वयं के स्रोत कोड को फिर से पार्स करने का निर्देश दे सकते हैं और ट्रंकेशन फ़ंक्शन में जाने वाले पहले तर्क के अनुरूप भाग निकाल सकते हैं। यदि वह तर्क एक फ्लोटिंग-पॉइंट शाब्दिक है, तो आप इसे दशमलव बिंदु के बाद निश्चित संख्या में काट सकते हैं और वापस कर सकते हैं। हालाँकि यह रणनीति काम नहीं करती है यदि तर्क एक चर है, जो इसे काफी बेकार बनाता है। निम्नलिखित केवल मनोरंजन मूल्य के लिए प्रस्तुत किया गया है:
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
इस मामले को संभालने के लिए सामान्य बनाना, जहां आप एक चर में गुजरते हैं, एक खोए हुए कारण की तरह लगता है, क्योंकि आपको प्रोग्राम के निष्पादन के माध्यम से पीछे की ओर ट्रेस करना होगा जब तक कि आपको फ़्लोटिंग-पॉइंट शाब्दिक नहीं मिल जाता है जिसने चर को अपना मान दिया। अगर एक भी है। अधिकांश चर को उपयोगकर्ता इनपुट या गणितीय अभिव्यक्तियों से आरम्भ किया जाएगा, जिस स्थिति में बाइनरी प्रतिनिधित्व है।