程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> 關於C# >> 用C#寫個人住房貸款計算器

用C#寫個人住房貸款計算器

編輯:關於C#

現在,很多人都有個人住房貸款,或者將要有個人住房貸款。那麼,就讓我們用 C# 寫一個計算器,用於計算個人住房貸款的還款計劃表。

這個計算器能夠根據你給出的貸款金額、貸款期數、貸款日期、還款方式、貸款種類,計算出相應的還款計劃表,如上圖所示。

這樣,就很容易知道每月要還多少錢,到現在為止剩余多少貸款未還,最終要付出多少貸款利息,等等。

貸款利率是由貸款種類決定的,存放在 LoanCalculator.xml 文件中:

<?xml version="1.0" encoding="utf-8" ?>
<LoanCalculator>
 <option balance="13.8" months="180" date="2004-07-23" method="等本息" item="公積金" />
 <items>
  <item title="公積金">
   <rate date="1001-01-01" low="3.6" high="4.05" />
   <rate date="2005-01-01" low="3.78" high="4.23" />
   <rate date="2006-01-01" low="3.96" high="4.41" />
   <rate date="2007-01-01" low="4.14" high="4.59" />
   <rate date="2008-01-01" low="5.04" high="5.22" />
   <rate date="2009-01-01" low="3.33" high="3.87" />
  </item>
  <item title="商業性基准">
   <rate date="1001-01-01" low="5.51" high="5.75" />
   <rate date="2007-01-01" low="5.51" high="5.81" />
   <rate date="2008-01-01" low="6.579" high="6.65" />
   <rate date="2009-01-01" low="5.76" high="5.94" />
  </item>
  <item title="商業性優惠">
   <rate date="1001-01-01" low="5.51" high="5.75" />
   <rate date="2007-01-01" low="5.51" high="5.81" />
   <rate date="2008-01-01" low="6.579" high="6.65" />
   <rate date="2009-01-01" low="4.03" high="4.16" />
  </item>
 </items>
</LoanCalculator>

你可以自行修改這個文件,以適應不同銀行的貸款利率。

這個文件由 Config.cs 文件中的 Config 類讀取:

using System;
using System.Xml;
using System.Drawing;
using System.Collections.Generic;

namespace Skyiv.Ben.LoanCalculator
{
 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 AttrLow = "low";
  static readonly string AttrHigh = "high";

  public decimal Balance { get; private set; } // 貸款金額(萬元)
  public int Months { get; private set; }   // 貸款期數(月)
  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, PointF>[] Rates { get; private set; } // 貸款利率

