買房利器 淺談C#個(gè)人住房貸款計(jì)算器
51CTO編輯推薦《C#實(shí)用 基礎(chǔ)教程》
先看最終界面圖。
這個(gè)計(jì)算器能夠根據(jù)你給出的貸款金額、貸款期數(shù)、貸款日期、還款方式、貸款種類,計(jì)算 出相應(yīng)的還款計(jì)劃表,如上圖所示。
這樣,就很容易知道每月要還多少錢,到現(xiàn)在為止剩余多少貸款未還,最終要付出多少貸款 利息,等等。
貸款利率是由貸款種類決定的,存放在 LoanComputer.xml 文件中:
<?xml version="1.0" encoding="utf-8" ?>
<LoanComputer>
<option balance="13.8" months="180" date="2004-07-23" method="等本息" item="公積金中長期" />
<items>
<item title="公積金短 期">
<rate date="1001-01-01" value="3.6" />
<rate date="2005-01-01" value="3.78" />
<rate date="2006-01-01" value="3.96" />
<rate date="2007-01-01" value="4.14" />
<rate date="2008-01-01" value="5.04" />
<rate date="2009-01-01" value="3.33" />
</item>
<item title="公 積金中長期">
<rate date="1001-01-01" value="4.05" />
<rate date="2005-01-01" value="4.23" />
<rate date="2006-01-01" value="4.41" />
<rate date="2007-01-01" value="4.59" />
<rate date="2008-01-01" value="5.22" />
<rate date="2009-01-01" value="3.87" />
</item>
<item title="商業(yè)性短期基準(zhǔn)">
<rate date="1001-01-01" value="5.51" />
<rate date="2008-01-01" value="6.579" />
<rate date="2009-01-01" value="5.76" />
</item>
<item title="商業(yè)性中長期基 準(zhǔn)">
<rate date="1001-01-01" value="5.75" />
<rate date="2007-01-01" value="5.81" />
<rate date="2008-01-01" value="6.65" />
<rate date="2009-01-01" value="5.94" />
</item>
<item title="商 業(yè)性短期優(yōu)惠">
<rate date="1001-01-01" value="5.51" />
<rate date="2008-01-01" value="6.579" />
<rate date="2009-01-01" value="4.03" />
</item>
<item title="商業(yè)性中長期優(yōu)惠">
<rate date="1001-01-01" value="5.75" />
<rate date="2007-01-01" value="5.81" />
<rate date="2008-01-01" value="6.65" />
<rate date="2009-01-01" value="4.16" />
</item>
</items>
</LoanComputer>
你可以自行修改這個(gè)文件,以適應(yīng)不同銀行的貸款利率。
這個(gè)文件由 Config.cs 文件中的 Config 類讀取:
using System;
using System.Xml;
using System.Collections.Generic;
namespace Skyiv.Ben.LoanComputer
{
sealed class Config
{
static readonly string ElmOption = "option";
static readonly string ElmItems = "items";
static readonly string AttrBalance = "balance";
static readonly string AttrMonths = "months";
static readonly string AttrDate = "date";
static readonly string AttrMethod = "method";
static readonly string AttrItem = "item";
static readonly string AttrTitle = "title";
static readonly string AttrValue = "value";
public decimal Balance { get; private set; } // 貸款金額(萬元)
public int Months { get; private set; } // 貸款期數(shù)(月)
public DateTime Date { get; private set; } // 貸款日期
public bool IsEq { get; private set; } // 還款 方式: true:等本息 false: 等本金
public string Item { get; private set; } // 貸款種類
public string[] Items { get; private set; } // 貸款種類列表
public KeyValuePair<DateTime, decimal>[] Rates { get; private set; } // 貸款利率
KeyValuePair<DateTime, decimal>[][] ratesArray; // 各種類的“貸款利率”列表
public Config(string fileName)
{
try
{
var doc = new XmlDocument ();
doc.Load (fileName);
var elm = doc.DocumentElement[ElmOption];
if (elm == null) throw new Exception("未能找到 <" + ElmOption + "> 元 素");
Balance = GetDecimal(elm, AttrBalance);
Months = GetInt32(elm, AttrMonths);
Date = GetDateTime(elm, AttrDate);
IsEq = GetBooleanFromMethod(elm, AttrMethod);
Item = GetString(elm, AttrItem);
LoadItems (doc);
}
catch (Exception ex)
{
throw new Exception("讀配置文件(" + fileName + ")", ex);
}
}
// 根據(jù)貸款種類設(shè)置貸款利率
public void SetRates(string key)
{
var idx = Array.IndexOf(Items, key);
if (idx < 0) throw new Exception("無此貸 款種類: " + key);
Rates = ratesArray [idx];
}
void LoadItems(XmlDocument doc)
{
var elm = doc.DocumentElement[ElmItems];
if (elm == null) throw new Exception("未能找到 <" + ElmItems + "> 元 素");
var elms = elm.ChildNodes;
Items = new string [elms.Count];
ratesArray = new KeyValuePair<DateTime, decimal>[elms.Count] [];
for (var i = 0; i < elms.Count; i++)
{
Items[i] = GetString(elms[i], AttrTitle);
ratesArray[i] = GetRates (elms[i]);
}
}
KeyValuePair<DateTime, decimal>[] GetRates(XmlNode elm)
{
var elms = elm.ChildNodes;
var rates = new KeyValuePair<DateTime, decimal> [elms.Count];
for (var i = 0; i < elms.Count; i++)
rates[i] = new KeyValuePair<DateTime, decimal>(GetDateTime(elms[i], AttrDate), GetDecimal (elms[i], AttrValue));
return rates;
}
string GetString(XmlNode elm, string key)
{
if (elm.Attributes[key] == null) throw new Exception("未能找到 <" + elm.Name + "> 元素的 " + key + " 屬性");
return elm.Attributes [key].Value;
}
decimal GetDecimal(XmlNode elm, string key)
{
decimal value;
if (!decimal.TryParse(GetString(elm, key), out value))
throw new Exception ("<" + elm.Name + "> 元素的 " + key + " 屬性的值必須為實(shí) 數(shù)");
return value;
}
int GetInt32(XmlNode elm, string key)
{
int value;
if (!int.TryParse(GetString(elm, key), out value))
throw new Exception("<" + elm.Name + "> 元素的 " + key + " 屬性的值必須為整 數(shù)");
return value;
}
DateTime GetDateTime(XmlNode elm, string key)
{
DateTime value;
if (!DateTime.TryParseExact(GetString(elm, key), "yyyy-MM-dd", null, System.Globalization.DateTimeStyles.None, out value))
throw new Exception("<" + elm.Name + "> 元素的 " + key + " 屬性的值必須為日期 值");
return value;
}
bool GetBooleanFromMethod(XmlNode elm, string key)
{
var value = GetString(elm, key);
if (value == "等本息") return true;
if (value == "等本金") return false;
throw new Exception("<" + elm.Name + "> 元素的 " + key + " 屬性的值必須為“等本息”或者“等本 金”");
}
}
}
而 Pub.cs 文件中的 Pub 靜態(tài)類提供的 GetMessage 方法用于顯示錯(cuò)誤信息:
using System;
using System.Text;
namespace Skyiv.Ben.LoanComputer
{
static class Pub
{
public static string GetMessage(Exception ex)
{
var sb = new StringBuilder("錯(cuò)誤: ");
for (var e = ex; e != null; e = e.InnerException) sb.Append(e.Message + ": ");
sb.Length -= 2;
return sb.ToString();
}
}
}
接著,就是 LoanBase.cs 文件中的抽象基類 LoanBase 了:
using System;
using System.Data;
using System.Collections.Generic;
namespace Skyiv.Ben.LoanComputer
{
abstract class LoanBase
{
public DataTable Table { get; private set; }
public LoanBase(decimal balance, int months, DateTime date, KeyValuePair<DateTime, decimal>[] rates)
{
Table = GetTable ();
Compute(balance, months, date, rates);
}
protected virtual void Compute(decimal balance, int months, DateTime date, KeyValuePair<DateTime, decimal>[] rates)
{
}
protected decimal GetMonthRate(DateTime date, KeyValuePair<DateTime, decimal>[] rates)
{
int i;
for (i = rates.Length - 1; i >= 0; i--) if (date >= rates[i].Key) break;
return rates[i].Value / 100 / 12;
}
protected decimal Round(decimal dec)
{
return decimal.Round (dec, 2);
}
DataTable GetTable()
{
var dt = new DataTable ();
dt.Columns.Add("期數(shù)", typeof (int));
dt.Columns.Add("還款日期", typeof (DateTime));
dt.Columns.Add("本金", typeof (decimal));
dt.Columns.Add("利息", typeof (decimal));
dt.Columns.Add("還款", typeof (decimal));
dt.Columns.Add("貸款余額", typeof (decimal));
dt.Columns.Add("累計(jì)還款", typeof (decimal));
dt.Columns.Add("累計(jì)利息", typeof (decimal));
return dt;
}
}
}
該類中的 Round 方法用于決定在計(jì)算時(shí)如何進(jìn)行舍入,如有需要,可以修改該方法。
表示等本息法的 LoanEq 類是從 LoanBase 類中派生的:
using System;
using System.Collections.Generic;
namespace Skyiv.Ben.LoanComputer
{
// 等本息法
sealed class LoanEq : LoanBase
{
public LoanEq
(decimal balance, int months, DateTime date, KeyValuePair<DateTime, decimal>
[] rates)
: base(balance, months, date, rates)
{
}
protected override void Compute(decimal balance,
int months, DateTime date, KeyValuePair<DateTime, decimal>[] rates)
{
decimal baseAmount = 0,
monthRate0 = 0, monthAmount = 0, totalAmount = 0, totalInterest =
0;
for (int month = months; month >= 1; month
--, date = date.AddMonths(1))
{
decimal monthRate = GetMonthRate
(date, rates), interest = Round(balance *
monthRate);
if (monthRate0 !=
monthRate) monthAmount = GetMonthAmount(balance, monthRate0 = monthRate,
month);
baseAmount = monthAmount -
interest;
balance -=
baseAmount;
if (month == 1 &&
balance != 0)
{
baseAmount +=
balance;
interest -=
balance;
balance =
0;
}
totalAmount +=
monthAmount;
totalInterest +=
interest;
Table.Rows.Add(new object[]
{ months - month + 1, date, baseAmount, interest, monthAmount, balance,
totalAmount, totalInterest });
}
}
decimal GetMonthAmount(decimal balance, decimal
monthRate, int months)
{
double tmp = Math.Pow(1 + (double)monthRate,
months);
return Round((decimal)((double)balance *
(double)monthRate * tmp / (tmp - 1)));
}
}
}
在該類中覆蓋了基類的 Compute 虛方法,在主循環(huán)中逐月計(jì)算還款計(jì)劃表。
等本息法在利率不變的情況下,每月的還款額是固定的,所以也稱為“等額法”,計(jì)算公式 如下:
月還款額 = | 貸款金額 x 月利率 x (1 + 月利率)期數(shù) |
| |
(1 + 月利率)期數(shù) - 1 |
這個(gè)公式在 GetMonthAmount 方法中計(jì)算。
而月還利息等于上月剩余貸款余額乘以月利率,月還本金等于月還款額減去月還利息。
然后,本月剩余貸款余額等于上月剩余貸款余額減去月還本金。
最后,由于計(jì)算時(shí)需要進(jìn)行舍入處理,到最后一期還款后可能剩余的貸款余額不為零,這就 需要在保持月還款額不變的情況下調(diào)整月還本金和月還利息。
表示等本金法的 LoanDesc 類也是從 LoanBase 類中派生的:
using System;
using System.Collections.Generic;
namespace Skyiv.Ben.LoanComputer
{
// 等本金法
sealed class LoanDesc : LoanBase
{
public LoanDesc(decimal balance, int months, DateTime date, KeyValuePair<DateTime, decimal>[] rates)
: base(balance, months, date, rates)
{
}
protected override void Compute(decimal balance, int months, DateTime date, KeyValuePair<DateTime, decimal>[] rates)
{
decimal baseAmount = Round(balance / months), monthAmount = 0, totalAmount = 0, totalInterest = 0;
for (int month = months; month >= 1; month --, date = date.AddMonths(1))
{
decimal monthRate = GetMonthRate (date, rates), interest = Round(balance * monthRate);
monthAmount = baseAmount + interest;
balance -= baseAmount;
if (month == 1 && balance != 0)
{
baseAmount += balance;
monthAmount += balance;
balance = 0;
}
totalAmount += monthAmount;
totalInterest += interest;
Table.Rows.Add(new object[] { months - month + 1, date, baseAmount, interest, monthAmount, balance, totalAmount, totalInterest });
}
}
}
}
在該類中同樣也覆蓋了基類的 Compute 虛方法,在主循環(huán)中逐月計(jì)算還款計(jì)劃表。
等本金法的月還本金是固定的,并且在調(diào)整貸款利率時(shí)也不變,等于貸款金額除以總期數(shù)。
但是,在貸款利率不變的情況下,每月還款額卻是遞減的,所以也稱為“遞減法”。
月還利息等于上月剩余貸款余額乘以月利率,月還款額等于月還本金加上月還利息。
然后,本月剩余貸款余額等于上月剩余貸款余額減去月還本金。
最后,由于計(jì)算時(shí)需要進(jìn)行舍入處理,到最后一期還款后可能剩余的貸款余額不為零,這就 需要在保持月還利息不變的情況下調(diào)整月還本金和月還款額。
最后,MainForm.cs 文件中的 MainForm 類如下:
using System;
using System.Data;
using
System.Windows.Forms;
namespace Skyiv.Ben.LoanComputer
{
public sealed partial
class MainForm : Form
{
Config cfg;
public MainForm()
{
InitializeComponent();
}
private void MainForm_Load(object sender,
EventArgs e)
{
btnCompute.Enabled = false;
try
{
cfg = new Config
("LoanComputer.xml");
tbxBalance.Text
= cfg.Balance.ToString();
tbxMonths.Text = cfg.Months.ToString
();
dtpBegin.Value =
cfg.Date;
rbnDesc.Checked = !
(rbnEq.Checked = cfg.IsEq);
lbxType.DataSource = cfg.Items;
lbxType.SelectedIndex = lbxType.FindStringExact
(cfg.Item);
btnCompute.Enabled =
true;
}
catch
(Exception ex)
{
tbxOut.Text = Pub.GetMessage
(ex);
}
}
private void lbxType_SelectedIndexChanged(object
sender, EventArgs e)
{
cfg.SetRates(lbxType.SelectedValue.ToString());
dgvRate.Rows.Clear();
foreach (var kvp in
cfg.Rates)
dgvRate.Rows.Add(new
string[] { kvp.Key.ToString("yyyy-MM-dd"), kvp.Value + "%"
});
}
private void btnCompute_Click(object sender,
EventArgs e)
{
btnCompute.Enabled = false;
try
{
tbxOut.Text =
"";
var isEq =
rbnEq.Checked;
var date =
dtpBegin.Value;
int
months;
decimal
balance;
if (!int.TryParse
(tbxMonths.Text, out months) || months <= 0) throw new Exception("貸款期數(shù)必須是
正整數(shù)");
if (!decimal.TryParse
(tbxBalance.Text, out balance) || balance <= 0) throw new Exception("貸款金額必
須是正數(shù)");
balance *= 10000; // 貸款
金額單位是萬元
var loan = isEq ? (new
LoanEq(balance, months, date, cfg.Rates) as LoanBase)
:
(new LoanDesc(balance,
months, date, cfg.Rates) as
LoanBase);
dgvOut.Rows.Clear
();
foreach (DataRow row in
loan.Table.Rows) dgvOut.Rows.Add(row.ItemArray);
}
catch (Exception ex)
{
tbxOut.Text = Pub.GetMessage(ex);
}
btnCompute.Enabled = true;
}
}
}
當(dāng)用戶改變貸款種類時(shí),調(diào)用該類的 lbxType_SelectedIndexChanged 方法來相應(yīng)改變貸款 利率。
當(dāng)用戶點(diǎn)擊“計(jì)算”按鈕時(shí),就調(diào)用該類的 btnCompute_Click 方法來計(jì)算還款計(jì)劃表。
個(gè)人住房貸款計(jì)算器以及全部的源程序,可以在這里 下載。
【編輯推薦】