2015-03-29 2 views
41
import java.math.BigDecimal; 
import java.math.RoundingMode; 

public class BigDecimalTest { 

    public static void main (String[] args) { 

    // 4.88...e+888 (1817 digits) 
    BigDecimal x = new BigDecimal("4.8832420563130171734733855852454330503023811919919497272520875234748556667894678622576481754268427107559208829679871295885797242917923401597269406065677191699322289667695163278484184288979073748578074654323955355081326227413484377691676742424283166095829482224974429868654315166151274143385980609237680132582337344627820946638217515894542788180511625488217105374918015830882194114839900966043221545533114607439892553114356192220778082796185122942407317178325055570254731781136589172583464356709469398354084238614163644229733602505332671951571644165960364672255033809137641462904872690406789293887232669588297154237004709334039097468524122773548736567569610163195984254280720739773383424940292419418795538600322135358425131164741597944425501875163782825762694824406500718290697914964822219714335320528259344719705157913218736206355811213275685167080292570345461898557739737178480700932922510537942188898832900701474604169230215866582286672118698263686941093382945779882215421032414999405126831495224267159359035083987132591639397950272617333366716471522059176764287433877865132652162238979110053714139119937420203828830308427979430335027147927304099711225033972679405835031675622271744826476172494554124259735452592820489036311645033355738586053207859638698142614469753279404304130088308403735928520706825401977138623732336487326694527108332032932321484204820451539099031068139840323111890984119271864483907126875945501867099986131423579718697889448836497435592993168391953327829695391643033262276364164246663414855044991442223872210174626308430613254236633497864858897399515832571171741522071020097519091890029843359547212185712419638040776450730043492270253991396124987467648536016180816769990203447616590740625203442076233929983869509074724986395815800482885710533831896927860285993286232937744729344906236207008084e+888"); 
    BigDecimal y = new BigDecimal("7.11510949782866099699296193137700951609335763543887012748548458182417747578081585833524887774072570691956766860384875364912060891737185872746005419263400444156198098581226885923291670353816772414798224148927688218647602446762953730527741703572368727049379249227044080281137229152770971832240631944592537904743732558993126e+302"); 
    BigDecimal z = x.divide(y, 0, RoundingMode.HALF_UP); 

    System.out.println("x: " + x.toString()); 
    System.out.println(); 
    System.out.println("y: " + y.toString()); 
    System.out.println(); 
    System.out.println("z: " + z.toString()); 
    } 
} 

संकलितToString एक अपरिवर्तनीय BigDecimal पर सही मान का उत्पादन करने में विफल क्यों होता है?

>javac BigDecimalTest.java 

निष्पादित

>java BigDecimalTest 

आउटपुट

x: 625054983208066198204593354911415430438704792574969565088267203004781525349051886368978966454635866976757873019902352587338204709349419540445048397640668053751325307746498089964597558898932143981799355575346628545040975710892600034453462303030824526026617372479672702318775234126736309035340551798242305697053918011236108116969184203450147688710548806249178948798950602635292084669950732365353235782823866975230624679863759260425959459791169573662813659882560711299260566798548341409068343765881208298932278254261294646140590112068258200980117045324292667804864432756961810725182370437206902961756578170730203574233660279475700447597108771501423828064891010088908598454793225469099307839235742968560582894084123332587841678908692453688646424002096420169762493752403209194120933311549724412343492102761719612412226021289199823441354383529928770138627744900421912301539068635884552971941408134.8856600179050611289788749333661467630922532694031193377751928459953017059824923573892149119923856234431388706196397956490750352971729842937634895018670939708354823574625828791536366736979476766589326086875409807351989786090090279478781367082883474934694924763036804348502963946884054479650783337788950079302927905246137931881022596647890564269534539014810606033753362254652128419763750928651303475678198850650473651453073743837739070377816899469866500215337149978217017797004675976721899561358322045967266798653940112240121024238988798224822218203993329849451071671755903125554170025962201010130308257571374613023572917101445758904604655642902352167479118496542289087726701938867138026569109982914825090572482443761923819950022043159771189713669219385693445567010592510898703998395859012610071144546558746041294923614800026040585757943037935297161564798258664422461809370948330482806766116607140637816031325356147998234497034752 

y: 711510949782866099699296193137700951609335763543887012748548458182417747578081585833524887774072570691956766860384875364912060891737185872746005419263400444156198098581226885923291670353816772414798224148927688218647602446762953730527741703572368727049379249227044080281137229152770971832240631944592537.904743732558993126 

z: 6863200148645991450016700150728475158275817266239021182863526677885700921863906334312309256001619020949572592642200844420107346867400206096485382274175041601107978676753014927820457112641389679172479926134263590581506384223135957016211147412682886175625161361918270282067511320630977561140325469899962049739132122854543111824994613211802165652292305592183629295330885779837415870933600699791946039851356918600890315497940083093271504897016557099915008808164166772999720870505507779642391694002178573568389923682384862328430119487673749084566046514914589822168578412569408216619911686172 

उत्पादन में z.toString() का मूल्य सही है

4.883242e+888/7.115109e+302 = 6.863200e+585 

y.toString() का मान है, लेकिन ध्यान दें कि x.toString() के लिए दिया गया मान पूरी तरह गलत है।

यह क्यों है?

बात यह विभाजन का परिणाम के पैमाने (अर्थात वांछित दशमलव स्थानों)

BigDecimal z = x.divide(y, 3, RoundingMode.HALF_UP); 

बदल रही है तो x.toString()x के लिए सही मान का उत्पादन करेगा।

या, यदि ऑपरेंड

BigDecimal z = y.divide(x, 0, RoundingMode.HALF_UP); 

लगा दिया जाता था तो x.toString() भी तो सही मान का उत्पादन करेगा।

या, x का एक्सपोनेंट e+888 से बदल दिया गया है उदा। e+878 फिर x.toString() सही होगा।

या, यदि एक और x.toString() कॉल divide आपरेशन ऊपर जोड़ा जाता है, तो दोनोंx.toString() कॉल सही मान का उत्पादन करेगा!

मशीन पर मैं इसका परीक्षण कर रहा हूं, विंडोज 7 64 बिट, व्यवहार 32 और 64 बिट संस्करण दोनों जावा 7 और 8 का उपयोग कर समान है, लेकिन https://ideone.com/ पर ऑनलाइन परीक्षण जावा 7 और जावा 8 के लिए अलग-अलग परिणाम उत्पन्न करता है

जावा 7 का उपयोग करके, x का मान सही ढंग से दिया गया है: http://ideone.com/P1sXQQ, लेकिन जावा 8 का उपयोग करके इसका मान गलत है: http://ideone.com/OMAq7a

इसके अलावा, इस व्यवहार नहीं x के इस विशेष मूल्य के लिए अद्वितीय, उन्हें एक divide आपरेशन करने के लिए पहले संकार्य भी गलत मान का उत्पादन करेगा के रूप में पास करने के बाद लगभग 1500 से भी अधिक अंकों के साथ अन्य BigDecimals पर toString कॉल करने जैसा है।

इसके लिए स्पष्टीकरण क्या है?

divide ऑपरेशन उसके ऑपरेटरों पर बाद के toString कॉल द्वारा उत्पादित मूल्य को म्यूट कर रहा है।

क्या यह आपके प्लेटफॉर्म पर होता है?

संपादित करें:

मुद्दा ऊपर के साथ जब साथ निष्पादित जावा 7 सही उत्पादन जब जावा 7 क्रम के साथ निष्पादित, लेकिन गलत उत्पादन का उत्पादन संकलित कार्यक्रम के रूप में, केवल जावा 8 क्रम के साथ हो रहा है जावा 8 रनटाइम।

संपादित करें:

मैं जल्दी पहुँच jre1.8.0_60 साथ परीक्षण किया है और बग प्रकट नहीं होता है, और Marco13 के जवाब के अनुसार यह निर्माण 51. में तय किया गया था ओरेकल JDK 8 उत्पाद बाइनरी अद्यतन पर ही कर रहे हैं 40 हालांकि, निश्चित संस्करणों का व्यापक रूप से उपयोग होने से पहले कुछ समय हो सकता है।

+8

* " या, यदि कोई अन्य x.toString() कॉल विभाजन ऑपरेशन के ऊपर जोड़ा गया है, तो x.toString() कॉल दोनों सही मान का उत्पादन करेंगे! "* यह थोड़ा सा है जो मुझे वास्तव में डरावना लगता है। और यह मेरे लिए होता है (लिनक्स पर 64-बिट जावा 1.8.0_20)। –

+0

जिज्ञासा से बाहर, क्या आप apfloat के साथ प्रयास कर सकते हैं? – fge

+1

मुझे विश्वास है कि यह एक बग है जिसे जावा बग ट्रैकर में सबमिट किया जाना चाहिए। –

उत्तर

28

अजीब व्यवहार के लिए कारण को ट्रैक करना इतना कठिन नहीं है।

divide कॉल

public BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode) { 
    return divide(divisor, scale, roundingMode.oldMode); 
} 

यह करने के लिए चला जाता है, आंतरिक रूप से, एक और divide विधि के प्रतिनिधियों, राउंडिंग मोड के आधार पर:

public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode) { 
    if (roundingMode < ROUND_UP || roundingMode > ROUND_UNNECESSARY) 
     throw new IllegalArgumentException("Invalid rounding mode"); 
    if (this.intCompact != INFLATED) { 
     if ((divisor.intCompact != INFLATED)) { 
      return divide(this.intCompact, this.scale, divisor.intCompact, divisor.scale, scale, roundingMode); 
     } else { 
      return divide(this.intCompact, this.scale, divisor.intVal, divisor.scale, scale, roundingMode); 
     } 
    } else { 
     if ((divisor.intCompact != INFLATED)) { 
      return divide(this.intVal, this.scale, divisor.intCompact, divisor.scale, scale, roundingMode); 
     } else { 
      return divide(this.intVal, this.scale, divisor.intVal, divisor.scale, scale, roundingMode); 
     } 
    } 
} 

इस मामले में, पिछले कॉल लागू होता है। ध्यान दें कि intVal (जो एक BigInteger कि BigDecimal में संग्रहित है) पहले तर्क के रूप में इस विधि को सीधे पारित कर दिया है:

private static BigDecimal divide(BigInteger dividend, int dividendScale, BigInteger divisor, int divisorScale, int scale, int roundingMode) { 
    if (checkScale(dividend,(long)scale + divisorScale) > dividendScale) { 
     int newScale = scale + divisorScale; 
     int raise = newScale - dividendScale; 
     BigInteger scaledDividend = bigMultiplyPowerTen(dividend, raise); 
     return divideAndRound(scaledDividend, divisor, scale, roundingMode, scale); 
    } else { 
     int newScale = checkScale(divisor,(long)dividendScale - scale); 
     int raise = newScale - divisorScale; 
     BigInteger scaledDivisor = bigMultiplyPowerTen(divisor, raise); 
     return divideAndRound(dividend, scaledDivisor, scale, roundingMode, scale); 
    } 
} 

अंत में, दूसराdivideAndRound के लिए पथ यहाँ लिया जाता है, फिर से गुजर पर dividend (जो मूल BigDecimal की intVal था), इस कोड के साथ समाप्त:

private static BigDecimal divideAndRound(BigInteger bdividend, BigInteger bdivisor, int scale, int roundingMode, 
             int preferredScale) { 
    boolean isRemainderZero; // record remainder is zero or not 
    int qsign; // quotient sign 
    // Descend into mutables for faster remainder checks 
    MutableBigInteger mdividend = new MutableBigInteger(bdividend.mag); 
    MutableBigInteger mq = new MutableBigInteger(); 
    MutableBigInteger mdivisor = new MutableBigInteger(bdivisor.mag); 
    MutableBigInteger mr = mdividend.divide(mdivisor, mq); 
    ... 

और यह जहां त्रुटि शुरू की है है: mdivididend एक परिवर्तनशीलBigInteger, BigInteger की mag सरणी है कि मूल कॉल से BigDecimalx में संग्रहित है पर एक परिवर्तनशील दृश्य के रूप में बनाया गया था कि है। विभाजन mag फ़ील्ड को संशोधित करता है, और इस प्रकार, अब (अब अप्रचलित नहीं) BigDecimal की स्थिति।

यह divide विधियों में से एक के कार्यान्वयन में स्पष्ट रूप से एक बग है। मैंने ओपनजेडीके के परिवर्तन सेटों को ट्रैक करना शुरू कर दिया है, लेकिन अभी तक निश्चित अपराधी नहीं देखा है। (संपादित करें: नीचे अद्यतन देखें)

(ए ओर ध्यान दें: विभाजन करने से पहले x.toString() कॉलिंग वास्तव में से बचने नहीं करता, लेकिन केवल छिपाने बग: यह होने के लिए सही राज्य के एक स्ट्रिंग कैश का कारण बनता है आंतरिक रूप से बनाया गया। सही मान मुद्रित है, लेकिन आंतरिक स्थिति अभी भी गलत है - जो कम से कम कहने के लिए संबंधित है ...)


अद्यतन: पुष्टि करने के लिए क्या @MikeM ने कहा: बाहर खुदाई के लिए माइक और exex zian को कुडोस: बग openjdk bug list पर सूचीबद्ध किया गया है और उस में JDK8 Build 51

अद्यतन हल किया गया है बग रिपोर्ट। चर्चा के अनुसार, बग this changeset के साथ पेश किया गया था। (वैसे, जबकि परिवर्तन के माध्यम से स्कीम, मैं भी इस एक गर्म उम्मीदवार के रूप में माना जाता है, लेकिन विश्वास नहीं कर सकता कि इस चार साल पहले शुरू की गई थी और अब तक किसी का ध्यान नहीं बना रहा ...)

+1

मैं पुष्टि कर सकता हूं कि बग जावा 8 अपडेट 60 में तय किया गया है। – MikeM

संबंधित मुद्दे

 संबंधित मुद्दे