floating point - मुद्रा का प्रतिनिधित्व करने के लिए डबल या फ्लोट का उपयोग क्यों नहीं करें?




floating-point currency (10)

इस प्रश्न पर पोस्ट किए गए कई उत्तरों आईईईई और फ्लोटिंग-पॉइंट अंकगणितीय के आसपास के मानकों पर चर्चा करते हैं।

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

विकल्प क्या हैं? बहुत सारे हैं (और जिनमें से कई मुझे पता नहीं है!)।

जावा में BigDecimal जावा भाषा के मूल निवासी है। Apfloat जावा के लिए एक और मनमाना-सटीक पुस्तकालय है।

सी # में दशमलव डेटा प्रकार 28 महत्वपूर्ण आंकड़ों के लिए माइक्रोसॉफ्ट के .NET विकल्प है।

SciPy (वैज्ञानिक पायथन) शायद वित्तीय गणना भी संभाल सकता है (मैंने कोशिश नहीं की है, लेकिन मुझे संदेह है)।

जीएनयू मल्टीपल प्रेसिजन लाइब्रेरी (जीएमपी) और जीएनयू एमएफपीआर लाइब्रेरी सी और सी ++ के लिए दो मुक्त और मुक्त स्रोत संसाधन हैं।

जावास्क्रिप्ट (!) के लिए संख्यात्मक परिशुद्धता पुस्तकालय भी हैं और मुझे लगता है कि PHP वित्तीय गणना को संभाल सकता है।

There are also proprietary (particularly, I think, for Fortran) and open-source solutions as well for many computer languages.

I'm not a computer scientist by training. However, I tend to lean towards either BigDecimal in Java or decimal in C#. I haven't tried the other solutions I've listed, but they are probably very good as well.

For me, I like BigDecimal because of the methods it supports. C#'s decimal is very nice, but I haven't had the chance to work with it as much as I'd like. I do scientific calculations of interest to me in my spare time, and BigDecimal seems to work very well because I can set the precision of my floating point numbers. The disadvantage to BigDecimal? It can be slow at times, especially if you're using the divide method.

You might, for speed, look into the free and proprietary libraries in C, C++, and Fortran.

मुझे हमेशा कहा जाता है कि कभी भी double या float प्रकारों के साथ पैसे का प्रतिनिधित्व नहीं किया जाता है, और इस बार मैं आपको प्रश्न पूछता हूं: क्यों?

मुझे यकीन है कि एक बहुत अच्छा कारण है, मुझे नहीं पता कि यह क्या है।


कुछ उदाहरण ... यह लगभग किसी भी प्रोग्रामिंग भाषा पर काम करता है (वास्तव में काम नहीं करता है) ... मैंने डेल्फी, वीबीस्क्रिप्ट, विजुअल बेसिक, जावास्क्रिप्ट और अब जावा / एंड्रॉइड के साथ प्रयास किया है:

    double total = 0.0;

    // do 10 adds of 10 cents
    for (int i = 0; i < 10; i++) {
        total += 0.1;  // adds 10 cents
    }

    Log.d("round problems?", "current total: " + total);

    // looks like total equals to 1.0, don't?

    // now, do reverse
    for (int i = 0; i < 10; i++) {
        total -= 0.1;  // removes 10 cents
    }

    // looks like total equals to 0.0, don't?
    Log.d("round problems?", "current total: " + total);
    if (total == 0.0) {
        Log.d("round problems?", "is total equal to ZERO? YES, of course!!");
    } else {
        Log.d("round problems?", "is total equal to ZERO? NO... thats why you should not use Double for some math!!!");
    }

उत्पादन:

round problems?: current total: 0.9999999999999999 round problems?: current total: 2.7755575615628914E-17 round problems?: is total equal to ZERO? NO... thats why you should not use Double for some math!!!


