这两天研究了下CSAPP中的浮点数的知识,结合这些知识,终于可以解答之前关于float的一些迷思。
C语言中浮点数相等的比较为什么不能用==?
A:浮点数的舍入方法引发的。如果要搞清楚这个问题,需要明白下面的两个知识点。
- 计算机判定两个浮点数相等,基于它们的在计算上的二进制的表示的比特位是否相等。
- 计算机上用有限bit位表示的浮点对应到数学上的实数,表示的不是一个点而是一个区域。仔细考察这个结论,参考信息的表示和处理中关于浮点数舍入部分的描述和之前的浮点数在计算机中的表示,半精度浮点数可以表示的数中有如下的数[126.3, 126.4, 126.44, 126.5],它们是离散分布在数轴上的,这3个数对应的二进制的表示分别是\([(1.1111100101)_2\times 2^{6},(1.1111100110)_2\times 2^{6},(1.1111100111)_2\times 2^{6}]\),根据之前分析的round to even的舍入规则,所有位于\([(1.11111001011)_2\times 2^{6},(1.11111001101)_2\times 2^{6}]\)之间的浮点数都会舍入到\((1.1111100110)_2\times 2^{6}\),换算过来也就是所有位于\([126.34375,126.40625]\)区间的实数都会映射到这个值。如果有下面的语句
float16 a = 126.4;
float16 b;
if (a == b) {
//
}
那么b = 126.35
或者b = 126.38
或者b = 126.3876
都是成立的,因为它们都会在浮点数的表示体系下舍入到126.4。数学上不相等,但是在计算机里面是相等的,这就是数学意义和计算机表示意义上的背离,
浮点数的
==
和数学意义上严格的相等是不等价的。
所以为了追求相等的精确性,可以利用\(|a-b|<\epsilon\)的方式,将\(b\)限定在以\(a\)为中心的很短的绝对值区间上,\(\epsilon\)越小,二者的相等程度就越高。所以可以使用下面的代码做一个等价的处理
#define EPSILON 1e-6
float16 a = 126.4;
float16 b;
if (fabs(a - b) < EPSILON) {
//
}
什么是浮点数的“大数吃小数”问题?原理是什么?
A:浮点数在计算中的舍入引发的。在深入理解计算机系统第2章浮点运算的章节,作者提到过
使用单精度浮点,3.14 + 1e10 - 1e10 = 0.0
这就是一个大数吃小数的问题,严格来讲,在C语言中,当一个比较大的浮点数与一个相对小很多的浮点数相加时,结果是那个比较大的浮点数,就好像小数被“吃掉”了一样。上面的例子中,3.14 + 1e10 = 1e10,也就是说从计算机的角度来看,3.14 + 1e10的二进制表示与1e10的二进制浮点表示没有区别。这是怎么回事?
参考信息的表示和处理中的内容,模拟它们的二进制加法就能得到答案。
- 按照浮点数的表示法,在计算机中\((3.14)_{10} = (1.10010001111010111000011)_2\times 2^{1}\),而\((1e10)_{10} = (1.00101010000001011111001)_2\times 2^{33}\)。两个的二进制的小数都是23位
- 将两个数字的指数对齐,那么\((3.14)_{10} = (1.10010001111010111000011)_2\times 2^{1} = (0.0000000000000000000000000000000110010001111010111000011)_2\times 2^{33}\),但是float的小数点精度只能到23位,所以舍入到小数点后23位,结果就是浮点数的0
- 指数相同,小数相加,1e10的小数加的就是0,所以结果还是1e10。
从上面的分析可以看到,如果两个相加的数字由于指数悬殊,而导致在对齐指数中,较小数的浮点数用有限精度的尾数表示为0的时候,就会发生这样奇怪的事情。
全文完