浮点数绝对是计算机系统里最神秘的一种数值。在不了解它之前,很难想象同样是32位数值,int只能表示最大2的31次方,而浮点的表示范围却是-126到正的127次方。

以及浮点还能表示无穷大,NaN等特殊值。

我们也被教育浮点数计算的一些计算规则,例如很大的浮点跟小的浮点计算的时候,要先算小的。
例如判断浮点跟0时,时常使用 if (fval <= 0.00001f) 这种表达式来判断数值是不是可以被当做0了。

今天我们就来一起走近神奇的浮点数。

2.4 浮点

浮点数对形如 V = x ∗ 2 y V = x*2^y V=x∗2y 的有理数进行编码。

大约在1985年,IEEE标准754的推出,才有了浮点数标准。

由于浮点数的表示方法,必然产生舍入的问题,当一个数字无法精确用这种方式表示的时候,就需要向上或者向下进行舍入。

不难理解32位浮点表示范围比32整数范围大那么多,那么必然出现很多数值其实浮点数是表达不了的。

2.4.1 二进制小数

理解浮点数的第一步是理解二进制小数。

十进制小数可以表示为

d m d m − 1 ⋯ d 1 d 0 . d − 1 d − 2 ⋯ d − n d = ∑ i = − n m 1 0 i × d i d_md_{m-1} \cdots d_1d_0.d_{-1}d_{-2}\cdots d_{-n} \\ d = \sum_{i=-n}^{m}10^i \times d_i dm​dm−1​⋯d1​d0​.d−1​d−2​⋯d−n​d=i=−n∑m​10i×di​

同样的,二进制小数可以表示为:
b = ∑ i = − n m 2 i × b i b = \sum_{i=-n}^{m}2^i \times b_i b=i=−n∑m​2i×bi​
小数点左移相当于除以2,小数点右移相当于乘以2 。注意形如 0.111 ⋯ 11 1 2 0.111\cdots 111_2 0.111⋯1112​的数表示刚好小于1的数,例如0.111111表示 63 64 \frac{63}{64} 6463​, 也可以用$1.0-\epsilon $来表示。

就是说二进制小数是形如 1/2 + 1/4 + 1/8 + 1/16等组合成的,而小数实际是连续而无限的,二进制小数却是离散的,必定存在大量无法表达的数值。

例如,1/5无法被二进制小数准确表示,即使怎么加长精度也不行。

案例,海湾战争中的一个拦截导弹系统中使用0.1秒的间隔作为计数器增加,而0.1由于无法准确用二进制表示,
而该系统又使用了一个累计式计数法,结果就是长时间运行后,累计误差变的无法接受。
结果就是拦截导弹系统压根拦截不到导弹。对于这种情况,应当是有相对时间,而不是使用固定间隔。

2.4.2 IEEE浮点表示

IEEE浮点标准用 V = ( − 1 ) s × M × 2 E V =(-1)^s \times M \times 2^E V=(−1)s×M×2E来表示一个数:

  • 符号s决定数值得正负
  • 有效数M是一个二进制小数,它的范围在1 ~ 2- ϵ \epsilon ϵ 之间,或者在0~1- ϵ \epsilon ϵ之间。
  • 指数E是2的幂,可能是负数,它的作用是对浮点数加权。

于是浮点数的位被划分为三个域,以编码这些值:

  • 一个单独的符号位,用来表示S
  • k位指数域,用来表示E ,后称exp
  • n位小数域,用来表示有效数M,但是被编码的值也依赖于指数域的值是否等于0. 后称 frac

在单精度浮点格式中(C中的float), s=1,k=8,n=23 。而双精度中,s=1, k = 11, n = 52 。如下图

看到这里,我们就大概明白了一点,所谓有效位,就是能精确表达的部分,这里只有23位,加上后面会提到的隐藏有效位是24位。也就是说大于2的24次方的整数存到float中,实际值可能会变化。

例如 int a = 1 << 24 + 1;
float b = a;
int c = b;
则a == c 很大可能是不成立的。稍加验证可知。

所以说啊,比较大的整数就不要转浮点数了,很容易出问题。

根据exp的值,可以分为三种情况

1. 规格化值

即当指数域exp不全为0也不全为1时,属于此种情况。

此时,指数域的值解释为表示偏置形式的有符号整数,即E = e - Bias,e的值就是指数位的二进制的值,也就是 e k − 1 ⋯ e 1 e 0 e_{k-1}\cdots e_1e_0 ek−1​⋯e1​e0​ , 而Bias则为 2 k − 1 − 1 2^{k-1} -1 2k−1−1 。根据上面指数位的长度可以得到:单精度是 exp取值范围为-126 ~ 127 双精度是为-1022~1023

指数也能表示正负,为什么不适用前面有符号整数的补码形式呢?

只能说却是设计精妙,补码的-1表达全是1,而指数域全1是特殊用途的,所以是相冲突的。又为了跟非规格数值的平滑过渡,所以这是一套创新的计算方法。

小数域frac解释为f, 0 ≤ f ≤ 1 0 \le f \le 1 0≤f≤1 ,也就是小数点在最高有效位的左边。而有效数则定义为 1+f。这是因为我们总是可以通过调整指数来把小数表示为1.xxx的形式,既然第一位一直是1,则可以略去不显示,从而额外获得一位有效位。

凭空获得一个有效位,真是妙极。想一想可能可以在其他方面借鉴一下这个设计。

2. 非规格化值

当指数域为全0时,指数的值是 E = 1 - Bias, 而有效位则不包含隐含的那个有效位了。

无中生有的有效位也并不全是好处,想表达更大的数值的时候,就很有好处,想表达更小的数值的时候,这个有效位便成了障碍。

为什么指数值使用1 - Bias而不是 - Bias呢?后面将会看到这种方式提供了一种从非规格化值平滑转换到规格化值的方法。

非规格化值,提供了一种表示0的方法。否则隐含的有效位1导致无法表示0. 由于符号位的存在,则实际存在+0.0 和-0.0

这个符号位导致+0 -0的情况,在前面二进制补码的时候,也曾遇到一种替代的表示方法:符号位表示法,当然那是一个被淘汰的方法。

另一个功能是用来那些非常接近于0.0的数。

3. 特殊数值

当指数域全为1时,标识一些特殊值。

  • 当小数位全为0时,表示无穷大,有正无穷和负无穷。
  • 而当小数位非全0时,则表示“NaN”,表示不是一个数。

通过指数位的值就能有三种形态,简直就是二进制中的战斗机。

通过上表可以发现,非规格化数的取值范围在 1/512 到 7/512之间,而规格化数最小为8/512,这个平滑转变归功于非规格化E的定义为1-Bias,这样就补偿了非规格化数隐含的1,从而可以平滑过渡。

所谓补偿了1,咋补偿的呢?

隐含的1相当于是要求有效位至少左移了1位。左移一位就是乘以2,反映在指数上就是指数的数值+1。所以原本-Bias +1 就变成了 1 - Bias 。理解成-Bias + 1就好理解了。

有意思的是,从上表可以看到,上面的二进制解释为无符号整数,它也是升序排列的,这是设计如此,从而可以使用整数的排序函数来进行排序。

整数转浮点数案例
以12345转换为float为例,它的二进制是[11000000111001],

  • 有效位计算。通过小数点左移13位,可以得到 12345 = 1.100000011100 1 2 ∗ 2 13 12345=1.1000000111001_2 * 2^{13} 12345=1.10000001110012​∗213 .使用IEEE形式来编码,第一位作为隐藏有效位可以丢掉,得到有效位为[1000000111001] ,并补齐缺少的10位得到 [10000001110010000000000]。

  • 指数位。为了构造指数位,13加偏置值127得到140(计算的时候要减掉17,这里反向构造,则需要加上127),即[10001100]。

  • 符号位。符号位为0 。

最终可得到 [0 10001100 10000001110010000000000]。

这么一看,整数和浮点数互相转换还挺费劲的,效率那肯定是不高了。

2.4.4 舍入

由于表示方法限制了浮点数的范围和精度,所以浮点数只能近似的表示实数。所谓近似就是向上或者向下舍入到能够表示的最近的数值。

几种舍入方式:

  • 偶数舍入:数字向上或者向下舍入,使得结果的最低有效位是偶数
  • 向零舍入
  • 向下舍入
  • 向上舍入

向零舍入是浮点转整型的默认使用方法。

偶数舍入看起来有点奇葩,然而它却是浮点数舍入采用的方法。书上分析了一通,感觉还是不太理解,算了,这不重要 : )

2.4.5 浮点运算

浮点加法是可交换性的,就是说x+y = y + x 。然而需要特别注意的是浮点加法不满足结合性。例如3.14 + le10 - le10 = 0. 因为3.14倍舍入了。而3.14 + (le10 - le10) = 3.14 。

来了来了,这部分很重要!

所以有多个浮点数想加减,那么应当大数和大数操作,小数和小数操作。尽量减少舍入的发生。

同样乘法也不具备结合性,涉及溢出及舍入的问题。例如(le20* le20) * le-20 结果是无穷大,而 le20 * (le20 * le-20)的结果为le20.
以及乘法在加减法上也不具备分配性。例如 le20 * (le20 - le20) = 0. 而 le20 * le20 - le20 * le20 得到NaN(无穷大跟无穷大操作得到NaN)。

浮点数简直就是一匹未经驯化烈马,需要好好对待每个操作才能驯服它。

2.4.6 C语言中的浮点

int,float,double之间的转换:

  • int转成float,不会溢出,但可能会舍入
  • int,float转double,不会溢出,也不会精度丢失。
  • double转int,可能会变无穷大,可能会舍入
  • float,double转int,都会以向0舍入的方式舍入。比如1.999也会舍入成1.

题外:Intel IA32的浮点计算

IA32非同一般的属性是浮点寄存器使用80位的高精度来存储float和double。所以一个值在寄存器的时候,精度很高,然而转存到内存中的时候,又将下降为32位或64位。这样引起的一个奇特问题是,当比较两个浮点值,如果一个在寄存器,一个在主存时,即使两个看起来相同的值,比较结果可能是false。

14行r2刚刚赋值,还存储在寄存器中。立刻进行比较,则r1在主存,而r2在寄存器。16行则相当于是刷新寄存器,将r2从寄存器中挤走。于是17行得到true的结果。当denom为10或者其他导致结果无法在有限精度表达的数值时才会有此现象。

Ariane 5 浮点溢出的高昂代价1996年6.4,Ariane5火箭发射37秒后就偏离轨道并解体爆炸了。
后来检测出的结果是Ariane4中曾经确定水平速度不会超过16int值,
在Ariane5中简单复用了这部分,最终数值溢出了。

由于浮点造成的灾难还少吗,前有拦截导弹变成摆设,后有航天火箭瞬间爆炸,是时候喊出我们的口号:珍爱生命,远离浮点 : )

2.5 小结

  • C语言标准规定在无符号有符号转换时,基本的位模式不应该发生改变。 (这个一看效率就杠杠滴)
  • 由于溢出,x*x 可能得到负数 (数学定理遇到计算机都可以抛到一边)
  • (x<<3)-x 可以取代7*x作为优化
  • 通过补码学习,~x + 1 = -x
  • 假设我们需要一个[0001111]的位模式,可以通过 2 k − 1 , 即 ( 1 < < k ) − 1 2^k - 1,即 (1 << k) -1 2k−1,即(1<<k)−1来得到。
  • 注意浮点数不遵守算术中的结合性。