जैसा कि पहले कहा गया था "एक डबल या फ्लोट के रूप में धन का प्रतिनिधित्व करना शायद पहले अच्छा लगेगा क्योंकि सॉफ़्टवेयर छोटी त्रुटियों से गुजरता है, लेकिन जब आप असंख्य संख्याओं पर अधिक जोड़, घटाव, गुणा और विभाजन करते हैं, तो आप अधिक से अधिक परिशुद्धता खो देंगे चूंकि त्रुटियां बढ़ती हैं। इससे पैसे से निपटने के लिए फ्लोट्स और युगल अपर्याप्त होते हैं, जहां बेस 10 शक्तियों के गुणकों के लिए सही सटीकता की आवश्यकता होती है। "

अंत में जावा के पास मुद्रा और धन के साथ काम करने का एक मानक तरीका है!

जेएसआर 354: मनी और मुद्रा एपीआई

जेएसआर 354 धन और मुद्रा के साथ व्यापक गणना का प्रतिनिधित्व, परिवहन और प्रदर्शन करने के लिए एक एपीआई प्रदान करता है। आप इसे इस लिंक से डाउनलोड कर सकते हैं:

जेएसआर 354: मनी और मुद्रा एपीआई डाउनलोड करें

विनिर्देश में निम्नलिखित चीजें शामिल हैं:

  1. मौद्रिक मात्रा और मुद्राओं जैसे हैंडलिंग के लिए एक एपीआई
  2. विनिमयशील कार्यान्वयन का समर्थन करने के लिए एपीआई
  3. कार्यान्वयन कक्षाओं के उदाहरण बनाने के लिए कारखानों
  4. मौद्रिक रकम की गणना, रूपांतरण और स्वरूपण के लिए कार्यक्षमता
  5. पैसा और मुद्राओं के साथ काम करने के लिए जावा एपीआई, जिसे जावा 9 में शामिल करने की योजना है।
  6. सभी विनिर्देश वर्ग और इंटरफेस javax.money। * पैकेज में स्थित हैं।

जेएसआर 354 के नमूना उदाहरण: धन और मुद्रा API:

एक मौद्रिक राशि बनाने और इसे कंसोल पर प्रिंट करने का एक उदाहरण इस तरह दिखता है ::

MonetaryAmountFactory<?> amountFactory = Monetary.getDefaultAmountFactory();
MonetaryAmount monetaryAmount = amountFactory.setCurrency(Monetary.getCurrency("EUR")).setNumber(12345.67).create();
MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(Locale.getDefault());
System.out.println(format.format(monetaryAmount));

संदर्भ कार्यान्वयन API का उपयोग करते समय, आवश्यक कोड बहुत आसान है:

MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(Locale.getDefault());
System.out.println(format.format(monetaryAmount));

एपीआई मौद्रिक राशि के साथ गणना का भी समर्थन करता है:

MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmount otherMonetaryAmount = monetaryAmount.divide(2).add(Money.of(5, "EUR"));

CurrencyUnit और मौद्रिक राशि

// getting CurrencyUnits by locale
CurrencyUnit yen = MonetaryCurrencies.getCurrency(Locale.JAPAN);
CurrencyUnit canadianDollar = MonetaryCurrencies.getCurrency(Locale.CANADA);

मौद्रिक राशि में विभिन्न विधियां हैं जो निर्दिष्ट मुद्रा तक पहुंचने की अनुमति देती हैं, संख्यात्मक राशि, इसकी सटीकता और अधिक:

MonetaryAmount monetaryAmount = Money.of(123.45, euro);
CurrencyUnit currency = monetaryAmount.getCurrency();
NumberValue numberValue = monetaryAmount.getNumber();

int intValue = numberValue.intValue(); // 123
double doubleValue = numberValue.doubleValue(); // 123.45
long fractionDenominator = numberValue.getAmountFractionDenominator(); // 100
long fractionNumerator = numberValue.getAmountFractionNumerator(); // 45
int precision = numberValue.getPrecision(); // 5

// NumberValue extends java.lang.Number. 
// So we assign numberValue to a variable of type Number
Number number = numberValue;

एक गोल करने वाले ऑपरेटर का उपयोग करके मौद्रिक गणना गोल की जा सकती है:

CurrencyUnit usd = MonetaryCurrencies.getCurrency("USD");
MonetaryAmount dollars = Money.of(12.34567, usd);
MonetaryOperator roundingOperator = MonetaryRoundings.getRounding(usd);
MonetaryAmount roundedDollars = dollars.with(roundingOperator); // USD 12.35

मौद्रिक गणना के संग्रह के साथ काम करते समय, फ़िल्टरिंग, सॉर्टिंग और ग्रुपिंग के लिए कुछ अच्छी उपयोगिता विधियां उपलब्ध हैं।

List<MonetaryAmount> amounts = new ArrayList<>();
amounts.add(Money.of(2, "EUR"));
amounts.add(Money.of(42, "USD"));
amounts.add(Money.of(7, "USD"));
amounts.add(Money.of(13.37, "JPY"));
amounts.add(Money.of(18, "USD"));

कस्टम मौद्रिक राशि संचालन

// A monetary operator that returns 10% of the input MonetaryAmount
// Implemented using Java 8 Lambdas
MonetaryOperator tenPercentOperator = (MonetaryAmount amount) -> {
  BigDecimal baseAmount = amount.getNumber().numberValue(BigDecimal.class);
  BigDecimal tenPercent = baseAmount.multiply(new BigDecimal("0.1"));
  return Money.of(tenPercent, amount.getCurrency());
};

MonetaryAmount dollars = Money.of(12.34567, "USD");

// apply tenPercentOperator to MonetaryAmount
MonetaryAmount tenPercentDollars = dollars.with(tenPercentOperator); // USD 1.234567

संसाधन:

जेएसआर 354 के साथ जावा में धन और मुद्राओं को संभालना

जावा 9 मनी और मुद्रा एपीआई (जेएसआर 354) में देख रहे हैं

यह भी देखें: जेएसआर 354 - मुद्रा और धन


फ़्लोटिंग पॉइंट नंबर का परिणाम सटीक नहीं है, जो उन्हें किसी भी वित्तीय गणना के लिए अनुपयुक्त बनाता है जिसके लिए सटीक परिणाम की आवश्यकता होती है और अनुमान नहीं होता है। फ्लोट और डबल इंजीनियरिंग और वैज्ञानिक गणना के लिए डिज़ाइन किए गए हैं और कई बार सटीक परिणाम नहीं देते हैं, फ्लोटिंग पॉइंट गणना का परिणाम भी JVM से JVM तक भिन्न हो सकता है। बिगडेसिमल और डबल प्राइमेटिव का उदाहरण नीचे देखें, जिसका उपयोग पैसे मूल्य का प्रतिनिधित्व करने के लिए किया जाता है, यह स्पष्ट है कि फ़्लोटिंग पॉइंट गणना सटीक नहीं हो सकती है और वित्तीय गणनाओं के लिए किसी को बिगडिसीमल का उपयोग करना चाहिए।

    // floating point calculation
    final double amount1 = 2.0;
    final double amount2 = 1.1;
    System.out.println("difference between 2.0 and 1.1 using double is: " + (amount1 - amount2));

    // Use BigDecimal for financial calculation
    final BigDecimal amount3 = new BigDecimal("2.0");
    final BigDecimal amount4 = new BigDecimal("1.1");
    System.out.println("difference between 2.0 and 1.1 using BigDecimal is: " + (amount3.subtract(amount4)));

आउटपुट:

difference between 2.0 and 1.1 using double is: 0.8999999999999999
difference between 2.0 and 1.1 using BigDecimal is: 0.9

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

एक्सेल में मुद्रा के साथ गणना करने वाले लोगों ने हमेशा डबल परिशुद्धता फ्लोट का उपयोग किया है (एक्सेल में कोई मुद्रा प्रकार नहीं है) और मुझे अभी तक किसी को गोल करने वाली त्रुटियों के बारे में शिकायत करने की आवश्यकता नहीं है।

