gpt4 book ai didi

java - 为什么 toString 无法在不可变的 BigDecimal 上产生正确的值?

转载 作者:IT老高 更新时间:2023-10-28 20:44:25 25 4
gpt4 key购买 nike

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

编译

>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() 将是正确的。

或者,如果在 divide 操作之上添加另一个 x.toString() 调用,则 both x.toString( ) 调用会产生正确的值!

在我正在测试的机器上,Windows 7 64 位,使用 java 7 和 8(32 位和 64 位版本)的行为是相同的,但是在 https://ideone.com/ 在线测试对 java 7 和 java 8 产生不同的结果。

使用 java 7,x 的值是正确的:http://ideone.com/P1sXQQ,但使用 java 8,它的值是不正确的:http://ideone.com/OMAq7a

此外,这种行为并不是 x 的特定值所独有的,因为在将它们作为第一个操作数传递后,对其他大约 1500 位以上的 BigDecimal 调用 toString divide 操作也会产生不正确的值。

对此有何解释?

divide 操作似乎正在改变其操作数的后续 toString 调用所产生的值。

这是否发生在您的平台上?

编辑:

问题似乎仅限于 java 8 运行时,因为上述使用 java 7 编译的程序在使用 java 7 运行时执行时会产生正确的输出,但在使用 java 8 运行时执行时会产生错误的输出。

编辑:

我已经使用早期访问 jre1.8.0_60 进行了测试,并且该错误没有出现,并且根据 Marco13 的回答,它已在 build 51 中修复。Oracle JDK 8 产品二进制文件仅在更新 40 时才出现,因此可能需要一段时间固定版本被广泛使用。

最佳答案

找出奇怪行为的原因并不难。

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(它是存储在 BigDecimal 中的 BigInteger)作为第一个参数直接传递给此方法:

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

最后,这里采用 second divideAndRound 的路径,再次传递 dividend (即 intVal 的原始 BigDecimal),以这个代码结束:

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 是一个mutable BigInteger,它被创建为mag 数组的 BigInteger 存储在原始调用的 BigDecimal x 中。除法修改了 mag 字段,因此修改了(现在不那么不变的)BigDecimal 的状态。

这显然是 divide 方法之一的实现中的错误。我已经开始跟踪 OpenJDK 的变更集,但还没有找到明确的罪魁祸首。 (编辑:查看下面的更新)

(附注:在进行除法之前调用 x.toString() 并不能真正避免,而只是隐藏错误:它会导致在内部创建正确状态的字符串缓存。打印了正确的值,但内部状态仍然是错误的 - 至少可以说这是令人担忧的......)


Update: To confirm what @MikeM said: Bug has been listed on openjdk bug list and it has been resolved in JDK8 Build 51

Update : Kudos to Mike and exex zian for digging out the bug reports. According to the discussion there, the bug was introduced with this changeset.(Admittedly, while skimming through the changes, I also considered this as a hot candidate, but could not believe that this was introduced four years ago and remained unnoticed until now...)

关于java - 为什么 toString 无法在不可变的 BigDecimal 上产生正确的值?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29328785/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com