吾读 - 《深入理解计算机系统》第二章 信息的表示与处理 (二)浮点相关推荐

  1. 深入理解计算机系统读书笔记(第二章 信息的表示和处理)

    这里写自定义目录标题 第二章 信息的表示和处理 2.1 信息存储 2.1.1 十六进制表示法 2.1.2 字数据大小 2.1.3 寻址和字节顺序 2.1.4 表示字符串 2.1.5 代码表示 2.1. ...

  2. 《深入理解计算机系统》(CSAPP)第二章——信息的表示和处理 知识点总结

    CASPP 第二章 信息的表示与处理 2.1 信息存储 2.1.1 字数据大小 2.1.2 寻址和字节顺序 2.1.3 布尔运算 2.1.4 位移运算 2.2 整数表示 2.2.1 整数类型数据 2. ...

  3. 计算机系统导论——读书笔记——第二章 信息的表示和处理(持续更新)

    第二章 信息的表示和处理 2.1 信息存储 2.1.1 十六进制 2.1.2 字数据大小 2.1.3 寻址和字节顺序 1.地址:对象所使用的字节中最小的地址 2.大端法:最高有效字节在前 小端法:最低 ...

  4. 深入理解计算机系统——第九章 Virtual Memory

    深入理解计算机系统--第九章 Virtual Memory 9.1 Physical and Virtual Addressing 9.2 Address Spaces 9.3 VM as a Too ...

  5. 第二章 信息系统集成及服务管理

    第二章 信息系统集成及服务管理 2.1 信息系统集成及服务管理体系 一.信息系统集成及服务管理的内容 所有以满足企业和机构的业务发展所带来的信息化需求为目的, 基于信息技术和信息化理念而提供的专业信息 ...

  6. 信号与系统——初识到理解(第二章——信号与系统)

    目录 第二章 信号与系统 2.1 什么是信号及信号如何表征 2.1.1信号的概念 2.1.1信号的表征方法 2.2 信号如何分类 2.2.1 信号类别及基本概念 2.2.2 确定信号与随机信号 2.2 ...

  7. 深入理解Magento – 第二章 – Magento请求分发与控制器

    深入理解Magento 作者:Alan Storm 翻译:Hailong Zhang 第二章 – Magento请求分发与控制器 Model-View-Controller (MVC) ,模型-视图- ...

  8. 鸟哥的Linux私房菜(基础篇)-第二章、 Linux 如何学习(二.3. 有心朝Linux作业系统学习者的学习态度)

    第二章. Linux 如何学习 最近更新日期:2009/08/06 3. 有心朝Linux作业系统学习者的学习态度 3.1 从头学习Linux基础 3.2 选择一本易读的工具书 3.3 实作再实作 3 ...

  9. 鸟哥的Linux私房菜(基础篇)-第二章、 Linux 如何学习(二.2. 鸟哥的Linux苦难经验全都录)

    第二章. Linux 如何学习 最近更新日期:2009/08/06 2. 鸟哥的Linux苦难经验全都录 2.1 鸟哥的Linux学习之路 2.2 学习心态的分别 2.3 X window的学习 鸟哥 ...

最新文章

  1. C# socket编程实践——支持广播的简单socket服务器
  2. 容器网络规范CNM vs. CNI
  3. BugKuCTF WEB 计算器
  4. jmeter分布式测试配置
  5. 可视化分析WEB访问:logstalgia
  6. 第18课 闰年与平年 《小学生C++趣味编程》
  7. 设计模式学习---(2)工厂模式
  8. 可以运行python的路由器_用python管理Cisco路由器
  9. java构造函数中启动线程_通过构造器启动线程的实现方式及其缺点记录。
  10. 如何挖掘大数据的价值
  11. LeetCode—4.滑动窗口
  12. MySQL集群和主从复制分别适合在什么场景下使用
  13. [CF1149C](Tree Generator)
  14. win10禁用驱动程序强制签名_如何将驱动程序注入Windows 10 WIM / ISO安装映像?
  15. 思科模拟器路由表怎么看_思科路由器查看配置命令
  16. 实例99:使用AEGAN对MNIST数据集压缩特征及重建
  17. 从钉钉后台API获取企业通讯录以后,获取每个人的钉钉运动步数
  18. (编程题)相邻数字相乘为偶数
  19. JDBC如何有效防止SQL注入
  20. 微信JS-SDK实现自定义分享功能,分享给朋友,分享到朋友圈

热门文章

  1. 关于微信小程序上传按钮是灰色不能点击的问题
  2. exlsx表格教程_福利来了!Eexcel功能由你决定!什么?表格只需点两下就做好了?...
  3. 解决软件或游戏缺少api-ms-win-core-fibers-l1-1-1.dll无法启动问题
  4. 实用的 iPhone 解锁:4Easysoft iPhone Unlocker中文
  5. 计算机网络——华为QINQ详解及其实验配置
  6. UniApp:方法篇:微信小程序海报生成和保存
  7. linux版本的mongodb客户端,Linux版本MongoDB安装
  8. 新手小白如何自学会计?
  9. UI、ID、UE和GUI,这些都是什么
  10. nodejs用xlsx导出excel表