Problem with Amortization

Hey guys... I'm sure you've seen this mortgage calculator til you're just sick of it. But I'm having a problem and I just can't get it to work. Could you please take a look at my code and see if you can find what I'm missing. The program is compiling fine, runs fine except the calculations for the Amortization are wrong somewhere. It prints but the figures are all wrong. Thanks for your help!

(I apologize if the spacing is off, I tried to clean it up)

import java.awt.Container;

import java.awt.Dimension;

import java.awt.FlowLayout;

import java.awt.GridLayout;

import java.awt.event.ActionEvent;

import java.awt.event.ActionListener;

import java.text.DecimalFormat;

import java.text.NumberFormat;

import java.util.Locale;

import javax.swing.BorderFactory;

import javax.swing.BoxLayout;

import javax.swing.ButtonGroup;

import javax.swing.JButton;

import javax.swing.JFrame;

import javax.swing.JLabel;

import javax.swing.JOptionPane;

import javax.swing.JPanel;

import javax.swing.JRadioButton;

import javax.swing.JScrollPane;

import javax.swing.JTextArea;

import javax.swing.JTextField;

import javax.swing.border.Border;

import javax.swing.border.EmptyBorder;

publicclass MortgageCalculatorextends JFrameimplements ActionListener{

int term = 0;

double principal = 0;

double rate = 0;

double monthlyPayment = 0;

double interest = 0;

int notePeriod = 0;

String mTerm[] ={"7","15","30"};

String mInterst[] ={"5.35","5.50","5.75"};

JPanel row1 =new JPanel();

JLabel mortgageLabel =new JLabel("MORTGAGE PAYMENT CALCULATOR", JLabel.CENTER);

JPanel row2 =new JPanel(new GridLayout(1, 2));

JLabel principalLabel =new JLabel("Mortgage Principal $",JLabel.LEFT);

JTextField principalTxt =new JTextField(10);

JPanel row3 =new JPanel(new GridLayout(1, 2));

JLabel termLabel =new JLabel("Mortgage Term (Yrs)",JLabel.LEFT);

JTextField termTxt =new JTextField(10);

JPanel row4 =new JPanel(new GridLayout(1, 2));

JLabel rateLabel =new JLabel("Interest Rate (%)", JLabel.LEFT);

JTextField rateTxt =new JTextField(10);

JPanel radioPanel =new JPanel();

JRadioButton buttonA =new JRadioButton("7 Years at 5.35%" ,false);

JRadioButton buttonB =new JRadioButton("15 Years at 5.50%" ,false);

JRadioButton buttonC =new JRadioButton("30 Years at 5.75%",false);

JPanel row5 =new JPanel(new GridLayout(1, 2));

JLabel monthlyPaymentLabel =new JLabel("Monthly Payment $", JLabel.LEFT);

JTextField monthlyPaymentTxt =new JTextField(10);

//create buttons

JPanel button =new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 10));

JButton amortizeButton =new JButton("Amortize Payments");

JButton clearButton =new JButton("Clear");

JButton exitButton =new JButton("Exit");

JButton calculateButton =new JButton("Calculate");

//create textarea to diplay payments

JTextArea displayArea =new JTextArea(10, 45);

JScrollPane scroll =new JScrollPane(displayArea);

public MortgageCalculator()

