2025, Oct 07 01:31

float से Decimal: शून्य के पास राउंडिंग के लिए Emin और context सेटिंग्स

Python Decimal में create_decimal_from_float के साथ शून्य के पास राउंडिंग क्यों 1e-18 देता, और context.prec, Emin=0 से इसे सही तरह कैसे नियंत्रित करें.

फ्लोट्स को Decimal में बदलते समय, यह मान लेना आसान है कि संदर्भ की परिशुद्धता वैल्यूज़ को ठीक वैसे ही राउंड कर देगी, जैसा आप उम्मीद करते हैं। create_decimal_from_float() के साथ यह ज़्यादातर सही रहता है — जब तक कि आप शून्य के बहुत क़रीब संख्याओं पर नहीं पहुँचते। तब, शून्य पर राउंड होने की जगह मान वैज्ञानिक संकेतन में एक बेहद छोटा, शून्य से भिन्न परिमाण बनकर दिख सकता है। अगर आपने कभी sin(180°) जैसी किसी चीज़ को Decimal में बदलने की कोशिश की हो और 1e-18 जैसा शेष देखकर चौंके हों, तो यही पैटर्न आपने देखा था (Python 3.10)।

समस्या का डेमो

परिशुद्धता छह अंकों पर सेट है। एक सामान्य मान उम्मीद के मुताबिक राउंड हो जाता है, लेकिन शून्य के पास का मान 0 पर नहीं सिमटता:

>>> import decimal
>>> policy = decimal.getcontext()
>>> policy.prec = 6
>>> policy.create_decimal_from_float(0.123456789123456789)
Decimal('0.123457')
>>> policy.create_decimal_from_float(0.000000000000000001)
Decimal('1.00000E-18')

वास्तव में हो क्या रहा है

साधारण कंस्ट्रक्टर मार्ग Decimal(float_value) के लिए, प्रलेखन स्पष्ट कहता है:

नए Decimal का महत्व केवल इनपुट किए गए अंकों की संख्या से तय होता है। संदर्भ की परिशुद्धता और राउंडिंग केवल अंकगणितीय संचालन के दौरान प्रभावी होती हैं।

create_decimal_from_float() अलग है। यह निर्माण के समय सक्रिय decimal संदर्भ का उपयोग कर परिशुद्धता और राउंडिंग लागू करता है। लेकिन एक अहम बात है: परिशुद्धता लागू होने से पहले घातांक को ध्यान में लिया जाता है। इसलिए शून्य के पास वाले मान पहले नकारात्मक घातांक पाते हैं और उसके बाद ही अंक राउंड होते हैं। इसी वजह से बेहद छोटा इनपुट मौजूदा परिशुद्धता में 0 पर घटने के बजाय 1.00000E-18 जैसा दिख सकता है।

चार-अंकीय परिशुद्धता के साथ एक छोटा उदाहरण:

>>> import decimal
>>> rules = decimal.getcontext()
>>> rules.prec = 4
>>> rules.create_decimal_from_float(0.000000000123456789)
Decimal('1.235E-10')

समाधान: घातांक की सीमा बाँधें (Emin)

यदि आपका उद्देश्य शून्य के पास जाने वाले उन सभी घातांकों को राउंड कर देना और प्रतिनिधित्व को मौजूदा परिशुद्धता पर सीमित करना है, तो संदर्भ में घातांक की सीमा बाँधें। खास तौर पर, Emin को 0 पर सेट करें ताकि घातांकी संकेतन का उपयोग न हो:

>>> import decimal
>>> conf = decimal.getcontext()
>>> conf.prec = 4
>>> conf.Emin = 0
>>> conf.create_decimal_from_float(0.000000000123456789)
Decimal('0.000')

Emin को 0 करने से निर्माण चरण सूक्ष्म परिमाणों को नकारात्मक घातांक के साथ दर्शाने से रुक जाता है; फिर मान दी गई परिशुद्धता पर राउंड होकर शून्य में सिमट जाता है।

यह क्यों मायने रखता है

वित्तीय या वैज्ञानिक कोड में संख्यात्मक व्यवहार सख़्त करने के लिए बाइनरी float और Decimal के बीच आना-जाना आम है। शून्य के क़रीबी यह किनारी मामला राउंडिंग के बारे में आपकी अपेक्षाओं को चुपचाप बिगाड़ सकता है, जहाँ आप बिल्कुल शून्य चाहते थे वहाँ सूक्ष्म, शून्य से भिन्न मान छोड़कर। यह समझना कि decimal संदर्भ परिशुद्धता से पहले घातांक को कैसे संभालता है — और Emin से उसे कैसे नियंत्रित किया जा सकता है — सुनिश्चित करता है कि आपका रूपांतरण स्तर आपकी राउंडिंग नीति के अनुरूप रहे।

निष्कर्ष

किसी संदर्भ के भीतर float से Decimal बनाते समय केवल परिशुद्धता यह गारंटी नहीं देती कि सूक्ष्म मान शून्य बन जाएँगे, क्योंकि पहले घातांक संसाधित होता है। यदि आप चाहते हैं कि मौजूदा परिशुद्धता में शून्य के क़रीब इनपुट राउंड होकर विलीन हो जाएँ, तो create_decimal_from_float() को कॉल करने से पहले context.Emin = 0 सेट करें। इससे छोटे परिमाणों के लिए वैज्ञानिक संकेतन से बचाव होता है और परिणाम आपकी इच्छित राउंडिंग के अनुरूप आता है।

यह लेख StackOverflow पर एक प्रश्न (लेखक: Qwerty-uiop) और jsbueno के उत्तर पर आधारित है।