在我国,人民银行规定每个季度月末的20号为银行结息日,每一年四次结息,因此每年需要非常频繁的计算付给储户的利息。在计算利息时,小数点如何处理就变得很重要,并成为决定利润多少的关键细节。
通常,我们都知道在保留小数点的时候,常常会用到四舍五入。小于5的数字被舍去,大于等于5的数字进位后舍去,由于所有位上的数字都是自然计算出来的,按照概率计算可知,被舍入的数字均匀分布在0到9之间。
我们不妨以10笔存款利息计算作为模型,以银行家的身份来思考这个算法:
四舍,舍弃的值包含: 0.000、0.001、0.002、0.003、0.004,对银行而言舍弃的内容就不再需要支付,所以舍弃的部分我们可以理解为“赚到了”。
五入,进位的内容包括:0.005、0.006、0.007、0.008、0.009,对银行而言进位内容会造成亏损,对应亏损的金额则是: 0.005、0.004、0.003、0.002、0.001。
因为舍弃和进位的数字是在0到9之间均匀分布的,所以对于银行家来说,每10笔存款的利息因采用四舍五入而获得的盈利是:
0.000 + 0.001 + 0.002 + 0.003 + 0.004 - 0.005 - 0.004 - 0.003 - 0.002 - 0.001 = -0.005
总体来讲每10笔的利息,通过四舍五入计算就会导致0.005元的损失,即每笔利息计算损失0.0005元。假设某家银行有5千万储户,每年仅仅因为四舍五入的误差而损失的金额是:
public class Client {
public static void main(String[] args) {
//银行账户数量,5千万
int accountNum =5000*10000;
//按照人行的规定,每个季度末月的20日为银行结息日
double cost = 0.0005 * accountNum * 4 ;
System.out.println("银行每年损失的金额:" + cost);
}
}
计算结果是:“银行每年损失的金额:100000.0”。你可能难以相信,四舍五入小小一个动作,就导致了每年损失10万。但在真实环境中,实际损失可能是更多。
这个情况是由美国的私人银行家发现,为了解决这一情况提出了一个修正算法:
“舍去位的数值小于5时,直接舍去;
舍去位的数值大于等于6时,进位后舍去;
当舍去位的数值等于5时,分两种情况:5后面还有其他数字(非0),则进位后舍去;若5后面是0(即5是最后一个数字),则根据5前一位数的奇偶性来判断是否需要进位,奇数进位,偶数舍去。”
以上这么多,汇成一句话就是:四舍六入五考虑,五后非零就进一,五后为零看奇偶,五前为偶应舍去,五前为奇要进一。
我们举例说明,取2位精度:
10.5551= 10.56
10.555= 10.56
10.545= 10.54
(图片来自于网络)
简单来说,有了“四舍六入五成双”这样的银行家算法,就可以更为科学精确地处理数据。
在实际应用中,我们使用银行家算法最多的情况就是在大数据量的表格计算中,但是在表格计算中需要通过一系列的内置公式进行复合。对于普通用户来说无论是理解还是最终使用,都很繁琐且复杂。
为了更加方便地解决这个问题,我们可以通过自定义函数来完成这样的需求,这样用户只需要记住自定义的函数名即可使用具有这样一个规则的函数。
接下来我们一起看看,如何在前端表格中快速地实现“四舍六入五成双”。
我们首先需要定义函数的名称,以及里面的参数数目。因为我们想要实现的是,传递两个参数,“1”是需要被约修的数值,“2”是保留小数点后面的位数,根据值和位数进行约修。
var FdaFunction = function() {
this.name = "FDA";
this.minArgs = 1;
this.maxArgs = 2;
};
接下来就是为了方便用户理解和使用,我们需要对这个自定义函数添加一些描述:
FdaFunction.prototype.description = function() {
return {
description: "对value进行四舍六入五留双修约,保留小数点后指定位数",
parameters: [{
name: "value",
repeatable: false,
optional: false
}, {
name: "places",
repeatable: false,
optional: false
}]
}
}
最后到了关键步骤,也就是函数的逻辑运行都放在evaluate中,我们会对传入的值做一些判断,并且会利用正则表达式做一些匹配。要实现“五成双”,那么我们还要对需要约修的最后一个位值做判断,来决定是否进位。具体可以参考附件完整的demo。
FdaFunction.prototype.evaluate = function(context, num, places) {
if (!isNaN(parseInt(num)) && !isNaN(parseInt(places))) {
console.log("evaluate")
num = numGeneral(num);
if (!isNumber(num)) {
return num;
}
var d = places || 0;
var m = Math.pow(10, d);
var n = +(d ? num * m : num).toFixed(8); // Avoid rounding errors
var i = Math.floor(n),
f = n - i;
var e = 1e-8; // Allow for rounding errors in f
var r = f > 0.5 - e && f < 0.5 + e ? (i % 2 == 0 ? i : i + 1) : Math.round(n);
var result = d ? r / m : r;
if (places > 0) {
var s_x = result.toString();
var pos_decimal = s_x.indexOf(".");
if (pos_decimal < 0) {
pos_decimal = s_x.length;
s_x += ".";
}
while (s_x.length <= pos_decimal + places) {
s_x += "0";
}
return s_x;
} else {
return result;
}
}else{
return "#VALUE!";
}
}
体验下载完整demo:
https://gcdn.grapecity.com.cn...
大家如果想了解更多与自定义公式相关内容,可以查看链接: