技术文我爱编程技术干货

浮点数杂想

2018-08-04  本文已影响199人  刀斧手何在

距上一次写原码,反码,补码杂谈已经有一段日子了。关于浮点数,我也有一些自己的思考,想写出来,但是日月如梭,人懒如dog呀。所幸有一个周末,白无聊懒,我就开始动笔了。

IEEE754 浮点表示法

根据IEEE754 表示法,一个数可以表示为 :

V= (-1)^S*M*2^E

好了,从《深入理解计算机系统》中把这段抄了下来,本文终..........

引言

浮点数 (floating point number) ,顾名思义,小数点(point)浮动表示。
从上面我们可以看出浮点数的表示法,比原来我们表示整数的时候直接把一个十进制数表示为二进制数,看上去复杂多了。
那么,我们为什么需要浮点数表示法呢?

我个人的 观点是

使用浮点数表示法 是为了解决 在有限位数里,能表示的小数太少 。并非 为了解决 小数在计算机中无法精确存储。

其实浮点数表示法 并不复杂。我觉得它的想法和高中数学中的一个函数有一种千丝万缕的关系。

从二进制表示法说起

计算机的存储方式是通过二进制存储的。如果按照整数的存储方式,我们要表示小数也同样要先将十进制的小数转化为二进制表示法。
例如:

二进制 分数 十进制
0.0 0/2 0.0
0.1 1/2 0.5
0.01 1/4 0.25
0.001 1/8 0.125
0.0001 1/16 0.0625
0.00001 1/32 0.03125

结果我们发现其实大部分的小数,因为无法被2整除,而无法用二进制精确表示。
其实从严格意义来说,二进制表示法也不能表示所有的整数,因为位数有限。那么我们如何 表示更大的数呢?扩增位数。
那么对于二进制小数,我们也可以借鉴这种想法,扩增位数,表示更多的小数,对于某些无法精确的小数总是可以在有限范围内通过舍入来表示。

定点表示法的困境

可实际上,我们如果把扩展位数x和所能带来的表示范围y,画成图像。
从图像上看 其实 就有点像高中的幂指数函数 :

2^x 幂指数函数图

(但是x的取值是离散的,只能取整数,y的取值也是离散的。所以图像不是连续的。但是找个图不容易,暂且将就着看吧。)

从图中来看 :

二进制 分数 十进制
0.0001 1/16 0.0625
0.00001 1/32 0.03125

二进制扩增了一位,但是对应的十进制精度从0.0625-> 0.03125实际上最小位上的权还是10^(-2) 并没有移动。

这就是定点数二进制表示法在小数部分应用遇到的困境。怎么办?

32位浮点数的二进制表示法

那么根据IEEI754标准的,32位浮点数是如何表示的呢?这里我借用了阮一峰老师画的图。他写的浮点数介绍的文章讲得很简洁明了,一目了然。特意推荐一下:浮点数的二进制表示

32位浮点数表示法

由上图:

有点绕,直接上实锤吧。
根据
V= (-1)^S*M*2^E
上图的二进制数表示的浮点数为:

f : 0.01(2) = 0.25(10)
e : 01111100(2) = 124(10)
V = (-1)^S*M*2^E
= (-1)^0 * (1+ 0.25)  * 2^(124-127)
=  1* 1.25*2^(-3)
= 1 * 1.25 * 0.125
= 0.15625

再举个栗子:3.0625的浮点数二进制表示法是

第一步:
3.0625(10) = 11.0001(2)
第二步 : 小数点移位
11.0001 = 1.10001(2)* 2^(-1)(10)
第三步 : 根据(-1)^S*M*2^E  写出s ,exp ,f
s = 0 
f = 10001
exp = E+bias = -1+127 = 126 = 01111110
则3.0625 的浮点表示法为
0 01111110 10001 000000 000000 000000

我们可以看到s和frace 总是比较容易理解的,就是E = e - bias 或 E = 1-bias这个会比较难以理解。
这个我个人的看法是 :

用浮点数表示的意义在于,能在有限的位数里表示更多的数。

E的值如果只是负数的话,或只是正数的话。就只能表示小数或只能表示整数。阶码的正负值使浮点数的表示能同时覆盖到整数和小数。
那么,为什么E 的表达方式不和我们补码的那种模运算表示法一样呢?这个我也还没想明白。
但是,对于这个疑问?《深入理解计算机系统》第二章里的介绍关于 规格化数 和 非规格化数这几个概念时 有提过这样设计的原因 :
(1) 是为了将非规划化数和平滑过渡到规格化数
(2) 是为了浮点数能使用整数排序函数来进行排序

这样可以保证50%的情况是向上舍入,50%是向下舍入,不会在计算这些数的平均值时带入偏差。

好吧,还是《深入理解计算机系统》第二章说的,它有详细地介绍。但是我已经放弃了。哈哈哈.......

浮点数不能直接比较相等

由于浮点数的非精确存储,所以两个浮点数不能直接比较相等。

未必。这里,请允许我插入一张图-----轮子哥在知乎的回答。


浮点数不能直接用==比较?

由于浮点数的非精确存储,带来的问题通常是在计算时引起的。并且实际上计算的顺序也会影响丢失的精度范围。而对于未经过计算的,总不可能上一行代码中的二进制表示法和下一行的就不一致了。他就算不精确存储也应该有同一种相同的舍入策略(指存储时的舍入策略,而非计算时的截断策略)。所以,对于未经过计算的数值大可放心地直接进行数值比较。
常见场景 : 用户输入一个小数,判断是否小于等于一个常量(小数)。这种可以放心地使用 <=

那么如果对于经过计算的浮点数,要如何比较是否相等呢?
上一段熟悉的代码,大一在机房看到比较两个数是否相等要取绝对值 < 0.00001 一脸懵逼.gif 影响深刻。

    #define EPSILON 0.000001    //根据精度需要
    if ( fabs( fa - fb) < EPSILON )
    {
            printf("fa<fb\n");
    }

为何要取0.00001? 好吧,我也在找这个答案。

浮点数与PHP

值得一提的还有这段:


浮点数的精度
 2^ (-16) = 0.0000152587890625   => 接近于精度  0.00001

这会不会就是浮点数比较大小的epsilon 要取0.00001的由来?

第一次发现这个是因为大三学单片机,调C代码的下溢bug。然后就很好奇在php中如果int类型的数是否有溢出的问题。用上面的代码测了一下,发现结果并没有,这个具体是怎么做到的,我也很好奇。就先把坑挖在这里留待以后解决吧。


2018:10:01 新增加
php中int类型和float类型溢出时,自动转化这个问题。鸟哥的博客有介绍。
详见 : 关于PHP浮点数你应该知道的(All 'bogus' about the float in PHP)


以上就是我对浮点数的 一些思考。由于作者见识有限,文中难免纰漏繁多。欢迎读者交流指正。


参考阅读:

上一篇下一篇

猜你喜欢

热点阅读