查看原文
其他

从一道招聘考题谈起

蒋步星 数据蒋堂 2023-02-25

润乾研发部在招聘时有一个笔试题:

1/2, 1/5, 1/20, 1/64, 1/125都可以写成有限小数,而1/3, 1/7, 1/15, 1/24则必须写成无限循环小数。请指出能写成有限小数的分数具有什么样的特征?在什么情况下1/5也会被写成无限循环小数?

坦白地说,这个题的通过率并不高,不到一半吧。


仔细分析题目中的分母,我们会发现,这些能写成有限小数的分母,分解质因数之后就都只有2和5这两种质因子。而那些不能写成有限小数的分母分解因数后则含有不是2和5的质因子。也就是说,只有当分母可以写成2^n*5^m这种形式时才可能写成有限小数。

这是为什么呢?

其实很简单,因为我们把这些分数写成小数时使用的是10进制。而10=2*5,对于任何一个形如2^n*5^m的分母,设k=max(n,m),则把这个数乘以10^k,即2^k*5^k,就一定会变成一个整数了,也就是说,这个数的小数部分最多只有k位,这当然是有限的。而如果分母中含有其它质因子,则不可能有个k使得让这个数乘以10^k后变成整数,也就只能是无限小数了。


那么,什么时候1/5会写成无限小数呢?

如果我们采用的数制不是5的倍数,就会发现这种情况了,比如计算机普遍采用的2进制,这时候1/5会写成一个无限循环小数。

但是,我们的机器都是有限位数的,不可能真地表示一个无限位的数,只能舍弃后面的位。也就是说,1/5在计算机中是不能被精确表示的!


这个现象会影响到我们的程序设计。

比如我们写一段这样的代码:

double x = 0;

for ( int i=0; i<=1000; i++ )

x+=0.001;

我们现在想当然地认为x会等于1,然而并不是!在我的机器上的Java环境中跑出来x=1.0000000000000007,一个奇怪的结果。

为什么要强调的是我的机器上的Java环境呢?因为浮点数的表示和CPU以及编译器都有关系,换一台机器或编译器就可能会跑出不同的结果。

而且,结果也不总是变大。如果我们改成反复加1万次(即用i<=10000),得到的x=9.999999999999897,没啥规律可言。


计算结果和预期值的误差其实非常小,会有什么后果吗?

如果是用于后续再计算(比如再加减乘除等),这个误差确实不重要,可以不去理它。但有时候我们可能会把它用于比较,再根据比较结果做下面的动作。比如我们预期这个x应当等于1,如果后续是这样的代码:

if ( x==1.0 ) {…} else {…}

那就会执行出错误的结果了,这个bug还很难被发现,代码逻辑上看完全没有问题。而且由于前面所说的该现象出现的随机性,也不是对任何数都一定会产生这个结果,很可能在测试时没碰到而被放过了。


那么,怎么避免这个错误呢?

在涉及浮点数相等比较时,一般不要直接使用精确地相等去判断,而要看差的绝对值是否小于某个很小的数。代码写成:

if ( abs(x-1.0) < 1E-10) ….

就不会错了。

如果目标比较值是整数,那还可以将计算结果转换成整数,整数在2进制下都可以精确表示,可以放心地用==去判断,但注意要做四舍五入,即

if ( int(x+0.5) == 1 ) ….

如果直接用int(x)取整,在计算结果因舍位误差小于预期结果时,也会出错。

比较值不是整数,但能保证一定位数的精度,可以先用乘法再转换成整数:

if ( int(x*1000+0.5) == 1000 ) …


还有的办法就是避免使用浮点数。

我们知道,现代数据库都提供有decimal数据类型,其实就是这么个思路。decimal可以称为定点数,其小数部分也是按位数存储的,计算时能够精确表示,不会有上述的误差。但是,decimal不是现代CPU直接支持的数据类型,需要数据库软件来自行实现其计算逻辑,性能就会差出很多。所以,在不需要这种精度时(比如只是计算总数或平均值等),我们还是把它转换成浮点数来计算更好一点。集算器在从数据库取数时提供了@d选项就是为了自动把decimal转成浮点数获得高性能,但需要冒不精确的风险,所以做成选项由程序员自行根据场景决定。

实际业务中,需要精确比较的浮点数常常是金额。大多数国家的货币都是两位小数的,这样我们可以将数值先乘以100转换成整数再存储,而整数的运算和比较都是精确的,不会出现这种问题,但是在显示时需要再转换回来变成用户习惯的两位小数写法。CPU计算和处理整数的性能也非常高,64位的CPU能够表示的整数范围在±2^63,即使除以100也还有16位整数部分,大约是1千万亿,这对于相当多的场景都够用了。这样就即有精确度又有高性能。


数据蒋堂 第二年原创文章

报表工具的SQL植入风险

内置的数据无法实现高性能

怎样生成有关联的测试数据

遍历复用

- 一些数据压缩手段

用HBase做高性能键值查询?

BI系统中容易被忽视的数据源功能

这个产品能支持多大数据量?

最简单的大数据性能估算方法

大清单报表应当怎么做?

大清单报表的打印?

大数据技术的4个E

做基础软件很悲壮?

做基础软件要投入很多钱?

- 国产操作系统还能怎么做?

- 国产数据库通通都没戏!

- 人工智能中的“人工”

- 存储和计算技术的选择

- 区块链技术的一些疑问

- 数据蒋堂新一年




润乾软件创始人、首席科学家

中国大数据产业生态联盟 专家委员

1989年国际奥林匹克数学竞赛团体冠军成员,个人金牌

清华大学计算机硕士

发明了非线性报表模型,并著《非线性报表模型原理》

创建离散数据集模型,颠覆四十年关系代数理论体系!

2016、2017年中国软件和信息服务业 • 十大领军人物

2017年度中国数据大工匠

数据领域专业技术讲堂《数据蒋堂》创办者


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存