Pulpcode

捕获,搅碎,拼接,吞咽

0%

记一次工作中的金融计算程序

Start

最近工作中要实现一个计算分期价款程序。

比如1000万的总金额,如果分5期付完,那么第一期要占50%(也就是500),其余四期要均分剩余的50%,也就是125万,那么你得到的结果列表应该是[500,125,125,125,125]

但是并没有那么简单。

首先,不仅仅能够从总金直接生成,因为用户可以根据需要修改任意一期,那么其后金额的也要跟着变化。比如上面例子中的数据,如果修改了第二期。将其修改为200,那么余下三期将分摊剩下的300,那么结果列表将变为[500,200,100,100,100]

当然实际情况比这个还要复杂一点,因为要考虑到整除,也就是金额部分不能有小数。那么期数越大,比如11,是很容易不能整除的。

我的第一种做法是将每期的的小数部分保存在一个值中,最后将这个不断累加的值加到最后一期。但是这样算会有误差,(除法带来的误差),而且难以测试。

第二种做法是将不能整除的总金额减去余数,使其能被整除,然后将这个余数最近算入最后一期。

比如现在有三期,均摊22块钱,那么会多出1块钱,可以先将1块钱取出,然后让三期都是7块钱,将最后的一块钱分给最后一期,那么结果就是[7,7,8],这就比第一期简洁明了多了。

实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public static IEnumerable<decimal> NewCalculateEveryPeriodCapitalM(decimal total, int firstPeriod, int periods)
{
decimal exceptFirstTotal = 0;
decimal difference = 0;
decimal average = 0;
// 先将第一期除去
if(firstPeriod == 1)
{
if(total%2 == 0)
{
exceptFirstTotal = total * 0.5m;
yield return total * 0.5m;
}else
{
exceptFirstTotal = total - (total - 1m) * 0.5m;
yield return (total-1m)*0.5m;
}
// 计算平均值
if(exceptFirstTotal % (periods - firstPeriod ) == 0)
{
average = exceptFirstTotal / (periods - firstPeriod);
difference = 0;
}else
{
var mod = exceptFirstTotal % (periods - firstPeriod);
average = (exceptFirstTotal - mod)/(periods - firstPeriod );
difference += mod;
}

for(int i = firstPeriod+1; i < periods; i++)
{
yield return average;
}
yield return average + difference;

}
else{
// 直接计算 average
if (total % (periods - firstPeriod + 1) == 0)
{
average = total / (periods - firstPeriod + 1);
difference = 0;
}
else
{
var mod = total % (periods - firstPeriod + 1);
average = (total - mod) / (periods - firstPeriod + 1);
difference += mod;
}
for (int i = firstPeriod ; i < periods; i++)
{
yield return average;
}
yield return average + difference;
}
}

测试代码

我说的,这里并没有把每一个测试的名字都命名的那么信达雅,但是我只要在备注中写明我的这个测试主要是在干什么就行了,命名不要拘泥于形式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
/// <summary>
/// 测试每期金额是否正确
/// </summary>
private void CalculateEveryPeriodCapitalM(decimal totalPrice,int firstPeriod,int periodCount,decimal[] capitals)
{
if (capitals == null) throw new ArgumentNullException("capitals");
var i = 0;
foreach (var price in ExploitationRightPrice.NewCalculateEveryPeriodCapitalM(totalPrice, firstPeriod, periodCount))
{
Assert.AreEqual(capitals[i], price);
i++;
}
}

/// <summary>
/// 计算分期金额总和是否等于总金额(用于验证测试数据是否有效)
/// </summary>
private void ValidSum(IEnumerable<decimal> capitals, decimal sum)
{
Assert.AreEqual(sum, capitals.Sum(r => r));
}

/// <summary>
/// 基本测试,能够均匀整除
/// </summary>
[TestMethod]
public void TestMethod1()
{
decimal[] capitals = { 5000m, 1250m, 1250m, 1250m, 1250m };
const decimal total = 10000m;
const int start = 1;
const int end = 5;
ValidSum(capitals, total);
CalculateEveryPeriodCapitalM(total, start, end, capitals);
}

/// <summary>
/// 不能够被整除的数据1
/// </summary>
[TestMethod]
public void TestMethod2_1()
{
decimal[] capitals = { 14939m, 2134m, 2134m, 2134m, 2134m, 2134m, 2134m, 2135m };
const decimal total = 29878m;
const int start = 1;
const int end = 8;
ValidSum(capitals, total);
CalculateEveryPeriodCapitalM(total, start, end, capitals);
}

/// <summary>
/// 不能够被整除的数据2
/// </summary>
[TestMethod]
public void TestMethod2_2()
{
decimal[] capitals = { 284425m, 47404m, 47404m, 47404m, 47404m, 47404m, 47406m };
const decimal total = 568851m;
const int start = 1;
const int end = 7;
ValidSum(capitals, total);
CalculateEveryPeriodCapitalM(total, start, end, capitals);
}

/// <summary>
/// 不能够被整除的数据3
/// </summary>
[TestMethod]
public void TestMethod2_3()
{
decimal[] capitals = { 284152m, 47358m, 47358m, 47358m, 47358m, 47358m, 47362m };
const decimal total = 568304m;
const int start = 1;
const int end = 7;
ValidSum(capitals, total);
CalculateEveryPeriodCapitalM(total, start, end, capitals);
}

/// <summary>
/// 能够被整除,但不是从第一期开始计算
/// </summary>
[TestMethod]
public void TestMethod3()
{
decimal[] capitals = { 1250m, 1250m, 1250m, 1250m };
const decimal total = 5000m;
const int start = 2;
const int end = 5;
ValidSum(capitals, total);
CalculateEveryPeriodCapitalM(total, start, end, capitals);
}

/// <summary>
/// 不能够被整除,不是从第一期开始计算,数据1
/// </summary>
[TestMethod]
public void TestMethod4_1()
{
decimal[] capitals = { 71087m, 71087m, 71087m, 71089m };
const decimal total = 568850 - 284500;
const int start = 2;
const int end = 5;
ValidSum(capitals, total);
CalculateEveryPeriodCapitalM(total, start, end, capitals);
}