深入理解计算机系统(2.7)------浮点数舍入以及运算

  上一篇博客我们讲解了二进制小数如何表示以及IEEE浮点标准。而且我们也提到过因为这种表示方法限制了浮点数的范围和精度,浮点数只能近似的表示一个数。

  比如 数字1/5,我们能用十进制小数 0.2 准确的表示,但是我们却不能把它准确的表示为一个二进制小数,我们只能通过增加二进制表示的长度来提高表示的精度。如下:

  那我们该怎么办呢?

1、舍入

  对于不能精确的表示的数,我们采取一种系统的方法,找到“最接近”的匹配值,它可以用期望的浮点形式表现出来,这就是舍入。

舍入一共有四种方式,分别是向偶数舍入、向零舍入、向上舍入以及向下舍入

  可以看下面的例子:

   向偶数舍入,是将数字向上或向下舍入,使得结果的最低有效数字是偶数;而向零舍入则是向靠近零的值舍入;向上舍入则是向比它大的方向靠近;向下舍入则是向比它小的方向靠近。

  这四个我们可以用一个直角坐标系来理解:

  除了向偶数舍入以外,其它三种方式都会有明确的边界。这里的含义是指这三种方式舍入后的值x'与舍入之前的值x会有一个明确的大小关系,比如对于向上舍入来说,则一定有x <= x'。对于向零舍入来说,则一定有|x| >= |x'|。

  那么我们什么时候会使用向偶数舍入呢?

  1、比如舍入一组数值,计算这些值的平均数中引入统计偏差,如果向上舍入,那么得到的平均值会比这些数本身的平均值略高;向下舍入,则会偏低。而向偶数舍入则会避免这种偏差,在50%的时间内,它向上舍入,剩下50%的时间内,它向下舍入。

  2、在我们不想舍入到整数时,我们只是简单的考虑最低有效数字是奇数还是偶数。

通常情况下我们采取的舍入规则是在原来的值是舍入值的中间值时,采取向偶数舍入,在二进制中,偶数我们认为是末尾为0的数。而倘若不是这种情况的话,则一般会有选择性的使用向上和向下舍入,但总是会向最接近的值舍入。其实这正是IEEE采取的默认的舍入方式,因为这种舍入方式总是企图向最近的值的舍入。

 2、浮点运算

   在IEEE标准中,制定了关于浮点数的运算规则,就是我们将把两个浮点数运算后的精确结果的舍入值,作为我们最终的运算结果。正是因为有了这一个特殊点,就会造成浮点数当中,很多运算不满足我们平时熟知的一些运算特性。

  我们可以先看下面这段程序输出结果:

public void testFloat(){
		float f1 = 3.14f + 10000000000f - 10000000000f;
		float f2 = 3.14f + (10000000000f - 10000000000f);
		System.out.println(f1);
		System.out.println(f2);
		
	}

  结果都是 3.14 吗?

  我们看到 f1 的值是0,f2的值才是3.14。为什么呢?这是因为前面3.14f+10000000000f  时,会将 3.14 这个有效数值舍入掉,而导致最终结果为0.0

  f2 由于括号的存在,会先进行括号里面的运算,结果是0,然后在与3.14相加。

  也就是浮点运算不满足加法的结合律 a + b + c != a + (b + c)。同时乘法结合律也不满足:a * b * c != a * (b * c);还要分配律也不满足: a * (b + c) != a * b + a * c

  浮点数失去了很多运算方面的特性,因此也导致很多优化手段无法进行,比如我们试图优化下面这样一段程序。

/*   优化前       */
        float x = a + b + c;
        float y = b + c + d;
        /* 编译器试图省去一个浮点加法      */
        float t = b + c;
        float x = a + t;
        float y = t + d;

  上面优化前是进行了四次浮点运算,而编译器优化后只需要进行三次浮点运算。但是这中间的 x 可能回产生与原始值不同的值,因为它使用了加法运算不同的结合方式。所以现在的编译器都倾向于保守的方式,避免任何对功能产生的优化,即使是很轻微的影响。

  另外,浮点加法满足单调性属性:如果 a>=b,那么对于任何a、b以及 x 的值,除了 NaN,都有 x+a >= x+b。无符号或者补码加法不具有这个实数(和整数)加法的属性。

 3、总结

   好了,那么到此《深入理解计算机系统》前面两章的内容我们就结束了,这里我们主要需要了解无符号和补码编码格式,以及它们的运算。然后扩展到整数的表示和运算,实数的表示和运算,在实际编程中,我们会经常和数打交道,如何避免一些错误,相信看完后会有个大概的了解了。那么接下来我们将学习第三章,这将是一个全新的世界——汇编语言。这肯定比我们前面讲的要有趣多了,前面都是和0或者1这样的数字打交道,后面至少是一种编程语言,相信会更加有趣。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏数据结构与算法

小米OJ刷题日志

$f[i][j]$表示第一个队列匹配到了$i$位置,第二个队列匹配到了$j$位置是否可行

3752
来自专栏SeanCheney的专栏

《Pandas Cookbook》第05章 布尔索引1. 计算布尔值统计信息2. 构建多个布尔条件3. 用布尔索引过滤4. 用标签索引代替布尔索引5. 用唯一和有序索引选取6. 观察股价7. 翻译SQ

第01章 Pandas基础 第02章 DataFrame运算 第03章 数据分析入门 第04章 选取数据子集 第05章 布尔索引 第06章 索引对齐 ...

1172
来自专栏Java开发者杂谈

遍历算法(1)

遍历算法主要用在在处理迷宫问题,图,最短路径,以及枚举所有可能等问题上。下面我们通过一个简单的例子,来入门深度优先和广度优先算法: 1 package co...

3246
来自专栏偏前端工程师的驿站

基础野:细说浮点数

Brief                                 本来只打算理解JS中0.1 + 0.2 == 0.30000000000000004...

1919
来自专栏Leetcode名企之路

【Leetcode】60. 第k个排列

给定 n 的范围是 [1, 9]。 给定 k 的范围是[1, n!]。 示例 1:

1772
来自专栏Jacklin攻城狮

简谈常用算法

782
来自专栏TensorFlow从0到N

讨厌算法的程序员 5 - 合并算法

本篇介绍的“合并”算法,是为后面学习“归并排序”的一个准备。合并算法是归并排序中的一个子算法,请注意两者之间的关系和差异。 之所以把它独立成一篇,一方面是一旦...

3405
来自专栏前端说吧

JS-用js的for循环实现九九乘法表以及其他算数题等

3486
来自专栏ml

向前字典排序

          next_permutation算法对区间元素进行一次组合排序,使之字典顺序大于原来的排序,有如下两个使用原形,对迭代器区间[first,l...

2569
来自专栏人工智能LeadAI

讨厌算法的程序员 | 第五章 合并算法

本篇介绍的“合并”算法,是为后面学习“归并排序”的一个准备。合并算法是归并排序中的一个子算法,请注意两者之间的关系和差异。 之所以把它独立成一篇,一方面是一旦了...

3595

扫码关注云+社区