首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >住房贷款和摊销计划的每月分期付款(EMI)

住房贷款和摊销计划的每月分期付款(EMI)
EN

Code Review用户
提问于 2020-07-13 14:35:19
回答 2查看 497关注 0票数 6

对我的密码有什么评论吗?

数据模型

代码语言:javascript
复制
public class LoanRepaymentPlan
{
    public decimal TotalRepaymentAmount { get; private set; }
    public decimal CurrentOutstandingAmount { get; private set; }
    public decimal MonthlyRepaymentInterestAmount { get; private set; }
    public decimal MonthlyRepaymentAmount { get; private set; }
    public DateTime RepaymentDate { get; private set; }

    private LoanRepaymentPlan(decimal TotalRepaymentAmount, decimal CurrentOutstandingAmount, decimal MonthlyRepaymentAmount)
    {
        this.TotalRepaymentAmount = TotalRepaymentAmount;
        this.CurrentOutstandingAmount = CurrentOutstandingAmount;
        this.MonthlyRepaymentAmount = MonthlyRepaymentAmount;
    }

    public static LoanRepaymentPlan Create(decimal TotalRepaymentAmount, decimal CurrentOutstandingAmount, decimal MonthlyRepaymentAmount)
    {
        return new LoanRepaymentPlan(TotalRepaymentAmount, CurrentOutstandingAmount, MonthlyRepaymentAmount);
    }
}

主计算

代码语言:javascript
复制
static void Main(string[] args)
{
    Console.WriteLine("Equated Monthly Installment (EMI) for Home Loan");

    Console.WriteLine("Total Loan Amount: ");
    var principal = Convert.ToDouble(Console.ReadLine());

    Console.WriteLine("Interest Rate Per Annum (%): ");
    var interestRatePerAnnum = Convert.ToDouble(Console.ReadLine()) / 100;

    Console.WriteLine("Loan Period (in years): ");
    var years = Convert.ToDouble(Console.ReadLine());

    Console.WriteLine("------------------------------------------------");

    Console.WriteLine("Monthly installment: " + HousingLoanInterest(principal, interestRatePerAnnum, years).ToString("C2"));

    decimal totalRepaymentAmount = 0;
    var monthlyRepaymentAmount = HousingLoanInterest(principal, interestRatePerAnnum, years);
    var tenure = years * 12;
    var principalAndInterest = CompoundInterest(principal, interestRatePerAnnum, 12, years);
    List<LoanRepaymentPlan> repaymentPlans = new List<LoanRepaymentPlan>();

    while (tenure >= 0)
    {
        totalRepaymentAmount = totalRepaymentAmount + (decimal)monthlyRepaymentAmount;
        tenure = tenure - 1;

        repaymentPlans.Add(LoanRepaymentPlan.Create(totalRepaymentAmount,
            (decimal)principalAndInterest - totalRepaymentAmount, (decimal)monthlyRepaymentAmount));
    }

    var table = new ConsoleTable("Current Outstanding Amount", "Monthly Repayment Amount", "Total Repayment Amount");
    foreach (var item in repaymentPlans)
    {
        table.AddRow(item.CurrentOutstandingAmount, item.MonthlyRepaymentAmount, item.TotalRepaymentAmount);
    }
    table.Write();
}

帮助方法

代码语言:javascript
复制
static double CompoundInterest(double principal, double interestRate, int timesPerYear, double years)
{
    // (1 + r/n)
    double body = 1 + (interestRate / timesPerYear);

    // nt
    double exponent = timesPerYear * years;

    // P(1 + r/n)^nt
    return principal * Math.Pow(body, exponent);
}

static double HousingLoanInterest(double loanAmount, double interestRate, double years)
{
    return (loanAmount * Math.Pow((interestRate / 12) + 1,
         (years * 12)) * interestRate / 12) / (Math.Pow
         (interestRate / 12 + 1, (years * 12)) - 1);
}

这一计算有些不准确,但我以后会设法弄清楚的。困扰我的一件事是Math.Pow,它使用double。我已尝试将计算转换为decimal,但目前无法这样做。

EN

回答 2

Code Review用户

回答已采纳

发布于 2020-07-14 03:06:16

var

特别是考虑到您有准确性问题,对于您自己理解代码在以下声明中所做的事情非常重要:

代码语言:javascript
复制
    var monthlyRepaymentAmount = HousingLoanInterest(principal, interestRatePerAnnum, years);
    var tenure = years * 12;
    var principalAndInterest = CompoundInterest(principal, interestRatePerAnnum, 12, years);

仅从这些行,我就不知道它们是否应该是双数,整数,小数等等,只需拼出实际的类型。

就地操作

