Computer >> कंप्यूटर ट्यूटोरियल >  >> प्रोग्रामिंग >> Ruby

रूबी में सटीक मुद्रा गणना: फ्लोटिंग-पॉइंट नुकसान से बचें

पैसा, चाहे वह किसी भी मुद्रा में हो, एक फ्लोटिंग-पॉइंट नंबर की तरह लगता है। लेकिन मुद्रा के लिए फ्लोट्स का उपयोग करना एक गलती है।

फ्लोट नंबर (इसलिए, फ्लोट ऑब्जेक्ट्स), परिभाषा के अनुसार, अनएक्सैक्ट रियल नंबर हैं जो नेटिव आर्किटेक्चर की विशेषता डबल-प्रिसिजन फ्लोटिंग-पॉइंट प्रतिनिधित्व का उपयोग करते हैं। अनएक्सैक्ट नंबर एकाउंटेंट्स को नाखुश करते हैं।

इस लेख में, आप कुछ त्वरित उदाहरणों के माध्यम से निर्देशित होंगे जो रूबी और रेल्स में मनी डेटा से निपटने के लिए उपलब्ध विकल्पों को संबोधित करने में आपकी सहायता करेंगे।

फ्लोट क्या है?

जैसा कि हमने कहा, float ऑब्जेक्ट्स में अलग अंकगणित होता है। यही मुख्य कारण है कि वे अनएक्सैक्ट नंबर हैं, विशेष रूप से क्योंकि रूबी (अधिकांश भाषाओं की तरह) फ्लोट्स को दर्शाने के लिए बाइनरी अंकों की एक निश्चित संख्या का उपयोग करता है। दूसरे शब्दों में, रूबी फ्लोटिंग नंबरों को दशमलव से बाइनरी में और इसके विपरीत परिवर्तित करता है।

जब आप रियल नंबरों (उर्फ दशमलव नंबरों) के बाइनरी प्रतिनिधित्व में गहराई से गोता लगाते हैं, तो उनमें से कुछ को बिल्कुल दर्शाया नहीं जा सकता, इसलिए सिस्टम के लिए एकमात्र शेष विकल्प उन्हें राउंड ऑफ करना है।

यदि आप आगे सोचते हैं और सामान्य गणित संरचनाओं पर विचार करते हैं, जैसे आवर्ती दशमलव, तो आप समझ सकते हैं कि वे एक निश्चित संख्या के भीतर पूरी तरह से दर्शाए नहीं जा सकते क्योंकि पाई नंबर, उदाहरण के लिए, अनंत है। फ्लोट्स आमतौर पर 32 या 64 बिट्स की प्रिसिजन तक धारण कर सकते हैं, जिसका अर्थ है कि संख्या सीमा तक पहुंचने पर काट दी जाएगी।

आइए एक क्लासिक उदाहरण का विश्लेषण करें:

1200 * (14.0/100) 

यह एक संख्या का प्रतिशत निकालने का एक सीधा तरीका है। 1200 का चौदह प्रतिशत 168 होना चाहिए; हालांकि, रूबी के भीतर इस निष्पादन का परिणाम होगा

1200 * (14.0/100) => 168.00000000000003 

हालांकि, यदि आप फॉर्मूला में सिर्फ 0.1% जोड़ते हैं, तो आपको कुछ अलग मिलता है:

1200 * (14.1/100) => 169.2 

वैकल्पिक रूप से, आप वांछित दशमलव स्थानों को परिभाषित करते हुए मान को निकटतम संभव मान पर round कर सकते हैं:

(my_calculation).round(2) 

वास्तव में, यह अधिक जटिल गणनाओं के साथ गारंटीकृत नहीं है, विशेष रूप से यदि आप इन मूल्यों की तुलना करते हैं।

यदि आप इसके पीछे के वास्तविक विज्ञान को समझने में रुचि रखते हैं, तो मैं Oracle के परिशिष्ट को पढ़ने की अत्यधिक अनुशंसा करता हूं: What Every Computer Scientist Should Know About Floating-Point Arithmetic। यह विस्तार से बताता है कि फ्लोट नंबरों की गलत प्रकृति के पीछे क्या कारण हैं।

भरोसेमंद BigDecimal

