.Net的小数取整Math.Round细节
.Net开发遇到的趣事。
— 问题 —
如果我们想对一个小数做取整,该如何做呢?
Math.Round(3.5);返回的结果是4
Math.Round(4.5);返回的结果是4
可见,结果并不是四舍五入,查看文档后发现,.Net里面默认的取整方式并不是“四舍五入”而是“四舍六入,五取偶”(五取最近的偶数)。
这种默认的取整方式不太符合我们平常的认知,如果在程序开发中没有注意这方面的细节,一不小心就会使程序实际运行的效果违背设计初衷。
— 解决 —
解决方法很简单,微软提供了一个枚举型的参数 MidpointRoundind用于指定舍入的方法,
Math.Round(4.5, MidpointRoundind.ToEven); //四舍六入、五取偶, 返回结果是4
Math.Round(4.5, MidpointRoundind.AwayFromZero); //四舍六入、五远离零(即四舍五入),返回结果是5
因此,四舍五入应该记得加上参数 MidpointRoundind.AwayFromZero
— 深挖 —
于是,就有一个问题,为什么微软设计Round的时候默认采取的舍入方法不是四舍五入呢?
网上查资料得知,四舍六入五取偶的方式又称之为“银行家舍入算法” (Banker’s rounding),而微软也在其官方文档里说明了这一点,并且表示这种方式是符合IEEE标准的:
中文文档:https://msdn.microsoft.com/zh-cn/library/as4h66hd
英文文档:https://docs.microsoft.com/en-us/dotnet/api/system.math.round?view=netframework-4.7.2
于是,我们再去看看这个IEEE标准是什么:
IEEE 754: https://en.wikipedia.org/wiki/IEEE_754#Rounding_rules
IEEE 754标准的写明了:
Roundings to nearest
Round to nearest, ties to even – rounds to the nearest value; if the number falls midway it is rounded to the nearest value with an even (zero) least significant bit; this is the default for binary floating-point and the recommended default for decimal.
舍入到最近的偶数,如果一个数落在中间则舍入到最接近的偶数;这是二进制浮点数的默认方法,同时也推荐作为decimal数的默认方法;
Round to nearest, ties away from zero – rounds to the nearest value; if the number falls midway it is rounded to the nearest value above (for positive numbers) or below (for negative numbers); this is intended as an option for decimal floating point.
舍入到远离零的数,如果一个数落在中间则往上舍入(该数大于零)或往下舍入(该数小于零);这被作为一个可选的方法;
那么大家为什么要推荐使用Banker’s rounding来取整呢?因为如果采用四舍五入,会造成结果偏大、统计误差变大的后果,尤其在有乘法出现的计算过程中,这个偏差会被进一步放大。
而Banker’s rounding取整方式在统计、金融领域非常有用,可以避免四舍五入方式带来的偏离。
假设有100个平均分布在1和11之间的的小数,我们看看他们取整后的统计值有什么特点:
可以看出,四舍六入五取偶的方式,在样本数越多的情况下,统计上的数值的偏离更小。
很厉害的样子啊。