代码语言:javascript
复制
        totalRepaymentAmount = totalRepaymentAmount + (decimal)monthlyRepaymentAmount;
        tenure = tenure - 1;

可以是

代码语言:javascript
复制
totalRepaymentAmount += monthlyRepaymentAmount;
tenure--;

格式

这可以从一些临时变量中受益,但即使没有这些变量,

代码语言:javascript
复制
    return (loanAmount * Math.Pow((interestRate / 12) + 1,
         (years * 12)) * interestRate / 12) / (Math.Pow
         (interestRate / 12 + 1, (years * 12)) - 1);

更易读懂

代码语言:javascript
复制
return (
    loanAmount * Math.Pow(
        interestRate / 12 + 1,
        years * 12
    ) * interestRate / 12
) / (
    Math.Pow(
        interestRate / 12 + 1,
        years * 12
    ) - 1
);

而这又相当于

代码语言:javascript
复制
double annual = interestRate / 12;
double pow = Math.Pow(
    annual + 1,
    years * 12
);
return loanAmount * pow * annual / (pow - 1);

更简单:

代码语言:javascript
复制
double annual = interestRate / 12,
       pow = Math.Pow(
           annual + 1,
           -12 * years
       );
return loanAmount * annual / (1 - pow);

请测试这个是否等价。

票数 6
EN

Code Review用户

发布于 2020-07-14 08:42:04

@Reinderien提到了一些很大的改进点,所以我不会重复它们。

还有其他领域可以微调。

LoanRepaymentPlan -属性

在这种情况下,您不需要显式地为属性指定private set。如果您只指定getter,那将是非常好的,因为ctors可以设置属性,即使它们不公开getter。

代码语言:javascript
复制
public class LoanRepaymentPlan
{
    public decimal TotalRepaymentAmount { get; }
    public decimal CurrentOutstandingAmount { get; }
    public decimal MonthlyRepaymentInterestAmount { get; }
    public decimal MonthlyRepaymentAmount { get; }
    public DateTime RepaymentDate { get; }

    public LoanRepaymentPlan(decimal TotalRepaymentAmount, decimal CurrentOutstandingAmount, decimal MonthlyRepaymentAmount)
    {
        this.TotalRepaymentAmount = TotalRepaymentAmount;
        this.CurrentOutstandingAmount = CurrentOutstandingAmount;
        this.MonthlyRepaymentAmount = MonthlyRepaymentAmount;
    }
}

使用这种方法,您已经创建了一个不可变的类(在初始化之后不能修改)。从内存优化的角度来看,将class更改为struct是有意义的。这样,它很有可能被分配到堆栈上(甚至在CPU寄存器中),因此不需要为这个对象分配堆,也不需要gargabe集合。

还有另一个有趣的话题,目前还没有,但值得一提的是C# 9记录。在C# 9中,可以通过以下代码实现相同的目标:

代码语言:javascript
复制
public data class LoanRepaymentPlan
{
    public decimal TotalRepaymentAmount { get; init; }
    public decimal CurrentOutstandingAmount { get; init; }
    public decimal MonthlyRepaymentInterestAmount { get; }
    public decimal MonthlyRepaymentAmount { get; init; }
    public DateTime RepaymentDate { get; }    
}

LoanRepaymentPlan -静态构造函数

在我看来,这没有多大意义。您必须在每次初始化时多写两个字符,并且必须维护两个对象创建者函数。我不明白这个静电发生器的全部意义。

输入处理

您的代码在双小数类型之间跳来跳去。如果精度很重要,那么在任何地方都使用decimal,否则就使用double

Convert.ToDouble(Console.ReadLine())确实容易出错。如果用户输入“一些字符串”怎么办?它会抛出一个FormatException。使用double.TryParse代替:

代码语言:javascript
复制
var userInput = Console.ReadLine();
if(!double.TryParse(userInput, out var principal))
{
  //TODO: handle that case
}

您还应该考虑根据有效范围验证用户输入。例如,利率应该在0到100之间。

while vs for循环

尽管在我看来,while循环工作得很好,但for循环可能更有表现力和简洁性。

代码语言:javascript
复制
for (var tenure = years * 12; tenure >= 0; tenure--)
{
    totalRepaymentAmount += (decimal)monthlyRepaymentAmount;

    repaymentPlans.Add(LoanRepaymentPlan.Create(totalRepaymentAmount,
        (decimal)principalAndInterest - totalRepaymentAmount, (decimal)monthlyRepaymentAmount));
}

迭代器变量在被修改的地方声明。

它提高了可读性和可维护性。

函数分解

我强烈鼓励你把你的主要职能分成几个小块,专门负责:

  1. 收集用户输入以进行计算
  2. 执行计算
  3. 转储计算结果

它还将提高可测试性、可维护性和可读性。

票数 4
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/245412

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档