निम्नलिखित कोड स्निपेट पर विचार करें:

require "bigdecimal" 
BigDecimal("45.99") 

यह कोड आसानी से एक ईकॉमर्स कार्ट की राशि को अपनाने वाले एक वास्तविक तर्क का प्रतिनिधित्व कर सकता है। अंत में, हेरफेर किया जा रहा वास्तविक मूल्य हमेशा 45.99 होगा बजाय 45.9989 या 45.99000009 के, उदाहरण के लिए।

यह BigDecimal की सटीक प्रकृति है। सामान्य अंकगणितीय गणनाओं के लिए, float उसी तरह प्रदर्शन करेगा; हालांकि, यह अप्रत्याशित है, जो इसका उपयोग करने का खतरा है।

जब इसे BigDecimal के साथ चलाया जाता है, तो पिछले अनुभाग में हमने जो प्रतिशत गणना की थी, उसका परिणाम होता है

require "bigdecimal" 
(BigDecimal(1200) * (BigDecimal(14)/BigDecimal(100))).to_s("F") 
=> 168.0 

यह केवल एक संक्षिप्त संस्करण है जो irb कंसोल में तेजी से निष्पादन की अनुमति देता है।

मूल रूप से, जब आप सीधे BigDecimal ऑब्जेक्ट को प्रिंट करते हैं, तो आपको इसका साइंटिफिक नोटेशन मिलेगा, जो हम यहां नहीं चाहते हैं। to_s मेथड दिए गए तर्क को फॉर्मेटिंग सेटिंग्स के कारण प्राप्त करता है और BigDecimal के समकक्ष फ्लोटिंग मूल्य को प्रदर्शित करता है। इस विषय पर अधिक जानकारी के लिए, रूबी डॉक्स देखें।

यदि आपको दशमलव स्थानों के लिए एक सीमा निर्धारित करने की आवश्यकता है, तो इसमें truncate मेथड है, जो आपके लिए काम करेगा:

(BigDecimal(1200) * (BigDecimal("14.12")/BigDecimal(100))).truncate(2).to_s("F") 
=> 169.44 

RubyMoney प्रोजेक्ट

RubyMoney को इन समस्याओं के बारे में सोचने के बाद बनाया गया था। यह रूबी डेवलपर्स का एक ओपन-सोर्स समुदाय है जिसका उद्देश्य रूबी पारिस्थितिकी तंत्र में मनी डेटा में हेरफेर करने के लिए बेहतरीन लाइब्रेरी प्रदान करके डेवलपर्स के जीवन को सुविधाजनक बनाना है।

प्रोजेक्ट सात लाइब्रेरी से बना है, जिनमें से तीन महत्व में सबसे अलग हैं:

  • Money: मनी और मुद्रा रूपांतरण से निपटने के लिए एक रूबी लाइब्रेरी। यह मनी को मजबूत और आधुनिक एप्लिकेशन में संभालने के लिए कई ऑब्जेक्ट-ओरिएंटेड विकल्प प्रदान करता है, चाहे वे वेब के लिए हों या नहीं।
  • Money-rails: रूबी ऑन रेल्स के लिए RubyMoney का एकीकरण, money लाइब्रेरी की सभी शक्ति को रेल्स की लचीलापन के साथ मिलाना।
  • Monetize: विभिन्न ऑब्जेक्ट्स को money ऑब्जेक्ट्स में बदलने के लिए एक लाइब्रेरी। यह उन एप्लिकेशन के लिए एक सहायक लाइब्रेरी की तरह अधिक काम करता है जो बहुत सारे स्ट्रिंग पार्सिंग से निपटते हैं, उदाहरण के लिए।

प्रोजेक्ट में चार अन्य दिलचस्प लाइब्रेरी हैं:

  • EU_central_bank: एक लाइब्रेरी जो यूरोपीय सेंट्रल बैंक से प्रकाशित दरों का उपयोग करके विनिमय दरों की गणना करने में मदद करती है।
  • Google_currency: Google मुद्रा दरों को संदर्भ के रूप में उपयोग करके मुद्रा रूपांतरण के लिए एक दिलचस्प लाइब्रेरी।
  • Money-collection: money ऑब्जेक्ट्स के योग/न्यूनतम/अधिकतम की सटीक गणना के लिए एक सहायक लाइब्रेरी।
  • Money-heuristics: मनी जेम के लिए स्ट्रिंग इनपुट के यूरिस्टिक विश्लेषण के लिए एक मॉड्यूल।

