A "bug" in Java about BigDecimal that causes a double loss of precision

Source: Internet
Author: User

Background

In the blog disgusting 0.5 rounding problem article see a question about 0.5 not rounding correctly. The main thing is that a double conversion to BigDecimal does not result in the correct rounding:

 Public class bigdecimaltest {    publicstaticvoid  main (string[] args) {          Double d = 301353.05;         New BigDecimal (d);        System.out.println (decimal); // 301353.0499999999883584678173065185546875        System.out.println (Decimal.setscale (1, roundingmode.half_up)); // 301353.0     }}

The result of the output is:

301353.0499999999883584678173065185546875
301353.0

The result is obviously not what we expected, we hope to get 301353.1.

Reason

Allowing discerning eye to see the other problem at a glance--BigDecimal's constructor, public BigDecimal (double val), loses the precision of the double parameter , resulting in an incorrect result. So the point is: BigDecimal's constructor public BigDecimal (double val) loses the precision of the double parameter.

The way to solve

Because the above found the reason, so it is very good solution. As long as the loss of the precision of double to BigDecimal is prevented, there will be no problem.

1) It's easy to think of the first workaround: use BigDecimal's string-argument constructor : Public BigDecimal (string val) instead.

 Public class bigdecimaltest {    publicstaticvoid  main (string[] args) {          Double d = 301353.05;        System.out.println ( new BigDecimal (new  Double (d). toString ()));        System.out.println (new BigDecimal ("301353.05"));        System.out.println (new BigDecimal ("301353.895898895455898954895989"));}    }

Output Result:

301353.05
301353.05
301353.895898895455898954895989

We see no loss of precision , rounding will certainly not be wrong.

2) The BigDecimal constructor public BigDecimal (double val) loses the precision of the double parameter, which might be counted as a bug in the JDK. Since there is a bug, then we should solve it. The above approach is to bypass it. Now we implement our own double-to-BigDecimal conversion, and guarantee that in some cases the precision of the double can be completely lost.

ImportJava.math.BigDecimal; Public classBigdecimalutil { Public StaticBigDecimal Doubletobigdecimal (Doubled) {String Doublestr=string.valueof (d); if(Doublestr.indexof (".")! =-1){            intPointlen = Doublestr.replaceall ("\\d+\\.", ""). Length ();//The number of digits after the decimal point is obtainedPointlen = pointlen > 16? 16:pointlen;//double the number of digits after the maximum effective decimal point is            DoublePOW = Math.pow (10, Pointlen);
Long tmp = (long) (d * POW); return NewBigDecimal (TMP). Divide (NewBigDecimal (POW)); } return NewBigDecimal (d); } Public Static voidMain (string[] args) {//System.out.println (Doubletobigdecimal (301353.05));//System.out.println (Doubletobigdecimal ( -301353.05));//System.out.println (Doubletobigdecimal (New Double ( -301353.05)));//System.out.println (Doubletobigdecimal (301353));//System.out.println (Doubletobigdecimal (New Double ( -301353))); DoubleD = 301353.05;//5898895455898954895989;System.out.println (Doubletobigdecimal (d)); System.out.println (d); System.out.println (NewDouble (d). toString ()); System.out.println (NewBigDecimal (NewDouble (d). toString ())); System.out.println (NewBigDecimal (d)); }}

Output Result:

301353.05
301353.05
301353.05
301353.
301353.0499999999883584678173065185546875

Above we have written a tool class, to achieve a double to BigDecimal "no loss" double precision conversion. The method is to convert a double with a valid number after the decimal point to a double that does not have a valid number after the decimal point, and then returns the previous size after converting to BigDecimal, using the divide of BigDecimal.

The above results seem perfect, but there are problems. We also said that "in some cases you can never lose the precision of a double", let's look at an example:

     Public Static voidMain (string[] args) {DoubleD = 301353.05;        System.out.println (Doubletobigdecimal (d));        System.out.println (d); System.out.println (NewDouble (d). toString ()); System.out.println (NewBigDecimal (NewDouble (d). toString ())); System.out.println (NewBigDecimal (d)); System.out.println ("========================="); D= 301353.895898895455898954895989;        System.out.println (Doubletobigdecimal (d));        System.out.println (d); System.out.println (NewDouble (d). toString ()); System.out.println (NewBigDecimal (NewDouble (d). toString ())); System.out.println (NewBigDecimal (d)); System.out.println (NewBigDecimal ("301353.895898895455898954895989")); System.out.println ("========================="); D= 301353.46899434;        System.out.println (Doubletobigdecimal (d));        System.out.println (d); System.out.println (NewDouble (d). toString ()); System.out.println (NewBigDecimal (NewDouble (d). toString ())); System.out.println (NewBigDecimal (d)); System.out.println ("========================="); D= 301353.45789666;        System.out.println (Doubletobigdecimal (d));        System.out.println (d); System.out.println (NewDouble (d). toString ()); System.out.println (NewBigDecimal (NewDouble (d). toString ())); System.out.println (NewBigDecimal (d)); }

Output Result:

301353.05
301353.05
301353.05
301353.05
301353. 049 9999999883584678173065185546875
=========================
301353.8958988954 4
301353.89589889545
301353.89589889545
301353.89589889545
301353.8958988954545 93002796173095703125
301353.89589889545589 8954895989
=========================
301353.46899434
301353.46899434
301353.46899434
301353.46899434
301353.468994339 9862386286258697509765625
===== ====================
301353.45789666
301353.45789666
301353.45789666
301353.45789666
301353.4578966600238345563411712646484375
We can see that the Doubletobigdecimal method we implement is less than the number of digits after the decimal point of a double (For example, only 5, 6-bit), can guarantee a complete non-loss of precision .

The number of digits after the decimal point of a double is relatively long, and theD POW will have a loss of precision, so the final result will also have a loss of precision. So if the number of digits after the decimal point is relatively long, or use the BigDecimal String parameter of the constructor as well, only after the decimal point of the number of relatively young, you can use their own implementation of the Doubletobigdecimal method.

Because we see the original double after the conversion of the BigDecimal number of the last one of 5, one is 4, the reason is in the above conversion method:

Long tmp = (long) (d * POW);

This step can be a small loss of precision, because D is a double, d * POW after a double (but after the decimal point is 0, so there is no precision loss to long conversion), so there will be a small loss of precision ( The calculation of a double is always possible with a loss of precision. However, this loss of precision and BigDecimal's constructor public BigDecimal (double val) of the accuracy of the loss, will not appear so abrupt (perhaps we write our own doubletobigdecimal is also a problem, welcome guidance).

Summary :

If you need to guarantee precision, it is best not to use the constructor of the double parameter of BigDecimal, because there is a possibility of loss of double parameter precision, preferably using the constructor of the bigdecimal string parameter . It is best to eliminate the constructor of the double argument that uses BigDecimal.

A "bug" in Java about BigDecimal that causes a double loss of precision

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.