बेशक, आपको कारण के भीतर रहना होगा; उदाहरण के लिए एक साधारण वेबशॉप शायद डबल परिशुद्धता फ्लोट के साथ कभी भी किसी भी समस्या का अनुभव नहीं करेगा, लेकिन यदि आप ऐसा करते हैं जैसे लेखांकन या कुछ और जो बड़ी संख्या में (अप्रतिबंधित) संख्याओं को जोड़ने की आवश्यकता है, तो आप दस फुट के साथ फ़्लोटिंग पॉइंट नंबर को स्पर्श नहीं करना चाहेंगे पोल।


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

यदि आपका परिणाम गोलाकार होने और गोलाकार होने के बीच की सीमा रेखा पर है, और वह अंतिम पैसा वास्तव में मायने रखता है, तो शायद आपको दर्शक को यह बताना चाहिए कि उत्तर लगभग मध्य में है - अधिक दशमलव स्थानों को प्रदर्शित करके।

युगल के साथ समस्या, और फ्लोट के साथ और अधिक, जब वे बड़ी संख्या और छोटी संख्याओं को गठबंधन करने के लिए उपयोग किया जाता है। जावा में,

System.out.println(1000000.0f + 1.2f - 1000000.0f);

का परिणाम

1.1875

यदि आपकी गणना में विभिन्न चरणों को शामिल किया गया है, तो मनमाने ढंग से सटीक अंकगणित आपको 100% कवर नहीं करेगा।

परिणामों के सही प्रतिनिधित्व का उपयोग करने का एकमात्र विश्वसनीय तरीका (एक कस्टम फ्रैक्शन डेटा प्रकार का उपयोग करें जो अंतिम चरण में विभाजन संचालन बैच करेगा) और केवल अंतिम चरण में दशमलव नोटेशन में कनवर्ट करेगा।

मनमाने ढंग से सटीकता से मदद नहीं मिलेगी क्योंकि वहां हमेशा संख्याएं हो सकती हैं जिनमें बहुत अधिक दशमलव स्थान हैं, या कुछ परिणाम जैसे कि 0.6666666 ... कोई मनमाना प्रतिनिधित्व अंतिम उदाहरण को शामिल नहीं करेगा। तो आपको प्रत्येक चरण में छोटी त्रुटियां होंगी।

यह त्रुटियां ऐड-अप होंगी, अंत में अब अनदेखा करना आसान नहीं हो सकता है। इसे त्रुटि प्रचार कहा जाता है।


यह सटीकता का मामला नहीं है, न ही यह परिशुद्धता का विषय है। यह उन मनुष्यों की अपेक्षाओं को पूरा करने का विषय है जो आधार 2 के बजाय गणना के लिए आधार 10 का उपयोग करते हैं। उदाहरण के लिए, वित्तीय गणनाओं के लिए युगल का उपयोग करने से गणितीय अर्थ में "गलत" उत्तर नहीं होते हैं, लेकिन यह ऐसे उत्तर उत्पन्न कर सकता है जो उत्तर हैं एक वित्तीय अर्थ में क्या उम्मीद नहीं है।

यहां तक ​​कि यदि आप आउटपुट से पहले आखिरी मिनट में अपने परिणाम बंद कर देते हैं, तो भी आप कभी-कभी युगल का उपयोग करके परिणाम प्राप्त कर सकते हैं जो अपेक्षाओं से मेल नहीं खाता है।

कैलकुलेटर का उपयोग करना, या हाथ से परिणामों की गणना करना, 1.40 * 165 = 231 बिल्कुल। हालांकि, आंतरिक रूप से युगल का उपयोग करके, मेरे कंपाइलर / ऑपरेटिंग सिस्टम पर्यावरण पर, इसे 230.9 99 99 के करीब एक द्विआधारी संख्या के रूप में संग्रहीत किया जाता है ... इसलिए यदि आप संख्या को छोटा कर देते हैं, तो आपको 231 के बजाय 230 मिलते हैं। आप तर्क दे सकते हैं कि छंटनी के बजाए गोल करना 231 का वांछित परिणाम दिया है। यह सच है, लेकिन गोल करने में हमेशा छिड़काव होता है। आप जो भी गोलाकार तकनीक का उपयोग करते हैं, वहां अभी भी ऐसी सीमाएं हैं जो इस दौर में होती हैं जब आप इसे गोल करने की उम्मीद करते हैं। वे दुर्लभ हैं कि वे अक्सर आकस्मिक परीक्षण या अवलोकन के माध्यम से नहीं पाएंगे। आपको उन उदाहरणों की खोज करने के लिए कुछ कोड लिखना पड़ सकता है जो परिणामों को चित्रित करते हैं जो अपेक्षित व्यवहार नहीं करते हैं।

