新闻中心> 文章详情
很多搞 iOS 开发的同学都没有学过算法,有一些甚至没有学过数据结构。在很多人的观念中,算法和数据结构只是在面试的时候有用。
对于 iOS 开发来说,大多数时候都不需要算法和数据结构知识,但是如果你了解了算法和数据结构知识,在一些关键时候,这些知识会影响你的产品质量和开发效率。
很多人排斥学习这方面的知识,除了用得地方少之外,还有一个原因就是这部分知识比较难。
这是本系列的第一篇,也是比较轻松的一篇,我们来谈谈浮点数。 南京iOS应用开发培训
一个故事
直接讲浮点数的概念太枯燥,让我们先从一个iOS的示例程序讲起。这段程序非常简单,我们从 Xcode 中新建一个工程后,在 main 函数中添加一行代码,输出 @99.99 的值。如下所示:
大家觉得结果会是什么?
哈哈,你在开玩笑么?这个结果难道不就是 99.99 吗?」
如果你也和这么想,那么就让我们看看 Xcode 输出的结果吧,如下所示: 南京iOS应用开发培训
「我靠,谁偷偷地把值减了 0.000000000000001?」
别着急,这其实是正常的现象。要解释这样的现象,我们需要先从浮点数在计算机内部的表示法说起。
浮点数表示法 南京iOS应用开发培训
我们知道计算机内部都是二进制,所有东西都是用 0 和 1 来表示的。每一个表示 0 或 1 的位,我们叫做一个比特位(bit)。而对于整数类型,比如我们常见的 NSInteger,我们通常是按 CPU 的位数大小来决定其占用的比特位。比如在 32 位 CPU 下,NSInteger 通常是 32 比特(4 个字节),在 64 位 CPU 下,NSInteger 是 64 比特(8 个字节)。
知道字节之后,我们就很好计算它能够表示的范围了,NSInteger 会用一个比特位的 0 或 1 来表示正负,而其它比特位都是存储数值。所以,一个 32 比特的 NSInteger,它能表示的范围是 -2^31 ~ 2^31-1 。为什么右边这个范围需要减 1 我们就不展开说了,它涉及补码表示法,感兴趣的朋友可以自行 Google。
刚刚介绍的整数的表示方式其实有一个问题,就是它并不能表示非常大的数字,比如 123456789123456789 这个数,就无法保存到 32 位的 NSInteger 中。
所以人们又想了另一个办法,具体是这么做的,我们拿 123456789123456789 这个数来举例。
人们首先将很大的数字用科学计数法表示,比如 123456789123456789 就变成了 1.23456789123456789 * 10 17。
然后对于一个很大的数来说,最后的几位数值其实并不是那么重要,所以如果实在保存不下,优先可以省略的是这些数字,所以我们可以将
优化成
最后,我们将计算机里面存储的内存区位分成两个部分,一个部分存储科学计数法的有效精度,即 1.234567 ,另一部分用来存储科学计数法的指数,即本例中的 17。
用这种表示之后,一个常见的比较大的浮点数,只要不是对精度的要求非常高,那么都是可以表示下来的。当然这种办法也不是可以无限表示浮点数的,它的表示的范围主要取决于它存储指数的那部分内存的大小,如果指数部分太大,它也是存储不下来的。
实验的解释 南京iOS应用开发培训
说回我们之前实验的例子,因为浮点数并不是精确存储每一位的值的,所以实验中的 @99.99 在显示的时候,并不能保证严格输出成 99.99 , 而是变成了 99.98999999999999。
其实所有计算机语言,只要是用这种方式表示浮点数的,那么都会遇到相似的问题。我们在 python 解析器中输入 0.1 + 0.2,也会看到结果并不是严格的 0.3,如下所示:
在 nodejs 中也可以看到类似的结果:
当然,Swift 语言中也是这样的:
浮点数判等 南京iOS应用开发培训
由于浮点数内部存储地不精确,这也造成了我们在比较两个浮点数是否相等时,不能简单地使用 == 符号来判断。
通常情况下,我们判断两个浮点数 A, B 是否相等,需要转化成求这两个浮点数差的绝对值 C,即 C = fabs(A - B),然后看这个值 C 是否小于一个极小数。如果小于一个极小数,则可以认为这两个浮点数是相等的。
根据实际工程中的需要,这个极小数可以自己定义,通常这个极小数的参考值是 1e-6 或 1e-8 。
对于 Xcode 来说,为了方便开发者测试两个浮点数是否相等,在其 Test Case 框架中也提供了 XCTAssertEqualWithAccuracy宏来方便开发者使用。以下是一个示例:
高精度算法 南京iOS应用开发培训
在非常少的情况下,你可能需要一个完全精确的浮点数表示。这个时候,可以使用高精度算法。高精度算法其实就是不再使用简单的 float 或 double 来存储浮点数,而是构造一个专门的类,来存储浮点数。
在这个类的内部,其实是用一个数组来存储浮点数完整的精度信息。当然,随着运算的变化,这个数组可能还需要改变大小,存储更多的精度信息。感兴趣的读者其实可以尝试自己实践来写一个这样的类。
在一些语言中,也内置了对于高精度计算的支持,例如 Java 语言就提供了 BigDecimal 类。不过可惜的是 iOS 和 Swift 语言并未提供相应的类库。
在面试中,一些喜欢考查算法的公司也常常把「高精度算法」作为基础题目来考查。例如:让你在纸上实现一个浮点数的高精度加法算法,感兴趣的同学可以自己尝试实现一下,如果设计得精巧,完整的代码长度可以控制在 200 行以内。
这里给大家一些实现上的提示: 南京iOS应用开发培训
用两个数组来分别存放浮点数的整数部分和小数部分,数组中的每一个元素代表浮点数某一位的值。
在计算两个浮点数的加法时,两个数组分别进行相加求和。
需要处理进位和对齐,整数部分按低位对齐,小数部分按高位对齐。
除了上面的比较标准的做法外,还有一个「Trick」的做法是把浮点数转成字符串,然后按小数点的位置对齐后,前后补足相应位数的 0,这样就可以很方便地求和了。
总结
浮点数在计算机内部并不是严格精确到每一位的。
在判断两个浮点数相等时,不能使用 == 操作符,需要通过比较两个浮点数差的绝对值,是否小于一个极小数的方式来判断。
如果你需要精确的浮点数计算,需要使用高精度算法。
江苏万和作为南京资历深的南京iOS应用开发培训中心,定期会向大家带来一些干货,希望能帮助到大家。