{

super ("Mortgage Payment Calculator by S Kemen");

setSize(550, 500);

setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

Container pane = getContentPane();

Border rowborder =new EmptyBorder( 3, 10, 3, 10 );

pane.add(row1);

row1.add(mortgageLabel);

row1.setMaximumSize(new Dimension( 10000, row1.getMinimumSize().height));

row1.setBorder( rowborder);

pane.add(row2);

row2.add(principalLabel);

row2.add(principalTxt);

row2.setMaximumSize(new Dimension( 10000, row2.getMinimumSize().height));

row2.setBorder( rowborder);

pane.add(row3);

row3.add(termLabel);

row3.add(termTxt);

row3.setMaximumSize(new Dimension( 10000, row3.getMinimumSize().height));

row3.setBorder( rowborder);

pane.add(row4);

row4.add(rateLabel);

row4.add(rateTxt);

row4.setMaximumSize(new Dimension( 10000, row4.getMinimumSize().height));

row4.setBorder( rowborder);

ButtonGroup bgroup =new ButtonGroup();

bgroup.add(buttonA);

bgroup.add(buttonB);

bgroup.add(buttonC);

radioPanel.setLayout(new FlowLayout(FlowLayout.RIGHT, 4, 4 ));

radioPanel.add(buttonA);

radioPanel.add(buttonB);

radioPanel.add(buttonC);

pane.add(radioPanel);

radioPanel.setMaximumSize(new Dimension( 10000, radioPanel.getMinimumSize().height));

radioPanel.setBorder( rowborder);

pane.add(row5);

row5.add(monthlyPaymentLabel);

row5.add(monthlyPaymentTxt);

monthlyPaymentTxt.setEnabled(false);//set payment amount uneditable

row5.setMaximumSize(new Dimension( 10000, row5.getMinimumSize().height));

row5.setBorder( rowborder);

button.add(calculateButton);

button.add(clearButton);

button.add(exitButton);

button.add(amortizeButton);

pane.add(button);

button.setMaximumSize(new Dimension( 10000, button.getMinimumSize().height));

scroll.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));

pane.add(scroll);

pane.setLayout(new BoxLayout( pane, BoxLayout.Y_AXIS));

setVisible(true);

setContentPane(pane);

//add listeners

clearButton.addActionListener(this);

exitButton.addActionListener(this);

calculateButton.addActionListener(this);

amortizeButton.addActionListener(this);

buttonA.addActionListener(this);

buttonB.addActionListener(this);

buttonC.addActionListener(this);

}

publicvoid actionPerformed(ActionEvent event)

{

Object command = event.getSource();

if(command == calculateButton)

{

try

{

principal = Double.parseDouble(principalTxt.getText());

}

catch(NumberFormatException e)

{

//catch null pointer exception if Principal is null

JOptionPane.showMessageDialog(null,"Invaild Entry! Please Try Again","ERROR", JOptionPane.ERROR_MESSAGE);

}

try

{

term = Integer.parseInt(termTxt.getText());

rate = Double.parseDouble(rateTxt.getText());

}

catch(NumberFormatException e)

{

//Set rate and term based on which item in the combobox is selected

if(buttonA.isSelected() ==true)

{

rate = 5.35;

term = 7;

}

elseif(buttonB.isSelected() ==true)

{

rate = 5.5;

term = 15;

}

elseif (buttonC.isSelected() ==true)

{

rate = 5.75;

term = 30;

}

else

{

//If no button is selected, this is an actual error. Throw an exception

JOptionPane.showMessageDialog(null,"Invaild Entry! Please Try Again","ERROR", JOptionPane.ERROR_MESSAGE);

}

}

double interest = rate / 100 / 12;//Monthly interst rate

double notePeriod= term * 12;//Number of months over which loan is amortized

//calculation formula

double monthlyPayment = (principal * interest) / (1 - Math.pow(1 + interest, -notePeriod));

//formatting variables

DecimalFormat df =new DecimalFormat("\u00A4#,##0.00");//currency

DecimalFormat pf =new DecimalFormat("#,##0.00%");//percentages

DecimalFormat mi =new DecimalFormat("#,##0.000%");//percentages

monthlyPaymentTxt.setText("" + df.format(monthlyPayment));

}

if(command == clearButton)

{

principalTxt.setText(null);

monthlyPaymentTxt.setText(null);

displayArea.setText(null);

}

if(command == exitButton)

{

System.exit(0);

}

if (command == amortizeButton)

{

//Amoritization variables

double loanBalance = notePeriod * monthlyPayment;

double interestPaid = 0;//Amount of interest paid on the loan

double monthlyPrincipal = 0;//Amount of principal in each monthly payment

double principalBalance = principal;//runing total of principal after payment

String titles ="Month\t Principal\t\tInterest\t\tBalance\n";

displayArea.setText(titles);

displayArea.append("");//Inserts a blank line

//This loop is used to calculate and display the payment schedule information

for(int counter = 0; counter <= term * 12 - 0; counter++)

{

//start outer loop

if(interestPaid == 0)//start inner loop

interestPaid = principalBalance * interest;monthlyPrincipal = monthlyPayment - interestPaid;

loanBalance = loanBalance - monthlyPayment;

principalBalance = principalBalance - monthlyPrincipal;

//formatting variables

DecimalFormat df =new DecimalFormat("\u00A4#,##0.00");//currency

DecimalFormat pf =new DecimalFormat("#,##0.00%");//percentages

DecimalFormat mi =new DecimalFormat("#,##0.000%");//percentages

displayArea.setCaretPosition(0);

displayArea.append((counter +1) +")\t"+df.format(monthlyPrincipal)+"\t\t"+df.format(interestPaid)+"\t\t"+df.format(principalBalance)+"\n");

}

}

}

