0

I have a Java function that's supposed to calculate dividend interest and Java keeps getting the output off by several digits. I'm having issue finding where the rounding error keeps coming up, so I made a crude copy of my function that breaks the calculation down line by line.

public static double applyDivInterest2(double balance, double totalRate, double gRate, double days)
{
    DecimalFormat df = new DecimalFormat("#.##");
    double result;

    /*Access version of code (can be ignored)
     *dblInterest = prmBalance * ((1 + prmTotalIntRate) ^ dblExponent1 - (1 + prmGuarIntRate) ^ dblExponent1) * _
                                 ((1 + prmRolloverTotalIntRate) ^ dblExponentn - (1 + prmGuarIntRate) ^ dblExponentn) / _
                                 ((1 + prmRolloverTotalIntRate) ^ dblExponent1 - (1 + prmGuarIntRate) ^ dblExponent1)

                    balance * (1+totalRate^(1/365) - 1+gRate^(1/365))*
                              (1+(totalRate-gRate)^days/365 - 1+gRate^days/365)/
                              (1+(totalRate-gRate)^1/365 - 1+gRate^1/365)/

                    balance * (calcTotal - guaranteeTotal) *
                              (rolloverTotal - guaranteeTotalDays)/
                              (rolloverTotalDay - guaranteeTotal)
     */
    double day = 1.00/365.00;
    double dayn = (days-1.00)/365.00;
    double newDays = days/365.00;
    double rollover = totalRate-gRate;
    double newtotal = 1.0 + totalRate;
    double newguarantee = 1.0 + gRate;
    double newrollover = 1.0+rollover;

    double calcTotal = (Math.pow(newtotal,day));
    double guaranteeTotal = (Math.pow(newguarantee,day));
    double guaranteeTotalDays = (Math.pow(newguarantee,newDays));
    double rolloverTotalDays = (Math.pow(newrollover,newDays));
    double rolloverTotalDay = (Math.pow(newrollover,day));

    if(totalRate == gRate)
        result = balance*(calcTotal - guaranteeTotal)*days*guaranteeTotalDays;
    else
        result = balance*((calcTotal-guaranteeTotal)*(rolloverTotalDays-guaranteeTotalDays))/(1.0000272615520089941-guaranteeTotal);
    return Double.valueOf(df.format(result));
}

Test Variables:

balance = 1000000000
gRate = 0.03
totalRate = 0.04
days = 14

Output: 370889.63 Actual: 371019.16

I would use BigDecimal, but I'm raising double values to the power of double values; BigDecimal, to my knowledge, only raises doubles to an int value. Any ideas on how to make this more precise?

EDIT: I have tried switch 365 days to 365.25. There is no improvement. Earlier, I did something considered unethical and changed 1.0 to some random value (0.9999867 or something can't remember) and for this particular test case it worked, but failed on any other input.

EDIT 2: I have tried to use the X^(A+B)=X^A*X^B method by putting that as a separate function. I have rewritten my function to include BigDecimal functionality:

public static BigDecimal testFunc(BigDecimal base, BigDecimal power)
{
    MathContext mcontext = new MathContext(9,RoundingMode.DOWN); //12 works
    int signOf2 = power.signum();
    // Perform X^(A+B)=X^A*X^B (B = remainder)
    double dn1 = base.doubleValue();
    // Compare the same row of digits according to context
    power = power.multiply(new BigDecimal(signOf2)); // n2 is now positive
    BigDecimal remainderOf2 = power.remainder(BigDecimal.ONE);
    BigDecimal n2IntPart = power.subtract(remainderOf2);
    // Calculate big part of the power using context -
    // bigger range and performance but lower accuracy
    BigDecimal intPow = base.pow(n2IntPart.intValueExact(),mcontext);
    BigDecimal doublePow = new BigDecimal(Math.pow(dn1, remainderOf2.doubleValue()));
    BigDecimal result = intPow.multiply(doublePow);
    return result;
}
public static double applyDivInterest(double balance, double totalRate, double gRate, double in_days)
{
    MathContext mcontext  = new MathContext(15);

    DecimalFormat df = new DecimalFormat("#.###");
    //MONETARY      
    BigDecimal bd_balance = new BigDecimal(balance, mcontext);
    BigDecimal bd_tRate   = new BigDecimal(totalRate, mcontext);
    BigDecimal bd_gRate   = new BigDecimal(gRate, mcontext);
    BigDecimal bd_r   = bd_tRate.subtract(bd_gRate, mcontext);

    //DAYS
    BigDecimal bd_365     = new BigDecimal(365, mcontext);
    BigDecimal bd_days    = new BigDecimal(in_days, mcontext);
    BigDecimal bd_days_minus    = new BigDecimal(in_days-1, mcontext);

    //FINAL DAYS
    BigDecimal day = BigDecimal.ONE.divide(bd_365, mcontext);   //  1/365
    BigDecimal dayn = bd_days_minus.divide(bd_365, mcontext);   //  (days-1)/365    GUARANTEE == TOTAL ONLY!
    BigDecimal days = bd_days.divide(bd_365, mcontext);     //  days/365

    //FINAL RATES
    BigDecimal bd_total = BigDecimal.ONE.add(bd_tRate, mcontext);
    BigDecimal bd_guarantee = BigDecimal.ONE.add(bd_gRate, mcontext);
    BigDecimal bd_rollover = BigDecimal.ONE.add(bd_r, mcontext);

    BigDecimal calcTotal = testFunc(bd_total,day);
    BigDecimal guaranteeTotal = testFunc(bd_guarantee,day);

    BigDecimal rolloverTotalDays = testFunc(bd_rollover,days);
    BigDecimal guaranteeTotalDays = testFunc(bd_guarantee,days);

    BigDecimal rolloverTotal = testFunc(bd_rollover,day);

    BigDecimal Stage1 = calcTotal.subtract(guaranteeTotal, mcontext);
    BigDecimal Stage2 = rolloverTotalDays.subtract(guaranteeTotalDays, mcontext);
    BigDecimal Stage3 = rolloverTotal.subtract(guaranteeTotal, mcontext);
    BigDecimal Stage4 = Stage1.multiply(Stage2, mcontext);
    BigDecimal Stage5 = Stage4.divide(Stage3, mcontext);
    BigDecimal finalStage = bd_balance.multiply(Stage5);

    return Double.valueOf(df.format(finalStage)).doubleValue();
}

It still keeps outputting the same error.

Phreakradio
  • 176
  • 1
  • 18
  • 1
    Where did you get "Actual" value? I wonder if instead of 365.00 that source uses 365.25 or something close. I also wonder what the constant 1.0000272615520089941 could have to do with error. – DSlomer64 Jun 24 '14 at 18:43
  • I modified the code to make daysPerYear an argument, and tried it with 365.25. That gave 370764.43, which is still a bit less than Actual. – Patricia Shanahan Jun 24 '14 at 19:12
  • @DSlomer64 Forgot to give a backstory. I'm actually converting calculations that are done by a VBA app into a Java app. The VBA app is considered correct, so whatever value it spits out (actual) is the one to test for I also tried the quarter of a day approach. – Phreakradio Jun 24 '14 at 20:33
  • also http://stackoverflow.com/questions/16441769/javas-bigdecimal-powerbigdecimal-exponent-is-there-a-java-library-that-does – Leo Jun 24 '14 at 20:56

1 Answers1

0

You can try the Apache Commons Math library, specifically the BigFraction class. I think this will do what you want.

Alex Beardsley
  • 20,988
  • 15
  • 52
  • 67