पायथन 3 में 4 * 0.1 का फ्लोटिंग-पॉइंट वैल्यू अच्छा क्यों लगता है लेकिन 3 * 0.1 नहीं है?


158

मुझे पता है कि अधिकांश दशमलव में एक सटीक फ़्लोटिंग पॉइंट प्रतिनिधित्व नहीं है ( क्या फ़्लोटिंग पॉइंट गणित टूट गया है? )।

लेकिन मैं नहीं देखता कि क्यों 4*0.1अच्छी तरह से मुद्रित किया जाता है 0.4, लेकिन 3*0.1ऐसा नहीं है, जब दोनों मूल्यों में वास्तव में बदसूरत दशमलव अभ्यावेदन हैं:

>>> 3*0.1
0.30000000000000004
>>> 4*0.1
0.4
>>> from decimal import Decimal
>>> Decimal(3*0.1)
Decimal('0.3000000000000000444089209850062616169452667236328125')
>>> Decimal(4*0.1)
Decimal('0.40000000000000002220446049250313080847263336181640625')

7
क्योंकि कुछ नंबरों का सही प्रतिनिधित्व किया जा सकता है, और कुछ नहीं।
मॉर्गन थ्रैप

58
@ मॉर्गनट्रैप: नहीं, यह नहीं है। ओपी बल्कि मनमाने ढंग से दिखने वाले स्वरूपण विकल्प के बारे में पूछ रहा है। बाइनरी फ्लोटिंग पॉइंट में न तो 0.3 और न ही 0.4 का प्रतिनिधित्व किया जा सकता है।
बथशेबा

42
@BartoszKP: करने के बाद दस्तावेज़ कई बार पढ़ा है, यह स्पष्ट नहीं होता क्यों अजगर प्रदर्शित कर रहा है 0.3000000000000000444089209850062616169452667236328125के रूप में 0.30000000000000004और 0.40000000000000002220446049250313080847263336181640625के रूप में .4भले ही वे एक ही सटीकता की और होता है, और इस तरह सवाल का जवाब नहीं है।
मूविंग डक

6
यह भी देखें stackoverflow.com/questions/28935257/... - मैं कुछ चिढ़ रहा है कि उसे डुप्लिकेट को बंद कर दिया गया, लेकिन यह एक नहीं है।
रैंडम 832

12
फिर से खोल दिया, कृपया इसे बंद न करें क्योंकि "एक अस्थायी बिंदु गणित टूट गया है"
अंती हापाला २३'१६

जवाबों:


301

सरल उत्तर इसलिए है क्योंकि 3*0.1 != 0.3परिमाणीकरण (राउंडऑफ़) त्रुटि के 4*0.1 == 0.4कारण (जबकि दो की शक्ति से गुणा करने पर आमतौर पर "सटीक" ऑपरेशन होता है)।

आप उपयोग कर सकते हैं .hexएक नंबर (मूल रूप से, के आंतरिक प्रतिनिधित्व देखने पर पायथन में विधि सटीक द्विआधारी चल बिन्दु मूल्य के बजाय आधार -10 अनुमान)। यह समझाने में मदद कर सकता है कि हुड के नीचे क्या चल रहा है।

>>> (0.1).hex()
'0x1.999999999999ap-4'
>>> (0.3).hex()
'0x1.3333333333333p-2'
>>> (0.1*3).hex()
'0x1.3333333333334p-2'
>>> (0.4).hex()
'0x1.999999999999ap-2'
>>> (0.1*4).hex()
'0x1.999999999999ap-2'

0.1 0x1.999999999999a समय 2 ^ -4 है। अंत में "a" का अर्थ है अंक 10 - दूसरे शब्दों में, बाइनरी फ्लोटिंग पॉइंट में 0.1 0.1 के "सटीक" मान की तुलना में बहुत बड़ा है (क्योंकि अंतिम 0x0.99 0x0.a तक गोल है)। जब आप इसे 4 से गुणा करते हैं, तो दो की शक्ति, घातांक 2 (-4 से 2 ^ -2 तक) बढ़ जाती है, लेकिन संख्या अन्यथा अपरिवर्तित है, इसलिए 4*0.1 == 0.4

हालाँकि, जब आप 3 से गुणा करते हैं, तो 0x0.99 और 0x0.a0 (0x0.07) के बीच का छोटा सा अंतर 0x0.15 त्रुटि में बढ़ जाता है, जो अंतिम स्थिति में एक-अंकों की त्रुटि के रूप में दिखाई देता है। यह 0.3 के गोल मूल्य की तुलना में 0.1 * 3 बहुत थोड़ा बड़ा होता है।

पायथन 3 के फ्लोट reprको गोल-तिगुना होने के लिए डिज़ाइन किया गया है , अर्थात , दिखाया गया मूल्य मूल मूल्य में बिल्कुल परिवर्तनीय होना चाहिए। इसलिए, यह प्रदर्शित नहीं कर सकता है 0.3और 0.1*3ठीक उसी तरह, या दो अलग-अलग संख्याएं राउंड-ट्रिपिंग के बाद एक ही समाप्त हो जाएंगी। नतीजतन, पायथन 3 का reprइंजन थोड़ी स्पष्ट त्रुटि के साथ एक को प्रदर्शित करने का विकल्प चुनता है।


25
यह एक आश्चर्यजनक व्यापक उत्तर है, धन्यवाद। (विशेष रूप से, दिखाने के लिए धन्यवाद .hex(); मुझे नहीं पता था कि यह अस्तित्व में है।)
एनपीई

21
@ सुपरकैट: पायथन सबसे छोटी स्ट्रिंग खोजने की कोशिश करता है जो वांछित मान के साथ घूमती है , जो कुछ भी होता है। स्पष्ट रूप से मूल्यांकन किया गया मान 0.5ulp के भीतर होना चाहिए (या यह किसी और चीज़ के लिए गोल होगा), लेकिन इसे अस्पष्ट मामलों में अधिक अंकों की आवश्यकता हो सकती है। कोड बहुत स्पष्ट रूप से है , लेकिन अगर आप एक झांकना चाहते हैं: hg.python.org/cpython/file/03f2c8fc24ea/Python/dtoa.c#l2345
nnonneo

2
@ सुपरफ़ास्ट: हमेशा सबसे छोटा स्ट्रिंग जो 0.5 एलपीपी के भीतर होता है। ( सख्ती से अगर हम विषम एलएसबी के साथ एक फ्लोट देख रहे हैं; यानी, सबसे छोटा तार जो इसे गोल-टाई-टू-सम के साथ काम करता है)। इसके लिए कोई भी अपवाद एक बग है, और रिपोर्ट किया जाना चाहिए।
मार्क डिकिंसन

7
@MarkRansom निश्चित रूप से उन्होंने इसके अलावा कुछ और उपयोग किया eक्योंकि यह पहले से ही एक हेक्स अंक है। हो सकता है कि प्रतिपादक के बजाय शक्ति केp लिए ।
बरगी

11
@ बर्गी: pइस संदर्भ का उपयोग C99 में वापस (कम से कम) हो जाता है, और IEEE 754 में और विभिन्न अन्य भाषाओं (जावा सहित) में भी दिखाई देता है। जब float.hexऔर float.fromhexमेरे द्वारा लागू किया गया था :-), पायथन केवल उस समय की प्रचलित प्रथा की नकल कर रहा था। मुझे नहीं पता कि "पावर" के लिए इरादा 'पी' था, लेकिन यह इसके बारे में सोचने का एक अच्छा तरीका है।
मार्क डिकिंसन

75

repr(और strपायथन 3 में) मान को असंदिग्ध बनाने के लिए आवश्यक जितने अंकों को रखा जाएगा। इस मामले में गुणन 3*0.1का परिणाम 0.3 (0x1.3333333333333p-2 हेक्स में) का निकटतम मूल्य नहीं है, यह वास्तव में एक LSB उच्च (0x1.3333333333334p-2) है, क्योंकि इसे 0.3 से अलग करने के लिए अधिक अंकों की आवश्यकता है।

दूसरी ओर, गुणा 4*0.1 करता है , 0.4 (0x1.999999999999ap -2 हेक्स में) के लिए निकटतम मूल्य प्राप्त तो यह किसी भी अतिरिक्त अंक की जरूरत नहीं है।

आप इसे आसानी से सत्यापित कर सकते हैं:

>>> 3*0.1 == 0.3
False
>>> 4*0.1 == 0.4
True

मैंने ऊपर हेक्स नोटेशन का उपयोग किया क्योंकि यह अच्छा और कॉम्पैक्ट है और दोनों मूल्यों के बीच थोड़ा अंतर दिखाता है। आप अपने आप से यह कर सकते हैं जैसे (3*0.1).hex()। यदि आप उन्हें उनके सभी दशमलव महिमा में देखते हैं, तो आप यहाँ जाते हैं:

>>> Decimal(3*0.1)
Decimal('0.3000000000000000444089209850062616169452667236328125')
>>> Decimal(0.3)
Decimal('0.299999999999999988897769753748434595763683319091796875')
>>> Decimal(4*0.1)
Decimal('0.40000000000000002220446049250313080847263336181640625')
>>> Decimal(0.4)
Decimal('0.40000000000000002220446049250313080847263336181640625')

2
(+1) अच्छा जवाब, धन्यवाद। क्या आपको लगता है यह का परिणाम शामिल करके "नहीं निकटतम मूल्य" बिंदु illustrating के लायक हो सकता है 3*0.1 == 0.3और 4*0.1 == 0.4?
NPE

@ NPE मुझे गेट के ठीक बाहर होना चाहिए था, सुझाव के लिए धन्यवाद।
मार्क रंसोम

मुझे आश्चर्य है कि यह 0.1, 0.3 और 0.4 के निकटतम "डबल्स" के सटीक दशमलव मानों को ध्यान देने योग्य होगा, क्योंकि बहुत से लोग फ्लोटिंग-पॉइंट हेक्स नहीं पढ़ सकते हैं।
15 अक्टूबर को सुपरकैट

@ सुपरकैट आप एक अच्छा बिंदु बनाते हैं। उन सुपर बड़े डबल्स को टेक्स्ट में डालना विचलित करने वाला होगा, लेकिन मैंने उन्हें जोड़ने का तरीका सोचा।
रैनसम

25

यहां अन्य उत्तरों से एक सरलीकृत निष्कर्ष निकाला गया है।

यदि आप पायथन की कमांड लाइन पर एक फ्लोट की जांच करते हैं या इसे प्रिंट करते हैं, तो यह फ़ंक्शन से गुजरता है reprजो इसकी स्ट्रिंग प्रतिनिधित्व बनाता है।

संस्करण 3.2 के साथ शुरू, अजगर है strऔरrepr एक जटिल गोलाई योजना का उपयोग करता है, जो यदि संभव हो तो अच्छा दिखने वाले दशमलव को पसंद करता है, लेकिन अधिक अंकों का उपयोग करता है जहां तैरने वाले और उनके स्ट्रिंग दिशाओं के बीच bijective (वन-टू-वन) मैपिंग की गारंटी के लिए आवश्यक है।

यह योजना इस बात की गारंटी देती है कि repr(float(s))साधारण दशमलव के लिए अच्छा लग रहा है, भले ही वे फ्लोट के रूप में सटीक रूप से प्रतिनिधित्व नहीं कर सकते हैं (उदाहरण के लिए, जब)s = "0.1")

एक ही समय में यह गारंटी देता है कि float(repr(x)) == xप्रत्येक फ्लोट के लिए हैx


2
आपका उत्तर पायथन संस्करणों> = 3.2 के लिए सटीक है, जहां फ़्लोट के लिए समान strऔर reprसमान हैं। पायथन 2.7 के reprलिए, आपके द्वारा पहचाने जाने वाले गुण हैं, लेकिन strबहुत सरल है - यह केवल 12 महत्वपूर्ण अंकों की गणना करता है और उन पर आधारित आउटपुट स्ट्रिंग पैदा करता है। अजगर <= 2.6 के लिए, दोनों reprऔर strमहत्वपूर्ण अंक की एक निश्चित संख्या (के लिए 17 के आधार पर कर रहे हैं repr, 12 के लिए str)। (और कोई भी अजगर 3.0 या अजगर 3.1 :-) के बारे में परवाह नहीं करता है
मार्क डिकिन्सन

धन्यवाद @MarkDickinson! मैंने जवाब में आपकी टिप्पणी शामिल की।
आइवर

2
ध्यान दें कि शेल से गोलाई reprइस प्रकार से आती है कि पायथन 2.7 व्यवहार समान होगा ...
एंटिटी हवाला

5

पायथन के कार्यान्वयन के लिए वास्तव में विशिष्ट नहीं है, लेकिन दशमलव स्ट्रिंग फ़ंक्शन के लिए किसी भी फ्लोट पर लागू होना चाहिए।

एक फ्लोटिंग पॉइंट नंबर अनिवार्य रूप से एक बाइनरी नंबर है, लेकिन महत्वपूर्ण आंकड़ों की एक निश्चित सीमा के साथ वैज्ञानिक अंकन में।