publicstaticvoid main (String[] arguments)//Main Method

{

MortgageCalculator smc =new MortgageCalculator();

smc.setVisible(true);

smc.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

}

}//End of program

[16558 byte] By [Rhivkaa] at [2007-11-27 10:45:51]
# 1

My recommendation would be to not have your whole solution in one, big, messy class. You've got user interface and calculation stuff all mixed together. Forget about the UI stuff and concentrate on the calculations. Move them into a single class that you can drive on the command line. Once you're getting good values out of it, then worry about the Swing UI.

I don't like your UI much. Looks like you hard wire rate and duration into buttons. It's more general and useful if you allow the user to input a duration and rate and then have a "Calculate" button.

%

duffymoa at 2007-7-28 20:15:28 > top of Java-index,Java Essentials,New To Java...
# 2

I understand, but my assignment is to hardwire them together. I added the option to have the user input values and these values will override the radiobuttons if entered. I've done this on a command line, but I'm not doing as well converting it to GUI. I have to include the GUI in the assignment. Thanks for your opinion.

Rhivkaa at 2007-7-28 20:15:28 > top of Java-index,Java Essentials,New To Java...
# 3

Your problem is one of the "business logic" of the program, and this is complicated by having it wrapped up in a heck of a lot of complex GUI code. I agree with the previous poster that the business logic needs to be in its own class, and you need to make sure it works via console before plugging it into the GUI portion. I know that you are just learning, but this is a very important concept to learn and learn now: the separation of GUI from logic. This code is a poster child for the problems that can occur when you don't do this. Consider redoing your program with this in mind. Otherwise few of us will be willing or able to delve into all that code.

petes1234a at 2007-7-28 20:15:28 > top of Java-index,Java Essentials,New To Java...
# 4

> I understand, but my assignment is to hardwire them

> together.

> I added the option to have the user input

> values and these values will override the

> radiobuttons if entered. I've done this on a command

> line,

No, you don't understand. If it worked on the command line, it'd be working in this code, too.

> but I'm not doing as well converting it to GUI.

> I have to include the GUI in the assignment. Thanks

> for your opinion.

I know you need the UI. I'm saying defer the UI until the calculates are working. Those are what really matter. Once that's working, you're free to concentrate on UI issues.

%

duffymoa at 2007-7-28 20:15:28 > top of Java-index,Java Essentials,New To Java...
# 5

Your problem is here:

double interest = rate / 100 / 12; //Monthly interst rate

double notePeriod = term * 12; //Number of months over which loan is amortized

//calculation formula

double monthlyPayment = (principal * interest)

/ (1 - Math.pow(1 + interest, -notePeriod));

You re-declare these variables and effectively prevent the calculations done by the "Calculate" button from having any effect on the class variables. Get rid of the "double" statements so your calcs can have the proper effect.

Next time, please make it easy on yourself and on me by separating your business logic from your gui.

petes1234a at 2007-7-28 20:15:28 > top of Java-index,Java Essentials,New To Java...
# 6

You're a better man than me, Pete. I wasn't going to bother wading through that mess.

Advice to OP: When you're having trouble with the calculations, best to focus your attention there.

Got a debugger? Use it.

%

