Wednesday, July 15, 2009

Java and financial calculations

In general, Java isn't designed for financial calculations, but scientific ones. Java's float and double types don't have the rounding (to cents) and necessary precision for financial calculations. I wrote a small program to illustrate this that calculates monthly mortgage payments and the total payment. Here is the output:
Actual monthly payment = 2704.65
Actual total payment = 973674.00
---------- float ----------
Float monthly payment = 2704.654
Float total payment = 973675.44
---------- double ----------
Double monthly payment = 2704.6540626894594
Double total payment = 973675.4625682053
---------- BigDecimal ----------
BigDecimal monthly payment = 2704.65
BigDecimal total payment = 973674.00


The first two lines are what should be produced. The second and third sections are what Java's float and double types produce, leading to one overpaying their loan. The third section uses Java's BigDecimal class, setting all the precision and rounding values. It's messy to say the least, but this is what it takes to get the values correct. Here is the program:
import java.math.BigDecimal;
import java.math.MathContext;

public class Amort
{
public static void main (String args[])
{
System.out.println ("Actual monthly payment = 2704.65");
System.out.println ("Actual total payment = 973674.00");
System.out.println ("---------- float ----------");
floater();
System.out.println ("---------- double ----------");
doubler();
System.out.println ("---------- BigDecimal ----------");
bigger();
}

static void doubler()
{
double principal = 417000.00;
double annual = .0675;
double rate = annual / 12.0;
double years = 30;
double months = 12 * years;

double monthly = (principal * rate) /
(1.0 - (Math.pow ((1.0 + rate), -months)));

System.out.println ("Double monthly payment = " + monthly);
System.out.println ("Double total payment = " + monthly * months);
}

static void floater()
{
float principal = 417000.00F;
float annual = .0675F;
float rate = annual / 12.0F;
float years = 30.0F;
float months = 12.0F * years;

float monthly = (principal * rate) /
(float) (1.0F - (Math.pow ((1.0 + rate), -months)));

System.out.println ("Float monthly payment = " + monthly);
System.out.println ("Float total payment = " + monthly * months);
}

static void bigger()
{
MathContext mc2 = new MathContext (2);
MathContext mc4 = new MathContext (4);
MathContext mc6 = new MathContext (6);
MathContext mc8 = new MathContext (8);

BigDecimal principal = new BigDecimal (417000.00, mc8);
BigDecimal annual = new BigDecimal ("0.0675", mc4);
BigDecimal twelve = new BigDecimal ("12", mc2);
BigDecimal rate = annual.divide (twelve);
BigDecimal years = new BigDecimal ("30", mc2);
BigDecimal months = years.multiply (twelve);

BigDecimal monthly = principal.multiply (rate).divide
(BigDecimal.ONE.subtract
(power (BigDecimal.ONE.add (rate), -360, 10)),
2, BigDecimal.ROUND_HALF_UP);

System.out.println ("BigDecimal monthly payment = " + monthly);
System.out.println ("BigDecimal total payment = " +
monthly.multiply (months));
}

static BigDecimal power (BigDecimal a, int p, int scale)
{
boolean negative = p < 0;

if (p == 0)
return (new BigDecimal ("1"));
else if (p == 1)
return (a);

if (negative)
p = -p;

BigDecimal result = a;
for (int i = 2; i <= p; i++)
result = result.multiply (a);

if (negative)
result = BigDecimal.ONE.divide
(result, scale, BigDecimal.ROUND_HALF_UP);

return (result);
}
}