  KeyValuePair<DateTime, PointF>[][] 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);
    Items = GetItemsAndLoadRatesArray(doc);
    SetRates(Item);
   }
   catch (Exception ex)
   {
    throw new Exception("讀配置文件(" + fileName + ")", ex);
   }
  }

  // 根據貸款種類設置貸款利率
  public void SetRates(string key)
  {
   var idx = Array.IndexOf(Items, key);
   if (idx < 0) throw new Exception("無此貸款種類: " + key);
   Rates = ratesArray[idx];
  }

  string[] GetItemsAndLoadRatesArray(XmlDocument doc)
  {
   var elm = doc.DocumentElement[ElmItems];
   if (elm == null) throw new Exception("未能找到 <" + ElmItems + "> 元素");
   var elms = elm.ChildNodes;
   var items = new string[elms.Count];
   ratesArray = new KeyValuePair<DateTime, PointF>[elms.Count][];
   for (var i = 0; i < elms.Count; i++)
   {
    items[i] = GetString(elms[i], AttrTitle);
    ratesArray[i] = GetRates(elms[i]);
   }
   return items;
  }

  KeyValuePair<DateTime, PointF>[] GetRates(XmlNode elm)
  {
   var elms = elm.ChildNodes;
   var rates = new KeyValuePair<DateTime, PointF>[elms.Count];
   for (var i = 0; i < elms.Count; i++)
    rates[i] = new KeyValuePair<DateTime, PointF>(GetDateTime(elms[i], AttrDate),
     new PointF(GetFloat(elms[i], AttrLow), GetFloat(elms[i], AttrHigh)));
   return rates;
  }

  string GetString(XmlNode elm, string key)
  {
   if (elm.Attributes[key] == null) throw new Exception("未能找到 <" + elm.Name + "> 元素的 " + key + " 屬性");
   return elm.Attributes[key].Value;
  }

  float GetFloat(XmlNode elm, string key)
  {
   float value;
   if (!float.TryParse(GetString(elm, key), out value))
    throw new Exception("<" + elm.Name + "> 元素的 " + key + " 屬性的值必須為浮點數");
   return value;
  }

  decimal GetDecimal(XmlNode elm, string key)
  {
   decimal value;
   if (!decimal.TryParse(GetString(elm, key), out value))
    throw new Exception("<" + elm.Name + "> 元素的 " + key + " 屬性的值必須為實數");
   return value;
  }

  int GetInt32(XmlNode elm, string key)
  {
   int value;
   if (!int.TryParse(GetString(elm, key), out value))
    throw new Exception("<" + elm.Name + "> 元素的 " + key + " 屬性的值必須為整數");
   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 靜態類提供的 GetMessage 方法用於顯示錯誤信息:

using System;
using System.Text;

namespace Skyiv.Ben.LoanCalculator
{
 static class Pub
 {
  public static string GetMessage(Exception ex)
  {
   var sb = new StringBuilder("錯誤: ");
   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.Drawing;
using System.Collections.Generic;

namespace Skyiv.Ben.LoanCalculator
{
 abstract class LoanBase
 {
  public DataTable Table { get; private set; }

  public LoanBase(decimal balance, int months, DateTime date, KeyValuePair<DateTime, PointF>[] rates)
  {
   Table = GetTable();
   Calculate(balance, months, date, rates);
  }

  protected virtual void Calculate(decimal balance, int months, DateTime date, KeyValuePair<DateTime, PointF>[] rates)
  {
  }

  protected decimal GetMonthRate(DateTime date, int months, KeyValuePair<DateTime, PointF>[] rates)
  {
   int i;
   for (i = rates.Length - 1; i >= 0; i--) if (date >= rates[i].Key) break;
   return (decimal)(months <= 60 ? rates[i].Value.X : rates[i].Value.Y) / 100 / 12;
  }

  protected decimal Round(decimal dec)
  {
   return decimal.Round(dec, 2);
  }

  DataTable GetTable()
  {
   var dt = new DataTable();
   dt.Columns.Add("期數", 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("累計還款", typeof(decimal));
   dt.Columns.Add("累計利息", typeof(decimal));
   return dt;
  }
 }
}

該類中的 Round 方法用於決定在計算時如何進行捨入,如有需要,可以修改該方法。

在該類的 GetMonthRate 方法中,根據貸款期數(months)來判斷是該筆貸款是短期貸款還是中長期貸款,從而決定應該使用什麼利率。

表示等本息法的 LoanEq 類是從 LoanBase 類中派生的:

using System;
using System.Drawing;
using System.Collections.Generic;

namespace Skyiv.Ben.LoanCalculator
{
 // 等本息法
 sealed class LoanEq : LoanBase
 {
  public LoanEq(decimal balance, int months, DateTime date, KeyValuePair<DateTime, PointF>[] rates)
   : base(balance, months, date, rates)
  {
  }

  protected override void Calculate(decimal balance, int months, DateTime date, KeyValuePair<DateTime, PointF>[] rates)
  {
   decimal baseAmount = 0, totalAmount = 0, totalInterest = 0;
   decimal monthRate0 = decimal.MinValue, monthAmount = decimal.MinValue;
   for (int month = months; month >= 1; month--, date = date.AddMonths(1))
   {
    var monthRate = GetMonthRate(date, months, rates);
    var 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)));
  }
 }
}

在該類中覆蓋了基類的 Calculate 虛方法,在主循環中逐月計算還款計劃表。

等本息法在利率不變的情況下,每月的還款額是固定的,所以也稱為“等額法”,計算公式如下:

月還款額 = 貸款金額 x 月利率 x (1 + 月利率)期數


(1 + 月利率)期數 - 1

這個公式在 GetMonthAmount 方法中計算。

而月還利息等於上月剩余貸款余額乘以月利率,月還本金等於月還款額減去月還利息。

然後,本月剩余貸款余額等於上月剩余貸款余額減去月還本金。

最後,由於計算時需要進行捨入處理,到最後一期還款後可能剩余的貸款余額不為零,這就需要在保持月還款額不變的情況下調整月還本金和月還利息。

表示等本金法的 LoanDesc 類也是從 LoanBase 類中派生的:

using System;
using System.Drawing;
using System.Collections.Generic;

namespace Skyiv.Ben.LoanCalculator
{
 // 等本金法
 class LoanDesc : LoanBase
 {
  public LoanDesc(decimal balance, int months, DateTime date, KeyValuePair<DateTime, PointF>[] rates)
   : base(balance, months, date, rates)
  {
  }

  protected override void Calculate(decimal balance, int months, DateTime date, KeyValuePair<DateTime, PointF>[] rates)
  {
   decimal baseAmount = Round(balance / months), totalAmount = 0, totalInterest = 0;
   for (int month = months; month >= 1; month--, date = date.AddMonths(1))
   {
    var monthRate = GetMonthRate(date, months, rates);
    var interest = Round(balance * monthRate);
    var 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 });
   }
  }
 }
}

在該類中同樣也覆蓋了基類的 Calculate 虛方法,在主循環中逐月計算還款計劃表。

等本金法的月還本金是固定的,並且在調整貸款利率時也不變,等於貸款金額除以總期數。

但是,在貸款利率不變的情況下,每月還款額卻是遞減的,所以也稱為“遞減法”。

月還利息等於上月剩余貸款余額乘以月利率,月還款額等於月還本金加上月還利息。

然後,本月剩余貸款余額等於上月剩余貸款余額減去月還本金。

最後,由於計算時需要進行捨入處理,到最後一期還款後可能剩余的貸款余額不為零,這就需要在保持月還利息不變的情況下調整月還本金和月還款額。

最後,MainForm.cs 文件中的 MainForm 類如下:

using System;
using System.Data;
using System.Windows.Forms;

namespace Skyiv.Ben.LoanCalculator
{
 public sealed partial class MainForm : Form
 {
  Config cfg;

  public MainForm()
  {
   InitializeComponent();
  }

  private void MainForm_Load(object sender, EventArgs e)
  {
   btnCalculte.Enabled = false;
   try
   {
    cfg = new Config("LoanCalculator.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);
    btnCalculate.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 object[] { kvp.Key.ToString("yyyy-MM-dd"), kvp.Value.X, kvp.Value.Y });
  }

  private void btnCalculate_Click(object sender, EventArgs e)
  {
   btnCalculate.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("貸款期數必須是正整數");
    if (!decimal.TryParse(tbxBalance.Text, out balance) || balance <= 0) throw new Exception("貸款金額必須是正數");
    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);
   }
   btnCalculate.Enabled = true;
  }
 }
}

當用戶改變貸款種類時,調用該類的 lbxType_SelectedIndexChanged 方法來相應改變貸款利率。

當用戶點擊“計算”按鈕時,就調用該類的 btnCalculate_Click 方法來計算還款計劃表。

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved