浮点数杂想

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

IEEE754 浮点表示法

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

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

  • 符号 s(sign)
    s决定这个数是负数还是正数
  • 尾数 M
    M是一个二进制小数
  • 阶码 E(exponent)
    E 的作用是对浮点进行加权,这个权重加权,这个权重是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的取值也是离散的。所以图像不是连续的。但是找个图不容易,暂且将就着看吧。)

从图中来看 :

  • 整数
    对于整数来说,他对应的是2^x函数的第一象限。在该象限里,随着x也就是位数增加,可表示的范围y呈指数型增长。
  • 小数
    但是对于小数来说,对应的是2^x第二象限。在该象限里,随着位数的增加,可表示的范围y增长趋于平缓。也就是说位数扩增对于可表示的浮点数精度,效果并不明显。
  • 结论
    对应的大白话来说就是:可能对于底层硬件扩增了n位,但是所能表示的十进制数可能最小精度值一位都没移动。
    典型地,你看:
二进制 分数 十进制
0.0001 1/16 0.0625
0.00001 1/32 0.03125

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

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

  • think
    如果我们能把函数图像往 右边移动一下。让小数的表示能落在第一象限。那么对于同等位数的机器,增大 X 所能表示的小数范围 Y 就会相对更大一些,也就是最小精度能更精细一些。
    其实 想到这里的话,我就觉得用上浮点数表示法就很自然了,并不是刻意的。很水到渠成。向右移动这个2^x 函数, 得到的函数图像对应的就是2^(x-E) 函数。其中E这个偏移值 就对应了浮点数中,小数点移动的位数,也对应了浮点数表示法中的阶码(exponent)。
    其实这也有点 科学计数法的感觉。科学计数法是把n个0,写成m * 10^n这种形式,而浮点数表示为 M * 2^E

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

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

32位浮点数表示法

由上图:

  • sign
    符号位,用来区分正负
  • exponent
    这个其实应该写作exp才对,真正的exponent 应该指得是 E 阶码。
    并且 E = e-bias 或 E = 1-bias(当exp全为0 时)。其中bias定义为等于(2^k-1)的值,也叫做偏置值。
  • frac
    就是尾数,也叫位域了。
    exp!=0 && exp!=255 M = 1+f
    exp=0时 , M=f

有点绕,直接上实锤吧。
根据
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) 是为了浮点数能使用整数排序函数来进行排序

  • 舍入
    我们 还可以看到32位的浮点数,frace的位数是23,那么如果一个小数表示为二进制数,一直除不尽,浮点移位后 frace > 23呢?怎么办?
    舍入。根据IEEE754的标准是向最接近的能精确表示的小数舍入,如果最精确的有两个,就向最低位是偶数舍入。

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

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

浮点数不能直接比较相等

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

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


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

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

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

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

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

浮点数与PHP

  • 在PHP中如何比较浮点数
    关于浮点数的比较PHP手册也有介绍
    浮点数比较大小

    在PHP中,如果需要比较浮点数的大小,可以使用bccomp()函数进行比较。
    bccomp()其实是BCMath提供的一个函数。如果你需要更精确地进行浮点数的运算,都在BCMath中可以找到对应的函数。

值得一提的还有这段:


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

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

  • PHP中没有整型溢出,浮点数也不会溢出。
    与传统的强类型语言不同,php中并不会出现整型溢出现象。当一个int型的数溢出时会自动转为float类型,当一个float类型的数溢出时会维持float所能表达的最大值。


    溢出

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


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


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


参考阅读:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容