JDK-8066842 : java.math.BigDecimal.divide(BigDecimal, RoundingMode) produces incorrect result
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.math
  • Affected Version: 8u25
  • Priority: P3
  • Status: Closed
  • Resolution: Fixed
  • OS: os_x
  • CPU: x86
  • Submitted: 2014-12-07
  • Updated: 2016-08-24
  • Resolved: 2015-02-12
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 8 JDK 9
8u60Fixed 9 b51Fixed
Related Reports
Relates :  
Relates :  
Description
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


Comments
Review thread: http://mail.openjdk.java.net/pipermail/core-libs-dev/2015-January/030849.html
16-01-2015

The cause looks to be that in BigDecimal.divWord() the size of the quotient overflows that range of an int so when it is masked into the return value some information is lost.
16-01-2015

Verified that the inputs dividendHi and dividendLo supplied to divideAndRound128() are correct so the problem must be due to something within divideAndRound128() itself.
15-01-2015

Testing with a JDK8u build shows that this problem was apparently introduced by this changeset: changeset: 4546:ffada2ce20e5 user: darcy date: Thu Sep 01 23:00:09 2011 -0700 summary: 7082971: More performance tuning of BigDecimal and other java.math classes When java.math is reverted to revision 4545 the expected output is obtained, but reverting to revision 4546 still exhibits the exception, as does JDK 9.
14-01-2015

The issue looks related to JDK-6876282.
08-12-2014

Tested with JDK 8u25, 8u40 and 9ea to confirm the submitter claims. JDK 7u72 displayed correct results.
08-12-2014