貸款試算是經典財務應用場景。本文展示一個完整的 C# WinForms 貸款計算機,支援等本息 (固定月繳)與等本金 (遞減月繳)兩種還款方式、多種貸款利率方案 (公積金/商業貸款/優惠利率)、利率隨時間區段調整,採用 XML 設定檔 外部化利率參數,核心計算邏輯以抽象基類 + 策略模式封裝,介面與邏輯分離。
-
-
-
low:5年期(含)以下利率,high:5年期以上利率,依貸款總期數自動判斷。
// 抽象基類
public abstract class LoanBase
{
public DataTable Schedule { get; private set; }
protected LoanBase(decimal principal, int months, DateTime startDate, RateSegment[] rates)
{
Schedule = BuildScheduleTable();
Calculate(principal, months, startDate, rates);
}
protected abstract void Calculate(decimal principal, int months, DateTime startDate, RateSegment[] rates);
protected decimal GetMonthlyRate(DateTime date, int totalMonths, RateSegment[] rates)
{
// 找適用利率區段 (依日期遞減查找)
for (int i = rates.Length - 1; i >= 0; i--)
if (date >= rates[i].EffectiveDate)
return (totalMonths <= 60 ? rates[i].RateLow : rates[i].RateHigh) / 100m / 12m;
return 0m;
}
protected decimal Round(decimal v) => Math.Round(v, 2, MidpointRounding.AwayFromZero);
private DataTable BuildScheduleTable()
{
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;
}
}
// 等本息:月繳固定
public sealed class LoanEq : LoanBase
{
public LoanEq(decimal p, int m, DateTime d, RateSegment[] r) : base(p, m, d, r) { }
protected override void Calculate(decimal principal, int months, DateTime date, RateSegment[] rates)
{
decimal totalPrincipal = 0, totalInterest = 0;
decimal fixedMonthlyPayment = -1, currentRate = -1;
for (int m = months; m >= 1; m--, date = date.AddMonths(1))
{
decimal rate = GetMonthlyRate(date, months, rates);
decimal interest = Round(principal * rate);
if (rate != currentRate) // 利率變動重新計算固定月繳
{
currentRate = rate;
fixedMonthlyPayment = GetFixedPayment(principal, currentRate, m);
}
decimal principalPaid = fixedMonthlyPayment - interest;
principal -= principalPaid;
// 最後一期修正殘差
if (m == 1 && principal != 0)
{
principalPaid += principal;
interest -= principal;
principal = 0;
}
totalPrincipal += principalPaid;
totalInterest += interest;
Schedule.Rows.Add(months - m + 1, date, principalPaid, interest, fixedMonthlyPayment, principal, totalPrincipal, totalInterest);
}
}
private decimal GetFixedPayment(decimal p, decimal r, int n)
{
double factor = Math.Pow(1 + (double)r, n);
return Round((decimal)((double)p * (double)r * factor / (factor - 1)));
}
}
// 等本金:本金固定,月繳遞減
public sealed class LoanDesc : LoanBase
{
public LoanDesc(decimal p, int m, DateTime d, RateSegment[] r) : base(p, m, d, r) { }
protected override void Calculate(decimal principal, int months, DateTime date, RateSegment[] rates)
{
decimal fixedPrincipal = Round(principal / months);
decimal totalPrincipal = 0, totalInterest = 0;
for (int m = months; m >= 1; m--, date = date.AddMonths(1))
{
decimal rate = GetMonthlyRate(date, months, rates);
decimal interest = Round(principal * rate);
decimal monthlyPayment = fixedPrincipal + interest;
principal -= fixedPrincipal;
if (m == 1 && principal != 0)
{
fixedPrincipal += principal;
monthlyPayment += principal;
principal = 0;
}
totalPrincipal += fixedPrincipal;
totalInterest += interest;
Schedule.Rows.Add(months - m + 1, date, fixedPrincipal, interest, monthlyPayment, principal, totalPrincipal, totalInterest);
}
}
}
public partial class MainForm : Form
{
private Config _cfg;
private void MainForm_Load(object sender, EventArgs e)
{
_cfg = new Config("LoanCalculator.xml");
txtPrincipal.Text = _cfg.Balance.ToString(); // 萬元
txtMonths.Text = _cfg.Months.ToString();
dtpStartDate.Value = _cfg.Date;
rdoEq.Checked = _cfg.IsEqualPrincipalInterest;
lstLoanType.DataSource = _cfg.Items;
lstLoanType.SelectedItem = _cfg.Item;
}
private void lstLoanType_SelectedIndexChanged(object sender, EventArgs e)
{
_cfg.SetRates(lstLoanType.SelectedItem.ToString());
BindRateGrid();
}
private void btnCalculate_Click(object sender, EventArgs e)
{
if (!decimal.TryParse(txtPrincipal.Text, out var principal) || principal <= 0) return;
if (!int.TryParse(txtMonths.Text, out var months) || months <= 0) return;
principal *= 10000m; // 萬元轉元
var rates = _cfg.Rates;
LoanBase loan = rdoEq.Checked
? new LoanEq(principal, months, dtpStartDate.Value, rates)
: new LoanDesc(principal, months, dtpStartDate.Value, rates);
dgvSchedule.DataSource = loan.Schedule;
FormatGrid();
}
}
LoanEq/LoanDesc 共用基類邏輯,擴充新還款方式只需新增子類別。decimal,MidpointRounding.AwayFromZero 四捨五入,最後一期修正殘差。