JDK-6362557 : BigDecimal.add(BigDecimal, MathContext) can return an incorrectly rounded result
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.math
  • Affected Version: 5.0,5.0u6
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: linux,windows_xp
  • CPU: x86
  • Submitted: 2005-12-13
  • Updated: 2010-04-02
  • Resolved: 2006-02-04
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.
Other JDK 6
5.0u8Fixed 6 b71Fixed
Related Reports
Duplicate :  
Relates :  
Description
BigDecimal.add(BigDecimal, MathContext) can return an incorrectly rounded result.
The behaviour changes depending on whether this.precision() has been called beforehand or not.

OPERATING SYSTEM(S):
Windows XP

FULL JDK VERSION(S):
java version "1.5.0_06"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_06-b05)
Java HotSpot(TM) Client VM (build 1.5.0_06-b05, mixed mode)

DESCRIPTION:

import java.math.*;
class Test {
        public static void main(String[] args) {
                MathContext mc = new MathContext(1,RoundingMode.DOWN);
                BigDecimal a = BigDecimal.valueOf(1999, -1); //value is equivalent to 19990
                BigDecimal b = BigDecimal.valueOf(1999, -1);
                
                //Calling precision() should not change behaviour of the BigDecimal
                b.precision();
                
                //Adding 1, the unrounded result is 19991, so should round down to 1E+4
                BigDecimal resultA = a.add(BigDecimal.ONE, mc);
                BigDecimal resultB = b.add(BigDecimal.ONE, mc);
                
                System.out.println(resultA); //prints 1E+4
                System.out.println(resultB); //prints 2E+4
        }
} 

In BigDecimal.add(BigDecimal, MathContext) there is a read from the precision field rather than a call to the precision method.
This causes the change in behaviour depending on whether precision is called or not in the testcase above.
Note that it appears the correct result is obtained when precision() is _not_ called, implying that the logic in BigDecimal.add which uses the precision value might be wrong.

Comments
SUGGESTED FIX src/share/classes/java/math>sccs sccsdiff -r1.60 -r1.61 BigDecimal.java ------- BigDecimal.java ------- 1086c1086 < int padding = checkScale((long)lhs.scale - augend.scale); --- > long padding = (long)lhs.scale - augend.scale; 1088,1103c1088 < // if one operand is < 0.01 ulp of the other at full < // precision, replace it by a 'sticky bit' of +0.001/-0.001 ulp. < // [In a sense this is an 'optimization', but it also makes < // a much wider range of additions practical.] < if (padding < 0) { // lhs will be padded < int ulpscale = lhs.scale - lhs.precision + mc.precision; < if (augend.scale - augend.precision() > ulpscale + 1) { < augend = BigDecimal.valueOf(augend.signum(), ulpscale + 3); < } < } else { // rhs (augend) will be padded < int ulpscale = augend.scale - augend.precision + mc.precision; < if (lhs.scale - lhs.precision() > ulpscale + 1) < lhs = BigDecimal.valueOf(lhs.signum(), ulpscale + 3); < } < BigDecimal arg[] = new BigDecimal[2]; < arg[0] = lhs; arg[1] = augend; --- > BigDecimal arg[] = preAlign(lhs, augend, padding, mc); 1105c1090 < lhs = arg[0]; --- > lhs = arg[0]; 1113a1099,1164 > * Returns an array of length two, the sum of whose entries is > * equal to the rounded sum of the {@code BigDecimal} arguments. > * > * <p>If the digit positions of the arguments have a sufficient > * gap between them, the value smaller in magnitude can be > * condensed into a &quot;sticky bit&quot; and the end result will > * round the same way <em>if</em> the precision of the final > * result does not include the high order digit of the small > * magnitude operand. > * > * <p>Note that while strictly speaking this is an optimization, > * it makes a much wider range of additions practical. > * > * <p>This corresponds to a pre-shift operation in a fixed > * precision floating-point adder; this method is complicated by > * variable precision of the result as determined by the > * MathContext. A more nuanced operation could implement a > * &quot;right shift&quot; on the smaller magnitude operand so > * that the number of digits of the smaller operand could be > * reduced even though the significands partially overlapped. > */ > private BigDecimal[] preAlign(BigDecimal lhs, BigDecimal augend, > long padding, MathContext mc) { > assert padding != 0; > BigDecimal big; > BigDecimal small; > > if (padding < 0) { // lhs is big; augend is small > big = lhs; > small = augend; > } else { // lhs is small; augend is big > big = augend; > small = lhs; > } > > /* > * This is the estimated scale of an ulp of the result; it > * assumes that the result doesn't have a carry-out on a true > * add (e.g. 999 + 1 => 1000) or any subtractive cancellation > * on borrowing (e.g. 100 - 1.2 => 98.8) > */ > long estResultUlpScale = (long)big.scale - big.precision() + mc.precision; > > /* > * The low-order digit position of big is big.scale(). This > * is true regardless of whether big has a positive or > * negative scale. The high-order digit position of small is > * small.scale - (small.precision() - 1). To do the full > * condensation, the digit positions of big and small must be > * disjoint *and* the digit positions of small should not be > * directly visible in the result. > */ > long smallHighDigitPos = (long)small.scale - small.precision() + 1; > if (smallHighDigitPos > big.scale + 2 && // big and small disjoint > smallHighDigitPos > estResultUlpScale + 2) { // small digits not visible > small = BigDecimal.valueOf(small.signum(), > this.checkScale(Math.max(big.scale, estResultUlpScale) + 3)); > } > > // Since addition is symmetric, preserving input order in > // returned operands doesn't matter > BigDecimal[] result = {big, small}; > return result; > } > > /** 2661c2712 < * <tt>BigDecimal</tt> has a positive scale, the string resulting --- > * <tt>BigDecimal</tt> has a negative scale, the string resulting 3295,3299c3346,3350 < * Check a scale for Underflow or Overflow. If this < * BigDecimal. is uninitialized or initialized and nonzero, throw < * an exception if the scale is out of range. If this is zero, < * saturate the scale to the extreme value of the right sign if < * the scale is out of range. --- > * Check a scale for Underflow or Overflow. If this BigDecimal is > * uninitialized or initialized and nonzero, throw an exception if > * the scale is out of range. If this is zero, saturate the scale > * to the extreme value of the right sign if the scale is out of > * range.
31-01-2006

EVALUATION Will investigate.
09-01-2006