从一道招聘考题谈起

简介:

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

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千万亿,这对于相当多的场景都够用了。这样就即有精确度又有高性能。


原文发布时间为:2018-08-28

本文作者:蒋步星

本文来自云栖社区合作伙伴“数据蒋堂”,了解相关信息可以关注“数据蒋堂”。

相关文章
|
2月前
|
人工智能 架构师 NoSQL
24岁程序媛,二战考研失利、三无人员 ==> 最佳新人、优秀个人,讲讲我的技术成长之路
能力、格局、谋略、远见、耐心。灵魂的欲望是命运的先知,希望永远自信、洒脱、松弛、明媚、张扬;追随自己的内心、以喜欢的方式、往正确的方向前行,永远在路上,我甘之如饴! 持续精进Java领域相关技术,包括微服务、高并发、高可用、分布式、集群等等;希望能接触到更多更大的优质项目,逐渐成长为一名具备全栈思维的架构师,既能深入理解底层技术,又能把控全局架构;抽时间了解学习Go语言、人工智能、大模型等领域。 在探索中明晰后续的发展方向,形成自己的一套体系,成为主管、管理层乃至更高,不希望自己的上限只是程序员。
【数字设计】星宸科技_笔试面试题目分享
【数字设计】星宸科技_笔试面试题目分享
【数字设计】星宸科技_笔试面试题目分享
【数字设计】恒玄科技_笔试面试题目分享
【数字设计】恒玄科技_笔试面试题目分享
【数字设计】恒玄科技_笔试面试题目分享
【数字设计】诺瓦科技_笔试面试题目分享
【数字设计】诺瓦科技_笔试面试题目分享
【数字设计】诺瓦科技_笔试面试题目分享
|
前端开发 数据格式 Python
【数字设计】壁仞科技_笔试面试题目分享
【数字设计】壁仞科技_笔试面试题目分享
【数字设计】壁仞科技_笔试面试题目分享
|
芯片 内存技术
【数字设计】乐鑫科技_笔试面试题目分享
【数字设计】乐鑫科技_笔试面试题目分享
【数字设计】乐鑫科技_笔试面试题目分享
|
缓存 NoSQL 前端开发
揭密!程序员面试不得不知道的潜规则
规则1:面试的本质不是考试,而是告诉面试官你会做什么 很多刚入行的小伙伴特别容易犯的一个错误,不清楚面试官到底想问什么,其实整个面试中面试官并没有想难道你的意思,只是想通过提问的方式来知道你会什么
揭密!程序员面试不得不知道的潜规则
|
安全 测试技术 数据安全/隐私保护
6 大测试用例设计题详细整理— 助攻高薪求职之路!
我发现无论是刚入职场的测试新人还是在具备几年测试经验的职场老人,对于测试用例设计这块,倘若不是自己接触过测试过的软件产品,被问到如何测试基本回答不上来,原因归根结底还是测试思维积累不够。
6 大测试用例设计题详细整理— 助攻高薪求职之路!
|
前端开发 JavaScript 算法
从文科生到前端专家 - 在转行时我想过的问题
我是繁易,一名从文科转行的前端工程师,希望能为和我一样,对编程感兴趣/犹豫转行/正在转行的同学们提供一点经验分享。
294 0
从文科生到前端专家 - 在转行时我想过的问题
|
机器学习/深度学习 人工智能 自然语言处理
15篇面试通关经验+10大热招岗位,给你足够底气斩获offer!|开发者必读(164期)
人生有许多种可能,只要勇敢一次,你会发现收获的东西比想象中多很多,譬如说,我们正在招人,你勇敢投简历了吗?如果你还没有准备好,没关系,我们给你足够底气斩获offer!10大热招岗位需求+15篇师兄师姐面试经验分享,手把手教你面试通关!