从“四舍五入”到“奇进偶舍”
处理取整时,大概下意识的可以想到的方法,都是“四舍五入”吧?不过我们可以先看两个例子,在Python 3中,round(4.5) == 4
,而在mongodb 以上的版本中,{$round: 4.5}
的结果也是4。对于习惯了只存在“四舍五入”这一种舍入方法的同学们来说,估计是要去怀疑这是不是代码的bug了。其实,这里舍入的方法并不是“四舍五入”,而是采用了所谓的“奇进偶舍”或者“四舍六入五成双”的方法,这种方法也被称为Banker's Rounding(银行家舍入法)。Python 3选择了这种舍入方法作为标准库的实现,最主要的原因还是因为这个舍入方法被IEEE 754标准选为了默认的浮点数舍入方法和Decimal的推荐默认舍入方法(Round to nearest, ties to even)。
作为默认舍入方法被推荐,并且还有Banker's Rounding这么一个拉风的名字,这个方法的优势在什么地方呢?首先,以舍入到整数为例,让我们来看一下,“奇进偶舍”这个方法的规则是什么。这里,我们就从Round to nearest, ties to even这个定义来解释。首先是Round to nearest,就是向最接近的整数来舍入,比如5.6最接近的整数是6,而-3.2最接近的整数是-3,前面举得几个例子其实和“四舍五入”的规则是完全一样的,不同之处在于,当小数部分正好是0.5时,那么这个数到两边的整数的距离是完全一样的,这时ties to even这后半条规则就要派上用场了,也就是当到两边整数的距离相等时,向最接近的偶数舍入,比如0.5舍入到0,4.5舍入到4,而1.5则要舍入到2。
从上面的规则可以看出来,广为人知的“四舍五入”的规则还是要简单很多的,但是“四舍五入”这种方法会引入一个比较容易积累误差的问题。还是舍入到整数为例,当小数部分恰好是最中间的0.5时,这个部分总是向上取整的,于是向上取整的可能性就会比向下取整多,那么得到舍入之后的数字就会倾向于偏大,尤其是在类似于在计算比如收入数据之类只需要保留一两位小数这些情形中,这个误差就很容易提现出来。而进一步的,如果对已经舍入过的数字进行求和等计算,这个误差会被积累和放大,经过多级的数据统计之后,一些最终统计报表上的结果就会与实际数字差的很远。采用“奇进偶舍”这种方法时,如果小数部分恰好是0.5,舍入时会以均等的概率向上或者向下取整,所以舍入之后偏大或者偏小的倾向也会相互抵消,从而在概率上让实际的误差趋向于0。
下面我们设计一个实验来对比两种舍入方法的误差积累。我们可以使用Python的decimal模块来完成这个实验,在decimal模块的Decimal.to_integral_value函数中,可以指定rounding参数为decimal.ROUND_HALF_UP或者decimal.ROUND_HALF_EVEN在两种舍入方法中进行选择。主要的测试程序如下:
import decimal import math import random def get_random_decimal(n, f): ''' 生成decimal.Decimal随机数,整数n位,小数f位 ''' return decimal.Decimal(int(random.random() * 10 ** (n + f))) / decimal.Decimal(10 ** f) def test(n, f, count=1000): ''' 进行求和测试并计算舍入的误差,count为随机数的个数,整数n位,小数f位 ''' sum_float = decimal.Decimal(0.0) sum_round_half_up = decimal.Decimal(0.0) sum_round_half_even = decimal.Decimal(0.0) for i in range(count): v = get_random_decimal(n, f) sum_float += v sum_round_half_up += v.to_integral_value(rounding=decimal.ROUND_HALF_UP) sum_round_half_even += v.to_integral_value(rounding=decimal.ROUND_HALF_EVEN) error_round_half_up = (sum_float - sum_round_half_up).copy_abs() error_round_half_even = (sum_float - sum_round_half_even).copy_abs() rate_round_half_up = '%.4f%%' % (error_round_half_up / sum_float * 100) rate_round_half_even = '%.4f%%' % (error_round_half_even / sum_float * 100) return [count, sum_float, sum_round_half_up, error_round_half_up, rate_round_half_up, sum_round_half_even, error_round_half_even, rate_round_half_even] # 范例调用方法 # test(2, 2, count=10000)
我们把随机数值控制在100以内,并且保留两位小数(n==2, f==2),在不同的count下可以得到如下的结果
count | sum_float | sum_up | error_up | rate_up | sum_even | error_even | rate_even | |
---|---|---|---|---|---|---|---|---|
0 | 10000 | 502250 | 502336 | 86.33 | 0.0172% | 502279 | 29.33 | 0.0058% |
1 | 100000 | 5.00007e+06 | 5000671 | 604.66 | 0.0121% | 5000208 | 141.66 | 0.0028% |
2 | 1000000 | 5.00414e+07 | 50046394 | 5008.05 | 0.0100% | 50041434 | 48.05 | 0.0001% |
3 | 10000000 | 5.00122e+08 | 500170946 | 48962.4 | 0.0098% | 500120975 | 1008.57 | 0.0002% |
从上面的结果中(以_up结尾的为"四舍五入"的,以_even结尾的为"奇进偶舍"的)可以看出,“奇进偶舍”的误差是明显小于“四舍五入”的,而且会随着count的增大而越来越趋于0(“四舍五入”在这个设定下会趋于0.01%)。虽然计算的规则稍微复杂一些,但是“奇进偶舍”这种舍入方法的优势还是非常明显的,这也是这种方法成为推荐标准,也被越来越多的编程语言和数据库把这种方法作为默认实现的原因。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
助阵战疫,共克时艰,阿里云物联网平台为医疗设备提供免费服务
2020年2月-6月,医疗类设备可免费使用阿里云物联网平台的各项服务,详情咨询请扫码加入钉钉群/邮件联系jirong.zjr@alibaba-inc.com
- 下一篇
认识Class -- 终于不在怂
引子 本是新年,怎奈新冠肆掠,路上行人,男女老少几乎是全副口罩,形色匆匆;偶尔有一两个裸露口鼻的,估计都是没囤到口罩的,这几天药店几乎都是贴上大字:口罩没货。看着网络上病毒消息满天飞,我也响应在家做贡献的号召。上班时,都是早出晚归,几乎只有早上能看到娃,出门时,娃每次都说:see you tomorrow 。赶上疫情,天天在家带娃,终于可以多多陪伴了;别说,带娃还真比上班费神。想着小时候,特别想有一个玩具小船,动手给娃做了一个,附图一张。把娃带好了,也得思考下学习的事儿。学习java有段时间了,想起之前学习java时,看着Class<?> 这样的符号就怵,不明白其表示的含义,又重读《java编程思想》第14章, 趁着这样的时间好好整理了一下,直面当时的怵。 Class对象 Class<?> - 类的类型,是运行时类型信息,也就是 RTTI - RTTI - RunTime Type Infomation;所谓一切皆对象,类也是一个对象,而类的类型信息,就叫做Class对象。RTTI使得我们可以在运行时发...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- CentOS8安装Docker,最新的服务器搭配容器使用
- CentOS7安装Docker,走上虚拟化容器引擎之路
- Linux系统CentOS6、CentOS7手动修改IP地址
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- Docker安装Oracle12C,快速搭建Oracle学习环境
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- CentOS7,CentOS8安装Elasticsearch6.8.6