"Money" जेम

आइए सबसे प्रसिद्ध से शुरू करें: मनी जेम। इसकी मुख्य विशेषताओं में निम्नलिखित हैं:

  • एक money क्लास जो प्रासंगिक मौद्रिक जानकारी रखती है, जैसे मूल्य, मुद्रा, और दशमलव चिह्न।
  • Money::Currency नामक एक अन्य क्लास जो डेवलपर द्वारा उपयोग की जा रही मौद्रिक इकाई के बारे में जानकारी को लपेटती है।
  • डिफ़ॉल्ट रूप से, यह फ्लोटिंग-पॉइंट नंबर के बजाय इंटीजर के साथ काम करता है ताकि उपरोक्त त्रुटियों से बचा जा सके।
  • एक मुद्रा से दूसरी मुद्रा में मनी का आदान-प्रदान करने की क्षमता, जो सुपर कूल है।

उसके अलावा, हमें सुसंगत और ऑब्जेक्ट-ओरिएंटेड संरचनाओं द्वारा प्रदान की जाने वाली उच्च लचीलापन भी मिलता है जो आपके प्रोजेक्ट्स में किसी अन्य मॉडल की तरह ही मनी डेटा में हेरफेर करते हैं।

इसका उपयोग बहुत सरल है, बस उचित जेम इंस्टॉल करें:

gem install money 

एक त्वरित उदाहरण जिसमें एक निश्चित राशि शामिल है:

my_money = Money.new(1200, "USD") 
my_money.cents #=> 1200 
my_money.currency #=> Currency.new("USD") 

जैसा कि आप देख सकते हैं, मनी को सेंट के आधार पर दर्शाया जाता है। बारह सौ सेंट 12 डॉलर के बराबर है।

जैसे आपने BigDecimal के साथ किया था, आप इन ऑब्जेक्ट्स के साथ कुछ बुनियादी गणित भी कर सकते हैं। उदाहरण के लिए,

cart_amount = Money.new(10000, "USD") #=> 100 USD 
discount = Money.new(1000, "USD") #=> 10 USD 
cart_amount - discount == Money.new(9000, "USD") #=> 90 USD 

दिलचस्प, है ना? यह उन ऑब्जेक्ट्स की प्रकृति है जिनका हमने उल्लेख किया था। कोडिंग करते समय, यह वास्तव में ऐसा लगता है जैसे आप अनएक्सप्रेसिव और सामान्य नंबरों के बजाय मौद्रिक मूल्यों में हेरफेर कर रहे हैं।

मुद्रा रूपांतरण

यदि आपके पास अपना खुद का विनिमय दर सिस्टम है, तो आप एक एक्सचेंज बैंक ऑब्जेक्ट के माध्यम से मुद्रा रूपांतरण कर सकते हैं। निम्नलिखित पर विचार करें:

Money.add_rate("USD", "BRL", 5.23995) 
Money.add_rate("BRL", "USD", 0.19111) 

जब भी आपको उनके बीच मूल्यों का आदान-प्रदान करने की आवश्यकता हो, तो आप निम्न कोड चला सकते हैं:

Money.us_dollar(100).exchange_to("BRL") #=> Money.new(523, "BRL") 

यही बात किसी भी अंकगणितीय और तुलना मूल्यांकन पर लागू होती है जिसे आप करना चाहें।

अधिक प्रदान की गई मुद्रा विशेषताओं के लिए डॉक्स देखना सुनिश्चित करें, जैसे iso_code (जो उस मुद्रा का अंतरराष्ट्रीय तीन अंकों का कोड लौटाता है) और decimal_mark (मनी डेटा के मूल्य और अंश के बीच का वर्ण), दूसरों के बीच।

ओह, मैं लगभग भूल गया; एक बार जब आप मनी जेम इंस्टॉल कर लेते हैं, तो आप BigDecimal मेथड to_money तक पहुंच सकते हैं जो स्वचालित रूप से आपके लिए रूपांतरण करता है।

"monetize" जेम

