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