FULL PRODUCT VERSION :
OS X:
Java(TM) SE Runtime Environment (build 1.8.0_25-b17)
Java HotSpot(TM) 64-Bit Server VM (build 25.25-b02, mixed mode)
Windows:
java version "1.8.0_25"
Java(TM) SE Runtime Environment (build 1.8.0_25-b18)
Java HotSpot(TM) 64-Bit Server VM (build 25.25-b02, mixed mode)
ADDITIONAL OS VERSION INFORMATION :
OS X Version 10.9.5
Microsoft Windows 7 Home Premium 6.1.6701.17514
A DESCRIPTION OF THE PROBLEM :
BigDecimal division with rounding mode argument and optionally with scale delivers wrong result for the following arguments:
dividend: Long.MAX_VALUE or -LONG.MAX_VALUE, any scale
divisor: value one with scale 17 (i.e. 1.00000000000000000)
(examples see code BigDecimalDivideByOne)
By the javadoc specification the preferred scale of divide(BigDecimal, RoundingMode) is this.scale, hence the input value should be identical to the output value. The method delegates to divide(BigDecimal divisor, int scale, int roundingMode) hence this method also delivers wrong results for the same arguments (with any scale).
The problem can also be triggered for a divisor different from one if all of the following criteria hold:
* dividend scale is negative: scale1 < 0
* divisor is 10 to the power of -scale1: divisor=10^-scale1
* divisor scale is dividend scale plus 17: scale2 = scale1 + 17
* result scale is 0
(examples see code BigDecimalDivideByPow10)
The problem does not occur if run with JRE6 or JRE7 hence it must have been introduced with JDK8.
REGRESSION. Last worked in version 7u72
ADDITIONAL REGRESSION INFORMATION:
OS X:
Java(TM) SE Runtime Environment (build 1.7.0_72-b14)
Java HotSpot(TM) 64-Bit Server VM (build 24.72-b04, mixed mode)
Windows:
java version "1.7.0_72"
Java(TM) SE Runtime Environment (build 1.7.0_72-b14)
Java HotSpot(TM) 64-Bit Server VM (build 24.72-b04, mixed mode)
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Execute this code:
public static void main(String[] args) {
//problematic divisor: one with scale 17
BigDecimal one = BigDecimal.ONE.setScale(17);
int scale;
RoundingMode rounding = RoundingMode.DOWN;
//scale 17 : dividend and divisor have same scale
scale = 17;
System.out.println(BigDecimal.valueOf(Long.MAX_VALUE, scale).divide(one, rounding));
System.out.println(BigDecimal.valueOf(-Long.MAX_VALUE, scale).divide(one, rounding));
//scale 0
System.out.println(BigDecimal.valueOf(Long.MAX_VALUE).divide(one, rounding));
System.out.println(BigDecimal.valueOf(-Long.MAX_VALUE).divide(one, rounding));
//any other scale, any other rounding mode
scale = 100;//any scale reproduces the error
System.out.println(BigDecimal.valueOf(Long.MAX_VALUE, scale).divide(one, rounding));
rounding = RoundingMode.UNNECESSARY;
System.out.println(BigDecimal.valueOf(-Long.MAX_VALUE, scale).divide(one, rounding));
}
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
92.23372036854775807
-92.23372036854775807
9223372036854775807
-9223372036854775807
9.223372036854775807E-82
-9.223372036854775807E-82
ACTUAL -
92.23372032559808512
-92.23372032559808512
9223372032559808512
-9223372032559808512
9.223372032559808512E-82
Exception in thread "main" java.lang.ArithmeticException: Rounding necessary
ERROR MESSAGES/STACK TRACES THAT OCCUR :
Exception in thread "main" java.lang.ArithmeticException: Rounding necessary
at java.math.BigDecimal.commonNeedIncrement(BigDecimal.java:4148)
at java.math.BigDecimal.needIncrement(BigDecimal.java:4204)
at java.math.BigDecimal.divideAndRound128(BigDecimal.java:4867)
at java.math.BigDecimal.multiplyDivideAndRound(BigDecimal.java:4789)
at java.math.BigDecimal.divide(BigDecimal.java:5155)
at java.math.BigDecimal.divide(BigDecimal.java:1561)
at java.math.BigDecimal.divide(BigDecimal.java:1641)
at BigDecimalDivide.main(BigDecimalDivide.java:26)
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
import java.math.BigDecimal;
import java.math.RoundingMode;
public class BigDecimalDivideByOne {
public static void main(String[] args) {
//problematic divisor: one with scale 17
BigDecimal one = BigDecimal.ONE.setScale(17);
int scale;
RoundingMode rounding = RoundingMode.DOWN;
//scale 17 : dividend and divisor have same scale
scale = 17;
System.out.println(BigDecimal.valueOf(Long.MAX_VALUE, scale).divide(one, rounding));
System.out.println(BigDecimal.valueOf(-Long.MAX_VALUE, scale).divide(one, rounding));
//scale 0
System.out.println(BigDecimal.valueOf(Long.MAX_VALUE).divide(one, rounding));
System.out.println(BigDecimal.valueOf(-Long.MAX_VALUE).divide(one, rounding));
//any other scale, any other rounding mode
scale = 100;//any scale reproduces the error
System.out.println(BigDecimal.valueOf(Long.MAX_VALUE, scale).divide(one, rounding));
rounding = RoundingMode.UNNECESSARY;
System.out.println(BigDecimal.valueOf(-Long.MAX_VALUE, scale).divide(one, rounding));
}
}
--
import java.math.BigDecimal;
import java.math.RoundingMode;
public class BigDecimalDivideByPow10 {
public static void main(String[] args) {
final RoundingMode rounding = RoundingMode.DOWN;
int scale1, scale2;
BigDecimal pow10;
scale1 = -100;
scale2 = -83;
pow10 = BigDecimal.ONE.movePointRight(-scale1).setScale(scale2);
System.out.println(BigDecimal.valueOf(Long.MAX_VALUE, scale1).divide(pow10, 0, rounding));
System.out.println(BigDecimal.valueOf(-Long.MAX_VALUE, scale1).divide(pow10, 0, rounding));
scale1 = -17;
scale2 = 0;
pow10 = BigDecimal.ONE.movePointRight(-scale1).setScale(scale2);
System.out.println(BigDecimal.valueOf(Long.MAX_VALUE, scale1).divide(pow10, 0, rounding));
System.out.println(BigDecimal.valueOf(-Long.MAX_VALUE, scale1).divide(pow10, 0, rounding));
scale1 = -1;
scale2 = 16;
pow10 = BigDecimal.ONE.movePointRight(-scale1).setScale(scale2);
System.out.println(BigDecimal.valueOf(Long.MAX_VALUE, scale1).divide(pow10, 0, rounding));
System.out.println(BigDecimal.valueOf(-Long.MAX_VALUE, scale1).divide(pow10, 0, rounding));
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
use scale 18 and re-scale result back to scale 17