RubyMoney प्रोजेक्ट के भीतर प्रत्येक लाइब्रेरी की भूमिका को समझना महत्वपूर्ण है। जब भी आपको एक अलग रूबी ऑब्जेक्ट (उदाहरण के लिए एक String) को Money में बदलने की आवश्यकता होती है, तो monetize वही है जो आप खोज रहे हैं।

सबसे पहले, जेम निर्भरता इंस्टॉल करना सुनिश्चित करें या इसे अपने Gemfile में जोड़ें:

gem install monetize 

जाहिर है, money को भी इंस्टॉल करने की आवश्यकता है।

parse मेथड भी बहुत उपयोगी है जब आप अलग प्रारूप में मनी डेटा प्राप्त करते हैं; उदाहरण के लिए,

Money.parse("£100") == Money.new(100, "GBP") #=> true 

हालांकि जिन परिदृश्यों में आप इस पार्सिंग मेथड का उपयोग करेंगे वे प्रतिबंधित हैं, यह बहुत उपयोगी हो सकता है जब आप HTTP अनुरोध से अपनी मुद्रा कोड के साथ स्वरूपित एक मूल्य प्राप्त करते हैं। वेब पर, सब कुछ टेक्स्ट है, इसलिए स्ट्रिंग से money में परिवर्तित करना बहुत उपयोगी हो सकता है।

हालांकि, सावधान रहें कि आपका सिस्टम मूल्यों में कैसे हेरफेर करता है और क्या उन्हें किसी तरह हैक किया जा सकता है। वित्तीय सिस्टम हमेशा कई सुरक्षा परतों से ढके होते हैं ताकि यह सुनिश्चित हो सके कि आप जो मूल्य प्राप्त कर रहे हैं वह उस लेनदेन का वास्तविक मूल्य है।

"monetize-rails" जेम

यह वह लाइब्रेरी है जो वही मनी हेरफेर ऑपरेशन करती है, लेकिन एक रेल्स ऐप के भीतर।

हमें रेल्स के साथ काम करने के लिए सिर्फ एक दूसरी लाइब्रेरी की आवश्यकता क्यों है? खैर, आप निश्चित रूप से सामान्य गणित संचालन के लिए रेल्स प्रोजेक्ट्स में अकेले money जेम का उपयोग कर सकते हैं। हालांकि, यह ठीक से काम नहीं करेगा जब आपके रेल्स स्ट्रक्चर्स को money की विशेषताओं के साथ संवाद करने की आवश्यकता होगी।

निम्नलिखित उदाहरण पर विचार करें:

class Cart < ActiveRecord::Base 
  monetize :amount_cents 
end 

यह एक वास्तविक, कार्यात्मक रेल्स मॉडल ऑब्जेक्ट है। आप इसे डेटाबेस के साथ उपयोग कर सकते हैं (यहां तक कि एलियास सहित जब आप एक अलग मॉडल एट्रिब्यूट नाम चाहते हैं), Mongoid, REST वेब सेवाएं, आदि।

अब तक हम जिन विशेषताओं के संपर्क में रहे हैं, वे सभी इस जेम पर भी लागू होती हैं। आमतौर पर, इसे चलाने के लिए केवल अतिरिक्त सेटिंग्स आवश्यक हैं, जिन्हें config/initializers/money.rb फाइल में रखा जाना चाहिए:

MoneyRails.configure do |config| 
  # डिफ़ॉल्ट मुद्रा सेट करें 
  config.default_currency = :usd 
end 

यह डिफ़ॉल्ट मुद्रा को आपके द्वारा प्रदान की गई मुद्रा पर सेट करेगा। हालांकि, विकास के दौरान, संभावना है कि आपको मॉडल्स में एक से अधिक मुद्रा को संभालने या विनिमय रूपांतरण करने की आवश्यकता हो सकती है।

यदि हां, तो money-rails हमें मॉडल-लेवल मुद्रा परिभाषा कॉन्फ़िगर करने की अनुमति देता है:

class Cart < ActiveRecord::Base 
  # मॉडल लेवल मुद्रा के रूप में GPB का उपयोग करें 
  register_currency :eur 
  monetize :amount_cents, as "amount" 
  monetize :discount_cents, with_currency: :eur 
  monetize :converted_value, with_currency: :usd 
