买房利器 浅谈C#个人住房贷款计算器

开发 后端
现在,很多人都有个人住房贷款,或者将要有个人住房贷款。那么,就让我们用 C# 写一个计算器,用于计算个人住房贷款的还款计划表。

51CTO编辑推荐《C#实用 基础教程

先看最终界面图。

个人住房贷款计算器

这个计算器能够根据你给出的贷款金额、贷款期数、贷款日期、还款方式、贷款种类,计算 出相应的还款计划表,如上图所示。

这样,就很容易知道每月要还多少钱,到现在为止剩余多少贷款未还,最终要付出多少贷款 利息,等等。

贷款利率是由贷款种类决定的,存放在 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="商业性短期基准">
      <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="商业性中长期基 准">
      <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="商 业性短期优惠">
      <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="商业性中长期优惠">
      <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>

你可以自行修改这个文件,以适应不同银行的贷款利率。

这个文件由 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; }       // 贷款期数(月)
    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);
      }
    }
    // 根据贷款种类设置贷款利率


    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 + " 属性的值必须为实 数");
      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.LoanComputer
{
  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.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("期数", 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 方法用于决定在计算时如何进行舍入,如有需要,可以修改该方法。

表示等本息法的 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 虚方法,在主循环中逐月计算还款计划表。

等本息法在利率不变的情况下,每月的还款额是固定的,所以也称为“等额法”,计算公式 如下:

月还款额 = 贷款金额 x 月利率 x (1 + 月利率)期数

(1 + 月利率)期数 - 1

这个公式在 GetMonthAmount 方法中计算。

而月还利息等于上月剩余贷款余额乘以月利率,月还本金等于月还款额减去月还利息。

然后,本月剩余贷款余额等于上月剩余贷款余额减去月还本金。

最后,由于计算时需要进行舍入处理,到最后一期还款后可能剩余的贷款余额不为零,这就 需要在保持月还款额不变的情况下调整月还本金和月还利息。

表示等本金法的 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 虚方法,在主循环中逐月计算还款计划表。

等本金法的月还本金是固定的,并且在调整贷款利率时也不变,等于贷款金额除以总期数。

但是,在贷款利率不变的情况下,每月还款额却是递减的,所以也称为“递减法”。

月还利息等于上月剩余贷款余额乘以月利率,月还款额等于月还本金加上月还利息。

然后,本月剩余贷款余额等于上月剩余贷款余额减去月还本金。

最后,由于计算时需要进行舍入处理,到最后一期还款后可能剩余的贷款余额不为零,这就 需要在保持月还利息不变的情况下调整月还本金和月还款额。

最后,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("贷款期数必须是 正整数");
        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);
      }
      btnCompute.Enabled = true;
    }
  }
}

当用户改变贷款种类时,调用该类的 lbxType_SelectedIndexChanged 方法来相应改变贷款 利率。

当用户点击“计算”按钮时,就调用该类的 btnCompute_Click 方法来计算还款计划表。

个人住房贷款计算器以及全部的源程序,可以在这里 下载。

【编辑推荐】

  1. 简述用 C#实现优先队列方法
  2. 横向技 术分析C#、C++和Java优劣
  3. 高手详 解C#编程中的规则表达式
责任编辑:彭凡 来源: cnblogs
相关推荐

2016-12-02 17:56:15

华为双活存储

2019-01-21 08:25:24

房东程序员房租

2009-09-07 13:29:30

C#计算素数序列

2015-08-05 09:30:32

C#下拉式计算器

2024-01-31 08:33:06

C++编程计算器

2009-08-20 18:30:33

C# ReaderWr

2011-09-21 10:56:31

C#结构

2011-09-16 14:13:15

Windows7计算器

2009-08-12 18:17:31

C#随机数发生器

2009-08-26 15:28:52

C#对象集合初始化器

2009-08-12 11:24:25

C# String对象

2009-08-07 11:26:53

C#数组结构

2009-08-31 09:37:09

C# Employee

2009-08-19 17:12:18

C# Connecti

2009-08-26 15:46:01

C#匿名类型

2009-08-20 10:24:52

C#开发WinForm

2009-09-02 15:41:21

C# HTTPWebR

2009-08-14 17:58:05

C#接口方法

2009-08-06 15:30:23

C#类型系统

2009-08-26 13:15:38

C#选择控制
点赞
收藏

51CTO技术栈公众号