SORU
29 Mart 2015, Pazar


Neden olabilirdi değişmez bir BigDecimal doğru değer üretmek için başarısız mı?

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());
  }
}

Derleme

>javac BigDecimalTest.java

Yürütmek

 >java BigDecimalTest

Çıktı

x: 625054983208066198204593354911415430438704792574969565088267203004781525349051886368978966454635866976757873019902352587338204709349419540445048397640668053751325307746498089964597558898932143981799355575346628545040975710892600034453462303030824526026617372479672702318775234126736309035340551798242305697053918011236108116969184203450147688710548806249178948798950602635292084669950732365353235782823866975230624679863759260425959459791169573662813659882560711299260566798548341409068343765881208298932278254261294646140590112068258200980117045324292667804864432756961810725182370437206902961756578170730203574233660279475700447597108771501423828064891010088908598454793225469099307839235742968560582894084123332587841678908692453688646424002096420169762493752403209194120933311549724412343492102761719612412226021289199823441354383529928770138627744900421912301539068635884552971941408134.8856600179050611289788749333661467630922532694031193377751928459953017059824923573892149119923856234431388706196397956490750352971729842937634895018670939708354823574625828791536366736979476766589326086875409807351989786090090279478781367082883474934694924763036804348502963946884054479650783337788950079302927905246137931881022596647890564269534539014810606033753362254652128419763750928651303475678198850650473651453073743837739070377816899469866500215337149978217017797004675976721899561358322045967266798653940112240121024238988798224822218203993329849451071671755903125554170025962201010130308257571374613023572917101445758904604655642902352167479118496542289087726701938867138026569109982914825090572482443761923819950022043159771189713669219385693445567010592510898703998395859012610071144546558746041294923614800026040585757943037935297161564798258664422461809370948330482806766116607140637816031325356147998234497034752

y: 711510949782866099699296193137700951609335763543887012748548458182417747578081585833524887774072570691956766860384875364912060891737185872746005419263400444156198098581226885923291670353816772414798224148927688218647602446762953730527741703572368727049379249227044080281137229152770971832240631944592537.904743732558993126

z: 6863200148645991450016700150728475158275817266239021182863526677885700921863906334312309256001619020949572592642200844420107346867400206096485382274175041601107978676753014927820457112641389679172479926134263590581506384223135957016211147412682886175625161361918270282067511320630977561140325469899962049739132122854543111824994613211802165652292305592183629295330885779837415870933600699791946039851356918600890315497940083093271504897016557099915008808164166772999720870505507779642391694002178573568389923682384862328430119487673749084566046514914589822168578412569408216619911686172

Çıktı z.toString() değeri doğru mu?

4.883242e 888 / 7.115109e 302 = 6.863200e 585

y.toString() değeri olup bu değer x.toString() için verilen tamamen yanlış olduğuna dikkat edin.

Neden bu?

Eğer ölçek (istediğiniz basamağa yani) bölünme sonucu değişirse, garip bir şekilde

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

x.toString() x doğru değerini üretir.

Ya da, eğer işlenen takas

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

sonra x.toString() sonra da doğru değeri üretecektir.

Ya da, eğer x üs e 888 e 878 örneğin değişirse o zaman x.toString() doğru olacaktır.

Ya da, eğer x.toString() bir Ara divide yukarıda belirtilen işlem eklenirse, o zamanher ikisi dex.toString() doğru değer üretecek çağırıyor!

Makineyi ben test bu, Windows 7 64 bit, davranışları aynı kullanarak java 7 ve 8 hem 32 bit ve 64 bit sürümleri, ama test online https://ideone.com/ ürettiği farklı sonuçlar için java 7 ve java 8.

x değerini doğru olarak verilmiştir 7, java kullanarak: http://ideone.com/P1sXQQ, ama onun değeri doğru değil java 8 kullanarak: http://ideone.com/OMAq7a.

Ayrıca, bu davranış değil, benzersiz bu özel değeri x şeklinde arama toString diğer BigDecimals ile ilgili daha fazla bilgi için 1500 basamak sonra onları geçirmeden önce işlenen bir divide işlem de üretmek yanlış değerler.

Bunun açıklaması nedir?

divide işlem değeri, basitçe toString sonraki çağrılar tarafından üretilen mutasyona gibi görünüyor.

Bu platformda oluyor mu?

Düzenleme:

Sorun galiba ile java 8 çalışma zamanı sadece yukarıdaki program derlenmiş java 7 üretir doğru çıkış ne zaman idam ile java 7 çalışma zamanı, ama yanlış çıktı ne zaman idam ile java 8 çalışma zamanı.

Düzenleme:

İnşa 51 olarak tespit edildi Marco13 cevabına göre jre1.8.0_60 ve hata görünmüyor erken erişim ile test ettim. 8 ürün Oracle ile İLGİLENİYORUZ ikili sabit sürümleri yaygın olarak kullanılmasından önce biraz zaman olabilir bu yüzden ama sadece güncelleme 40.

CEVAP
29 Mart 2015, Pazar


Bu izini bulmak o kadar da zor değilnedenigarip davranış.

divide aramaya gider

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

Bu, dahili olarak, divide başka bir yöntem, yuvarlama kipine göre delegeler:

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);
        }
    }
}

Bu durumda, son çağrı geçerlidir. intVal BigDecimal depolanan BigInteger a) doğrudan ilk argüman olarak bu yöntemi geçirilen not:

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);
    }
}

Son olarak, yoluikincidivideAndRound yine dividend BigDecimal orijinal intVal () çıkarak, bu kod ile biten alınmıştır:

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);
    ...

Ve bu hata tanıtıldığı: mdivididendkesilebilir51**, özgün çağrıBigDecimal x içinde depolanan BigInteger mag dizi üzerinde değiştirilebilir bir görünüm olarak oluşturulmuştur. Bölümü mag alan değiştirir, ve böylece, devlet (şimdi pek değişmez) 57**.

Bu açıkça divide yöntemlerden birinin uygulanması bir hata değildir. Ben zaten değişim OpenJDK setleri takip başladı, ama henüz kesin suçlu görüldü. (Edit: güncellemeleri aşağıya Bakınız)

(Bir yan not: bu bölümü yapmadan önce x.toString() Demeyi gerçektenönlemekama sadecegizlemekhata: doğru devlet dize önbellek DAHİLİ olarak oluşturulan neden olur. Doğru değeri yazdırılır, fakat iç durumu hala yanlış ile ilgili olan, en az... demek)


Güncelleme: @MikeM sözlerini doğrulamak için: Hata openjdk bug list listelendi ve JDK8 Build 51 . içinde çözümlendi

Güncelleme: Kazma Mike şeref ve exex zian dışarı hata raporları. Tartışma göre, hata this changeset birlikte kullanılmaya başlandı. Değişiklikleri ile kaymağını ise (kuşkusuz, ben de sıcak bir aday olarak kabul, ama bu başlanan inanamadımdört yıl önceve fark edilmeden kaldı şimdiye kadar...)

Bunu Paylaş:
  • Google+
  • E-Posta
Etiketler:

YORUMLAR

SPONSOR VİDEO

Rastgele Yazarlar

  • Amir Parmar

    Amir Parmar

    25 Kasım 2010
  • bigky226

    bigky226

    11 HAZİRAN 2006
  • CareyHolzman

    CareyHolzman

    24 Ocak 2008