end 

ध्यान दें कि एक बार सब कुछ सेट हो जाने पर, अपने प्रोजेक्ट्स के साथ मनी टाइप्स का उपयोग करना वास्तव में आसान है।

निष्कर्ष

इस ब्लॉग पोस्ट में, हमने रूबी और रेल्स पारिस्थितिकी तंत्र के भीतर मनी मूल्यों से निपटने के लिए उपलब्ध कुछ विकल्पों का पता लगाया है। नीचे कुछ महत्वपूर्ण बिंदुओं का सारांश दिया गया है:

  • यदि आप फ्लोट नंबरों की गणना से निपट रहे हैं, विशेष रूप से यदि वे मनी डेटा का प्रतिनिधित्व करते हैं, तो BigDecimal या Money इंस्टेंस के लिए जाएं।
  • अपने विकास के साथ आगे की असंगतियों से बचने के लिए केवल एक सिस्टम से चिपके रहने का प्रयास करें।
  • money लाइब्रेरी संपूर्ण RubyMoney सिस्टम का मूल है, और यह बहुत मजबूत और सीधी है। Money-rails रेल्स एप्लिकेशन के लिए समकक्ष संस्करण है, और monetize तब आवश्यक है जब भी आपको किसी भी मूल्य से Money ऑब्जेक्ट्स में पार्स करने की आवश्यकता होती है।
  • Float का उपयोग करने से बचें। भले ही आपके ऐप को अभी कुछ भी गणना करने की आवश्यकता न हो, संभावना है कि एक अनएडवाइज्ड डेव भविष्य में ऐसा करेगा। आप इसे रोकने के लिए वहां नहीं हो सकते।

याद रखें, आधिकारिक डॉक्स हमेशा जरूरी होने चाहिए। BigDecimal इसके उपयोग के शानदार स्पष्टीकरण और उदाहरणों से भरा है, और यही RubyMoney जेम प्रोजेक्ट्स के लिए भी सच है।

  1. Ruby में Lambdas में महारत हासिल करें: बुनियादी से उन्नत तकनीकें तक Ruby में Lambdas में महारत हासिल करें: बुनियादी से उन्नत तकनीकें तक

    ब्लॉक Ruby का इतना महत्वपूर्ण हिस्सा हैं, उनके बिना भाषा की कल्पना करना कठिन है। लेकिन लैम्ब्डा? कौन लैम्ब्डा से प्यार करता है? आप वर्षों तक बिना एक का उपयोग किए जा सकते हैं। वे लगभग एक बीते युग के अवशेष लगते हैं। ...लेकिन यह पूरी तरह सच नहीं है। लैम्ब्डा की आस्तीन में कुछ दिलचस्प तरकीबें हैं एक बार

  1. रूबी के साथ इंसर्शन सॉर्ट में महारत: एक चरण-दर-चरण गाइड रूबी के साथ इंसर्शन सॉर्ट में महारत: एक चरण-दर-चरण गाइड

    नोट: यह रूबी के साथ विभिन्न सॉर्टिंग एल्गोरिदम को लागू करने पर एक श्रृंखला का भाग 4 है। भाग 1 ने बबल सॉर्ट का पता लगाया, भाग 2 ने सिलेक्शन सॉर्ट का पता लगाया, और भाग 3 ने मर्ज सॉर्ट का पता लगाया। जैसा कि हम डेटा को सॉर्ट करने के लिए विभिन्न पद्धतियों का पता लगाना जारी रखते हैं, हम इंसर्शन सॉर्ट की

  1. Ruby 2.6 में 9 प्रमुख संवर्द्धन का अन्वेषण Ruby 2.6 में 9 प्रमुख संवर्द्धन का अन्वेषण

    Ruby का एक नया संस्करण नई सुविधाओं और प्रदर्शन सुधारों के साथ आ रहा है। क्या आप परिवर्तनों के साथ बने रहना चाहेंगे? आइए एक नज़र डालते हैं! अंतहीन रेंज (Endless Ranges) Ruby 2.5 और पुराने संस्करण पहले से ही अंतहीन रेंज का एक रूप (Float::INFINITY के साथ) का समर्थन करते हैं, लेकिन Ruby 2.6 इसे अगले स्त