前言
在看到<深入理解计算机系统>的浮点数时, 第一想法是:
- 无法精确保存大多数浮点数
- 精度上的缺失
零值的比较
很多面试题都会考一道浮点数零值比较的题(一般是单精度, 双精度太长了)
我觉得答案应该是:
1 | f > -0.000001f && f < 0.000001f |
这个题的核心在于 float 什么时候缺失精度
这里我没有使用等于, 因为我认为 0.000001f 和 -0.000001f 并不算缺失了精度
(百度上的答案是有等于的, 我很怀疑这个答案, 甚至有人还用的是 0.00001 (눈_눈) )
(而google上我好像没有找到类似的答案, 再根据编译器给我的结果, 我只能如此推断)
下面是我推断的依据:
1 | printf("%f\n", 0.000001f); |
你觉得上面会打印什么呢? 输出结果是:
1 | 0.000001 |
这也就是我认为 0.000001 它并未损失精度的原因, 既然未损失, 那么就不能当做 0 值来对待
(再次看不起百度上的解答(눈_눈), 不过… 万一是cas错了呢?)
(损失精度还有更精确的 0.00000055f, 这个数字也被认为是 0.000001)
数字的精度取决于有多少位表示
上面看到了6为精度的情况, 他准确表示了0.1 (虽然它把 0.0000006f 当做了0.1…)
我们来看看其他的结果, 比如:
1 | printf("%f\n", 255.1f); |
你觉得这次又会输出什么呢?
1 | 255.100006 1 1 1 1 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 |
(输出结果我做了缩减, 不然太长了)
也就是说, 除了
1 | 254.200005f |
之外, 编译器认为它们都是相等的, 为什么呢?
精度再次缺失(我只能如此猜测), 因为整数的数字过大, 剩下的留给小数的位数不足以达到6位精度
所以这次的精度缩减到了5位, 而因为四舍五入(我只能再次如此猜测 (눈_눈))的关系
(其实说四舍五入有点不对, 应该是: 数字的二进制表示刚好进入了有效的区间)
一些能达到 254.20001 的数字被判段为不等, 而一些 254.19999 的数字又可四舍五入的关系被判断为相等
所以, 整数数字的大小会影响小数的精度 (我感觉我在说废话 (눈_눈)), 而当整数过大时, 比如 0x7fffffffff
所有的小数精度全都会缺失(unsigned float 可能是例外, 不过不影响结论)
下面我又做了一次比较, 我将254换成了126, 输出结果是
0 0 0 0 0 0 0 0 0 1 1 1 1 1 0 0 0 0
emmm… 其实这次的有效精度还是接近5位
不过能够在5位之外, 能判断更多的数字了
(这个数字并未完全达到6位, 也许 0.000005 能判断到, 0.000004 却不能, 就像上面那样)
底层到底对我们的代码做了什么
又到了喜闻乐见的看汇编环节 ┑( ̄Д  ̄)┍
1 | float f = 0.1f; |
它在汇编中的样子:
1 | .cfi_startproc |
关键点在于 .LC1 和 .LC2, 他们的数字, 不过一点数字看不出什么, 需要多一些数据
1 | 1036831949 = 0.1f = 3dcccccd |
其中 0.1f 和 0.2f 相差 800000
0.1f 和 0.11f 相差 147ae1
emmmm… 想不出来, 或许我该再看看书
嗯 好的, 看完了 ( ̄ˇ ̄)
大概是这样的, 根据不同的位数安排, 计算的结果也有相应的不同
一个浮点数, 1位符号位S, 8位阶码E, 23位小数位M
其中又分为4种情况: 规格, 非规格, NaN(not a number?), 无穷大
(具体的细节请参考书中的介绍)
总之, 我们用书中的算法来检验一下这几个数字
首先 0.1f, 它的数字是 3dcccccd, 它是一个规格化数字
E = 123 - 127 = -4 , M = 5033165 / 8388735 +1
2的E次方 x M = 0.0999994337644472
emmm… 没错, 这是一个非常接近 0.1 的数字
(书中说到了小数的舍入, 简单来说是四舍五入, 同时向偶数舍入, 比如 1.245 它会向 1.24 舍入)
(再次很好奇一些需要极其精确的小数运算是如何做到的 (ー_ー?))
(像存储金额这样的小数精度, 特别是银行, 损失一个精度都很严重啊)
规格化用于表示一些比较大的数字, 而非规格化用于表示一些相对较小的数字
这里顺便再看一下失去精度的结果, 看看他是怎么计算的
1 | printf("%f\n", 255.1f); |
奇怪的是, 它有两个数字
1 | 111 .LC0: |
可惜计算不出来, 这种格式是无穷大(不太明白 (눈_눈))
summary
简单来说, 其实也没有做笔记的必要 ┑( ̄Д  ̄)┍, 书上已经给了你答案
不过还好, 沉浸在思考的海洋中挺不错的(其实都快被淹死了 (눈_눈))
最后, 若无必要, 或者非常确信浮点数的范围, 否则不要使用单精度浮点数
如你所见, 单精度浮点数的范围很小, 一不小心还要失去精度
(这可能也是默认小数是双精度的原因)