किसी भी संख्या का व्युत्क्रम एक प्रमुख संख्या कारक है जिसे आधार के साथ साझा नहीं किया जाता है, हमेशा एक आवर्ती बिंदु बिंदु प्रतिनिधित्व होता है। उदाहरण के लिए 1/7 में एक प्रमुख कारक है, 7, जिसे 10 के साथ साझा नहीं किया गया है, और इसलिए एक आवर्ती दशमलव प्रतिनिधित्व है, और वही 1/10 के लिए सही है जिसमें प्रमुख कारक 2 और 5 हैं, बाद वाला 2 के साथ साझा नहीं किया जा रहा है ; इसका मतलब यह है कि 0.1 बिंदु बिंदु के बाद बिट्स की एक परिमित संख्या द्वारा बिल्कुल प्रतिनिधित्व नहीं किया जा सकता है।

चूंकि 0.1 में कोई सटीक प्रतिनिधित्व नहीं है, एक फ़ंक्शन जो एक दशमलव बिंदु स्ट्रिंग के लिए सन्निकटन को परिवर्तित करता है, आमतौर पर कुछ मानों को अनुमानित करने की कोशिश करेगा ताकि उन्हें 0.1000000000004121 जैसे अनपेक्षित परिणाम न मिलें।

चूंकि फ्लोटिंग पॉइंट वैज्ञानिक संकेतन में है, इसलिए आधार की शक्ति से कोई गुणन केवल संख्या के घातांक भाग को प्रभावित करता है। उदाहरण के लिए 1.231e + 2 * 100 = 1.231e + 4 दशमलव संकेतन के लिए, और इसी तरह, 1.00101010e11 * 100 = 1.00101010e101 बाइनरी नोटेशन में। यदि मैं आधार की गैर-शक्ति से गुणा करता हूं, तो महत्वपूर्ण अंक भी प्रभावित होंगे। उदाहरण के लिए 1.2e1 * 3 = 3.6e1

उपयोग किए गए एल्गोरिदम के आधार पर, यह केवल महत्वपूर्ण आंकड़ों के आधार पर आम दशमलव का अनुमान लगाने की कोशिश कर सकता है। बाइनरी में 0.1 और 0.4 दोनों के समान आंकड़े हैं, क्योंकि उनकी फ़्लोट्स अनिवार्य रूप से (8/5) (2 ^ -4) और (8/5) के ट्रंकेशन हैं क्रमशः (2 ^ -6) के हैं। यदि एल्गोरिथ्म 8/5 सिगफिग पैटर्न को दशमलव 1.6 के रूप में पहचानता है, तो यह 0.1, 0.2, 0.4, 0.8 आदि पर काम करेगा। इसमें अन्य संयोजनों के लिए जादू सिगफिग पैटर्न भी हो सकते हैं, जैसे कि फ्लोट 3 को फ्लोट 10 से विभाजित किया गया है। और अन्य जादू पैटर्न सांख्यिकीय रूप से 10 तक विभाजन द्वारा गठित होने की संभावना है।

3 * 0.1 के मामले में, अंतिम कुछ महत्वपूर्ण आंकड़े संभवतः फ्लोट 3 को फ्लोट 10 से विभाजित करने से अलग होंगे, जिससे एल्गोरिथ्म सटीक नुकसान के लिए अपनी सहनशीलता के आधार पर 0.3 स्थिर के लिए जादू की संख्या को पहचानने में विफल हो जाएगा।

संपादित करें: https://docs.python.org/3.1/tutorial/floatingpoint.html

दिलचस्प है, कई अलग-अलग दशमलव संख्याएं हैं जो समान निकटतम बाइनरी अंश को साझा करते हैं। उदाहरण के लिए, संख्याएँ 0.1 और 0.10000000000000001 और 0.1000000000000000055511151231257827021181583404541015625, सभी 3602879701896397/2/55 द्वारा सन्निकट हैं। चूंकि ये सभी दशमलव मान एक ही सन्निकटन को साझा करते हैं, उनमें से किसी एक को भी प्रदर्शित किया जा सकता है, जबकि अभी भी इनवॉइस विकसित नहीं कर रहा है। ) == एक्स।

सटीक नुकसान के लिए कोई सहिष्णुता नहीं है, अगर फ्लोट x (0.3) फ्लोट y (0.1 * 3) के बराबर नहीं है, तो repr (x) repr (y) के बराबर नहीं है।


4
यह वास्तव में मौजूदा उत्तरों में बहुत कुछ नहीं जोड़ता है।
एंटटी हापाला २३'१६

1
"उपयोग किए गए एल्गोरिदम के आधार पर, यह केवल महत्वपूर्ण आंकड़ों के आधार पर आम दशमलव का अनुमान लगाने की कोशिश कर सकता है।" <- यह शुद्ध अटकलों की तरह लगता है। अन्य उत्तर में वर्णित है कि पायथन वास्तव में क्या करता है।
मार्क डिकिंसन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.