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.