मान लें कि आप निकटतम पैसा के लिए कुछ गोल करना चाहते हैं। तो आप अपना अंतिम परिणाम लेते हैं, 100 से गुणा करते हैं, 0.5 जोड़ते हैं, छंटनी करते हैं, फिर पैनीज़ पर वापस जाने के लिए 100 से परिणाम विभाजित करते हैं। यदि आपके द्वारा संग्रहीत आंतरिक नंबर 3.46499999 था .... 3.465 के बजाय, आप 3.47 के बजाय 3.47 प्राप्त करने जा रहे हैं जब आप निकटतम पैनी तक नंबर को गोल करते हैं। लेकिन आपकी आधार 10 गणनाओं ने संकेत दिया होगा कि उत्तर 3.465 होना चाहिए, जो स्पष्ट रूप से 3.47 तक होनी चाहिए, 3.46 तक नहीं। जब आप वित्तीय गणना के लिए युगल का उपयोग करते हैं तो इस तरह की चीजें कभी-कभी वास्तविक जीवन में होती हैं। यह दुर्लभ है, इसलिए यह अक्सर किसी मुद्दे के रूप में अनजान हो जाता है, लेकिन ऐसा होता है।

यदि आप युगल की बजाय अपनी आंतरिक गणना के लिए आधार 10 का उपयोग करते हैं, तो उत्तर हमेशा आपके कोड में अन्य बग मानते हुए मनुष्यों द्वारा अपेक्षित अपेक्षाओं के अनुसार होते हैं।


ब्लोच से, जे।, प्रभावी जावा, दूसरा संस्करण, आइटम 48:

float और double प्रकार विशेष रूप से मौद्रिक गणना के लिए उपयुक्त हैं क्योंकि 0.1 या (दस की कोई अन्य नकारात्मक शक्ति) को float या double रूप में प्रस्तुत करना असंभव है।

उदाहरण के लिए, मान लें कि आपके पास $ 1.03 है और आप 42 सी खर्च करते हैं। आपने कितना पैसा छोड़ा है?

System.out.println(1.03 - .42);

0.6100000000000001 प्रिंट 0.6100000000000001

इस समस्या को हल करने का सही तरीका मौद्रिक गणना के लिए BigDecimal , int या long का उपयोग करना है।


Most answers have highlighted the reasons why one should not use doubles for money and currency calculations. And I totally agree with them.

It doesn't mean though that doubles can never be used for that purpose.

I have worked on a number of projects with very low gc requirements, and having BigDecimal objects was a big contributor to that overhead.

It's the lack of understanding about double representation and lack of experience in handling the accuracy and precision that brings about this wise suggestion.

You can make it work if you are able to handle the precision and accuracy requirements of your project, which has to be done based on what range of double values is one dealing with.

You can refer to guava's FuzzyCompare method to get more idea. The parameter tolerance is the key. We dealt with this problem for a securities trading application and we did an exhaustive research on what tolerances to use for different numerical values in different ranges.

साथ ही, ऐसी स्थितियां हो सकती हैं जब आप हैश नक्शा कार्यान्वयन के साथ एक नक्शा कुंजी के रूप में डबल रैपर का उपयोग करने के लिए लुभाने लगे हैं। यह बहुत जोखिम भरा है क्योंकि उदाहरण के लिए Double.equals और हैश कोड "0.5" और "0.6 - 0.1" मान एक बड़ी गड़बड़ी का कारण बनेंगे।





currency