JDK-6837951 : Incorrect answer while summing serialized / deserialized BigDecimals
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.math
  • Affected Version: 6u14
  • Priority: P1
  • Status: Resolved
  • Resolution: Fixed
  • OS: solaris_8,windows_xp
  • CPU: x86
  • Submitted: 2009-05-06
  • Updated: 2011-02-16
  • Resolved: 2009-05-08
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 6 JDK 7
6u14 b07Fixed 7Fixed
Related Reports
Relates :  
Relates :  
Description
FULL PRODUCT VERSION :
java version "1.6.0_14-ea"
Java(TM) SE Runtime Environment (build 1.6.0_14-ea-b03)
Java HotSpot(TM) Client VM (build 14.0-b12, mixed mode)

ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows XP / Professional Edition x64 / 2003 / SP3

A DESCRIPTION OF THE PROBLEM :
When this program is run with JDKs prior to jdk1.6.0_14 the correct answer is given and no errors occur.  When run with jdk1.6.0_14 (up to b05) then the incorrect answer is given and an error is detected.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
No error printed on System.out.  The answer is:  1108240.4666936930160
ACTUAL -
An error printed on System.out.  Bad answer is:  781240.4666940200160

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
package com.certive.cs.util;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.math.BigDecimal;
import java.math.BigInteger;

public class BDTest
{
   protected static String[] S_arrayOfStringValues = new String[]
   {
      "117000.0", "83200.0", "0", "0", "0", "5280.0", "0", "69426.0",
      "6500.0", "7603.4919740918050", "7819.1205269716930", "9506.8541926295180",
      "31600.0", "39828.0", "71200.0", "24000.0", "14760.0", "84000.0",
      "0", "113184.0", "96333.0", "279000.0", "48000.0"
   };
   
   /**
    * @param args
    */
   public static void main(String[] args)
   {
      String         sFile = "C:/BDData.bd";
      BigDecimal[]   arrayOfBigDecimals = null;
            
      // convert strings to BigDecimals
      arrayOfBigDecimals = toBigDecimals( S_arrayOfStringValues );
      
      // write to the file?
      write( arrayOfBigDecimals, sFile );
            
      // read from the file?
      arrayOfBigDecimals = read( sFile );
      
      // print out the sum of the big decimals
      System.out.print( sum( arrayOfBigDecimals ) );
   }
   
   protected static void write( BigDecimal[] abd, String sFile )
   {
      try
      {
         FileOutputStream  fos = new FileOutputStream( sFile );
         DataOutputStream  dos = new DataOutputStream( fos );
         int               iValues = abd.length;
         
         dos.writeInt( iValues );
         
         for( int i = 0; i < iValues; i++ )
         {
            BigDecimal  bd = abd[i];
                        
            byte[]      aBytes = bd.unscaledValue().toByteArray();
            short       iBytes = (short) aBytes.length;
            short       iScale = (short) bd.scale();

            dos.writeShort( iBytes );
            dos.write( aBytes );
            dos.writeShort( iScale );
         }
         
         if ( dos != null )
            dos.close();
      }
      catch( Throwable t )
      {
         t.printStackTrace();
      }
   }
   
   protected static BigDecimal[] read( String sFile)
   {
      BigDecimal[]   abd = null;
      
      try
      {
         FileInputStream   fis = new FileInputStream( sFile );
         DataInputStream   dis = new DataInputStream( fis );
         int               iValues = dis.readInt();
         
         abd = new BigDecimal[iValues];
         
         for( int i = 0; i < iValues; i++ )
         {
            int         iBytes = dis.readShort();
            byte[]      aBytes = new byte[iBytes];
            
            dis.read( aBytes );
            
            int         iScale = dis.readShort();
            
            abd[i] = new BigDecimal(new BigInteger(aBytes), iScale);
         }
         
         if ( dis != null )
            dis.close();
      }
      catch( Throwable t )
      {
         t.printStackTrace();
      }
      
      return abd;
   }
   
   protected static BigDecimal sum( BigDecimal[] abd)
   {
      int         iLen = abd == null ? 0 : abd.length;
      BigDecimal  bdSum = iLen > 0 ? new BigDecimal( 0 ) : null;
      
      for( int i = 0; i < iLen; i++ )
      {
         BigDecimal  bdAdd = abd[i];
         BigDecimal  bdNewSum = bdSum.add( bdAdd );
         System.out.println( "Start: " + bdSum + "  Add: " + abd[i] + "  End: " + bdNewSum );
         
         if ( bdNewSum.intValue() == bdSum.intValue() && bdAdd.intValue() != 0 )
            System.out.println( "ERROR!  We didn't add a non-zero correctly!" );
         
         bdSum = bdNewSum;
      }
      
      return bdSum;
   }
   
   protected static BigDecimal[] toBigDecimals( String[] strValues )
   {
      int            iLen = strValues == null ? 0 : strValues.length;
      BigDecimal[]   abd = new BigDecimal[iLen];
      for ( int i = 0; i < iLen; i++ )
      {
         abd[i] = new BigDecimal( strValues[i] );
      }
      return abd;
   }
}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
None.  The JDK simply does not give the correct answer.

Release Regression From : 6u14
The above release value was the last known release where this 
bug was not reproducible. Since then there has been a regression.

Comments
EVALUATION The root cause of the failure is that in the BigDecimal(BigInteger ..) constructor, once we detect that the unscaled value can be made to fit into long, we forgot to set the intVal to be null. The BigDecimal.add method and many other places after the optimization put in assumed that if the unscaled value of the BigDecimal is not INFLATED, the intVal field should be null. The fix is to set the intVal field to be null once we detect that the intCompact field is not INFLATED.
07-05-2009