duffymoa at 2007-7-28 20:15:28 > top of Java-index,Java Essentials,New To Java...
# 7

other problems:

1) Get rid of this line:

if (interestPaid == 0) //start inner loop

its only purpose is to mess up your calculations.

2) Change your amortization schedule for loop from

counter = 0; counter <= .....

to

counter = 0; counter < .....

Otherwise you will make one payment too many.

petes1234a at 2007-7-28 20:15:28 > top of Java-index,Java Essentials,New To Java...
# 8

If I were to try to do just the business logic here, I'd do something along this order (this is all unfinished):

public class MortgageCalcBusinessLogic

{

private int term = 0;

private double principal = 0;

private double rate = 0;

private double monthlyPayment = 0;

private double interest = 0;

private int notePeriod = 0;

private AmortScheduleItem[] amortSchedule;

public MortgageCalcBusinessLogic(int term, double principal, double rate)

{

this.term = term;

this.principal = principal;

this.rate = rate;

notePeriod = term * 12;

interest = (rate / 100) / 12;

amortSchedule = new AmortScheduleItem[notePeriod];

calculatePayments();

}

private void calculatePayments()

{

monthlyPayment = (principal * interest) / (1 - Math.pow(1 + interest, -notePeriod));

AmortScheduleItem previousAmortSchedItem = new AmortScheduleItem(

principal, monthlyPayment, 0, 0, 0, 0);

for (int i = 0; i < amortSchedule.length; i++)

{

// TODO continue with amortSchedule calculations

previousAmortSchedItem = amortSchedule[i].copy(); // not sure if should copy or clone here?

}

}

public double getMonthlyPayment()

{

return monthlyPayment;

}

/**

* inner class to hold amortization table items

* @author Pete

*/

class AmortScheduleItem

{

double balanceOutstanding;

double payment;

double principalPaid;

double interestPaid;

double principalPaidToDate;

double interestPaidToDate;

public AmortScheduleItem(double balanceOutstanding, double payment,

double principalPaid, double interestPaid,

double principalPaidToDate, double interestPaidToDate)

{

this.balanceOutstanding = balanceOutstanding;

this.payment = payment;

this.principalPaid = principalPaid;

this.interestPaid = interestPaid;

this.principalPaidToDate = principalPaidToDate;

this.interestPaidToDate = interestPaidToDate;

}

public AmortScheduleItem copy()

{

AmortScheduleItem asi = new AmortScheduleItem(

balanceOutstanding, payment, principalPaid,

interestPaid, principalPaidToDate,

interestPaidToDate);

return asi;

}

public double getBalanceOutstanding()

{

return balanceOutstanding;

}

public double getInterestPaid()

{

return interestPaid;

}

public double getInterestPaidToDate()

{

return interestPaidToDate;

}

public double getPayment()

{

return payment;

}

public double getPrincipalPaid()

{

return principalPaid;

}

public double getPrincipalPaidToDate()

{

return principalPaidToDate;

}

}

}

Then test the class here.

import java.util.ArrayList;

import java.util.List;

public class TestMortCalcBL

{

private MortgageCalcBusinessLogic mortCalc;

public TestMortCalcBL()

{

mortCalc = new MortgageCalcBusinessLogic(30, 100000, 5.75);

System.out.println(mortCalc.getMonthlyPayment());

// TODO test amortization table calcs

}

public static void main(String[] args)

{

System.out.println("Welcome to Test Mort Calc BL");

new TestMortCalcBL();

}

}

petes1234a at 2007-7-28 20:15:28 > top of Java-index,Java Essentials,New To Java...
# 9

Thanks guys! I have definitely learned a great deal from your comments, although at first I was disheartened because I worked so hard just to hear it was a mess. lol But honestly, I didn't know. Thank you Pete for taking the time to "wade through the mess" and try to help. I "really" appreciate it. I will look over your suggestions while I continue to work on the program this week. I think your code will be a lot easier to implement arrays in also, which was something I couldn't even begin to figure out how to do in my code.

Rhivkaa at 2007-7-28 20:15:28 > top of Java-index,Java Essentials,New To Java...