深入理解计算机系统(2.6)---二进制整数的乘、除法运算(重要)【困难度高】

本文转载地址:http://www.cnblogs.com/zuoxiaolong/p/computer10.html

  2.5我们着重介绍了二进制整数的加、减运算,本次我们继续介绍乘、除运算。

引言

  运算一直是程序运行当中一个重要的环节,而在二进制的运算过程当中,加法运算又是重中之重,它基本上奠定了二进制运算的基础。因为无论是减法还是乘法,都可以由加法运算来替代,唯有除法不能由加法替代。

  了解计算机运算的规律,可以有助于我们理解很多程序代码上无法理解的内容。比如上章提到的溢出问题,在了解了加法运算的原理之后,相信猿友们都可以轻松的知道为何有些运算会得到意想不到的结果。

  这里还需要提一点的是,不同的处理器所采取的运算方式可能是有细微的差别的,因此也不能一概而论。因此我们大多时候会尽量讨论运算的抽象数学特性,抽象的东西大部分时候总是可靠的,这种特性为跨平台提供了基础,不过也并非总是如此,毕竟LZ只听说过浮点数运算标准,还没听说过整数运算标准,不知道究竟是LZ孤陋寡闻了,还是确无此物。

  正因如此,我们了解一下这些运算的抽象性,会有助于我们理解程序代码级无法理解的东西。

无符号乘法

  无符号的乘法与加法类似,它的运算方式是比较简单的,只是也可能产生溢出。对于两个w位的无符号数来说,它们的乘积范围在0到(2w-1)2之间,因此可能需要2w位二进制才能表示。因此由于位数的限制,假设两个w位的无符号数的真实乘积为pro,根据截断的规则,则实际得到的乘积为 pro mod 2w

补码乘法

  与加法运算类似,补码乘法也是建立在无符号的基础之上的,因此我们可以很容易的得到,对于两个w位的补码数来说,假设它们的真实乘积为pro,则实际得到的乘积为 U2Tw(pro mod 2w)。

  上面的式子我们有一个假设,就是假设对于w位的两个补码数来说,它们的乘积的低w位与无符号数乘积的低w位是一样的。这意味着计算机可以使用一个指令执行无符号和补码的乘法运算。

  在书中给出了这一过程的证明,我们来大概看一下,这里主要应用了无符号编码和补码编码的关系,其中x’和y’分别代表x和y的补码编码。

  这里运用的主要技巧就是2w mod 2w = 0。

乘法运算的优化

  根据我们小学所学的乘法运算,我们知道,假设两个w位的二进制数相乘,则需要进行w次与运算,然后进行w - 1次加法运算才能得到结果。从此不难看出,乘法运算的时间周期是很长的。因此计算机界的高手们想出了一种方式可以优化乘法运算的效率,就是使用移位和加法来替代乘法。

  上述优化的前提是对于一个w位的二进制数来说,它与2k的乘积,等同于这个二进制数左移k位,在低位补k个0。在书中对这一等式进行了证明,过程如下。

  这个过程主要应用了无符号编码的公式,各位猿友应该不难看懂。

  有了上面的基础,我们就可以使用移位和加法对乘法优化了。对于任意一个整数y,它总能使用二进制序列表示(假设不超过二进制的表示范围),因此我们可以将x和y乘积的二进制序列表示为如下形式(此公式在书中没有展现)。

x * y = x * (yw-12w-1 + ... + y020) =  (x << w-1) * yw-1 +....+ (x << 0 ) * y0

  我们举个例子,对于x * 17,我们可以计算x * 16 + x = (x << 4) + x ,这样算下来的话,我们只需要一次移位一次加法就可以搞定这个乘法运算。而对于x * 14,则可以计算 x * 8 + x * 4 + x * 2 = (x << 3) + (x << 2) + (x << 1) ,更快的方式我们可以这么计算,x * 16 - x * 2 = (x << 4) - (x << 1) 。

  这里最后需要提一下的是,加法、减法和移位的速度并不会总快于乘法运算,因此是否要进行上面的优化就取决于二者的速度了。

无符号除法

  除法与乘法不同,它不满足加法的分配律,也就是设y = m + n , x/y != x/m + x/n。而且不幸的是,它有时候会比乘法运算更慢,但是我们只能针对除数可表示为2k的除法运算进行优化,转换为算数右移或者逻辑右移k位的运算(无符号数为逻辑右移,为正数时,逻辑右移与算术右移效果一样)。

  由于是除法,因此我们会涉及到舍入的问题。这里定义└x/y┘的值为a‘,x/y为a,则对于a‘,它是唯一一个整数,满足 a‘ =< a < a‘+1。

  比如└2.1┘的值就为2,而对于└-2.1┘则为-3,如果本身就是整数,则等于自身。

  书中给出了无符号数除以2k等价于右移k位(w > k >= 0)的证明,这一证明过程相对比较复杂一点,LZ这里给出一个相对简单的证明方式,不采用书上的证明。如果各位看LZ的证明看不懂的话,也可以参照一下书上的方式。

  我们假设对于一个w位的无符号数来说,假设它的位表示为[xw-1....x0],则x = xw-12w-1 + .... + x020 。因此就有以下结果。

x/2k = xw-12w-1-k +... + xk20 + xk-12-1 +...+ x02-k = B2Uw-k([xw-1....xk]) + xk-12-1 +...+ x02-k

  由于xk-12-1 +...+ x02-k <= 2-1 + .... 2-k = (1-(1/2)k) < 1 (这里是证明的关键一步,先假设所有位为1,则利用等比数列求和公式即可得到),因此有└xk-12-1 +...+ x02-k┘ = 0。

  因此我们可以得到└x/2k┘ = └B2Uw-k([xw-1....xk])┘ + └xk-12-1 +...+ x02-k┘ = └B2Uw-k([xw-1....xk])┘ = B2Uw-k([xw-1....xk]) = x >> k。

  更直观的,我们可以使用程序验证这一结果,看下面的Java代码。

public class Main {

    public static void main(String[] args) {
        int a = 17;
        int b = 8;
        int c = a/b;
        System.out.println("a:" + Integer.toBinaryString(a));
        System.out.println("b:" + Integer.toBinaryString(b));
        System.out.println("c:" + Integer.toBinaryString(c));
        System.out.println("a >> 3:" + Integer.toBinaryString(a >> 3));
    }
}

  这段程序的结果如下,可以看出a/b的结果就是a右移3位的结果,也就是结果等于a >> 3。

  

补码除法

  由于刚才我们的程序使用的都是正数,因此虽然Java中没有无符号数,不过我们可以模拟出无符号数的效果。也可以认为,补码除法在被除数为正数的情况下,与无符号编码是一样的效果(我们不考虑除数为负的情况,因为被除数与除数的符号位可以相互抵消,以下也一样),不过当被除数为负数时就不同了。这里在介绍补码除法之前,我们先来看一下,当a为负数时的结果,也就是此时会采用补码编码。

  我们将刚才的程序稍微修改一下,如下。

public class Main {

    public static void main(String[] args) {
        int a = -17;
        int b = 8;
        int c = a/b;
        System.out.println("a:" + Integer.toBinaryString(a));
        System.out.println("b:" + Integer.toBinaryString(b));
        System.out.println("c:" + Integer.toBinaryString(c));
        System.out.println("a >> 3:" + Integer.toBinaryString(a >> 3));
        System.out.println("c:" + c);
        System.out.println("a >> 3:" + (a >> 3));
    }
}

  它得到的结果如下,有点出乎意料。

  这次为了便于观看,我们将c和a >> 3的整数值打印了出来,发现移位运算的结果是-3,而a/b的结果为-2。可以看出我们a/b的结果是我们所期望的,可是移位的运算结果似乎在舍入的时候出现了问题。

  其实这个问题出现的原因很简单,补码编码与无符号编码类似,对于位表示都有└x/2k┘= B2Tw-k([xw-1....xk]) = x >> k。不过此时由于是负数,所以采取了向下舍入。上面已经提到过└-2.1┘的值为-3。

  因此,我们得到这样一个结论,当有舍入发生时,将一个负数右移k位不等价于把它除以2k

除法的补救

  既然在舍入时,一个负数右移k位不等价于把它除以2k。那么为了使用这种优化,计算机界的大神们自然要想办法解决这个问题。于是他们想出了一个办法,即“偏置”这个值(不得不佩服这些大神们)。

  首先我们定义┌x/y┐的值为a‘,x/y为a,则对于a‘,它是唯一一个整数,满足 a‘-1 < a <= a‘。

  在上面的定义基础上,“偏置”的含义就是,我们有┌x/y┐ = └(x+y-1)/y┘。这一过程的证明不难理解,我们假设x = ky + r(我们考虑r > 0 ,此时会有舍入发生),则有。

└(x+y-1)/y┘ = └(ky+r+y-1)/y┘ = k + └(r+y-1)/y┘ = k + 1

  可以看出在做了这个处理之后,也就是将x加上y-1的偏移量,此时在舍入时,结果会在原来的基础上加1。这也正是“偏置”的含义所在,它会将舍入“偏置”到向上舍入。

  下面我们将补码除法当中的程序按照这种方式修复一下,看是不是这个结果,如下。

public class Main {

    public static void main(String[] args) {
        int a = -17;
        int b = 8;
        int c = a/b;
        System.out.println("a:" + Integer.toBinaryString(a));
        System.out.println("b:" + Integer.toBinaryString(b));
        System.out.println("c:" + Integer.toBinaryString(c));
        System.out.println("(a+b-1) >> 3:" + Integer.toBinaryString((a+b-1) >> 3));
        System.out.println("c:" + c);
        System.out.println("(a+b-1) >> 3:" + ((a+b-1) >> 3));
    }
}

  此处我们将a“偏置”,也就是加上b-1的偏移量,我们来看结果。

  可以看出,在偏置之后,在负结果舍入时,移位运算的结果将会是我们期望得到的,这样我们便可以使用这一技巧进行优化了。

文章小结

  到这里,二进制整数的运算就介绍完了,本章难度较高,因此各位猿友可能要费点力气了。下一章我们将进入浮点数的世界,一起期待吧。

时间: 2024-10-14 05:40:38

深入理解计算机系统(2.6)---二进制整数的乘、除法运算(重要)【困难度高】的相关文章

二进制整数的加,减运算

前言 在平时的编程中,当进行整数运算时,经常会遇到一些奇怪的结果,比如两个正数相加出现了负数,两个负数相加出现了正数,这些都是因为数值表示的有限性导致的.来看一个案例: int a = 0x7FFFFFFF; int b = 0x7FFFFFFF; Console.WriteLine(a); Console.WriteLine(b); Console.WriteLine(a+b); 我这是C#的代码,我告诉你输出结果吧:-2 程序中的a和b都是很大的正整数,结果它们相加会得到一个负数. 再看看用

《深入理解计算机系统》2.3整数的运算

无符号加法 对于w位数字,它的取值范围是(0,2w?1).如果两个这样的数字相加,其结果的取值范围将为(0,2w+1).那么对于固定字长的数据类型,将可能丢弃最高位 无符号加法等价于计算和再模上2w 例如x=9:[1001]y=12:[1100]其和为21:[10101]由于字长固定,故丢弃最高位:[0101]即5以上等价于(9+12)% 24 =5 显然如果s=x+y当((s<x)||(s<y))时,可以说计算溢出.此时表达式为 s=x+y-2w 加法逆元 补码加法 补码加法实际是把补码变无

深入理解计算机系统(2.7)---二进制浮点数,IEEE标准(重要)

本文转载地址:http://www.cnblogs.com/zuoxiaolong/p/computer11.html 2.6我们进行了二进制整数运算的最后一役,本次LZ将和各位一起进入浮点数的世界,这里没有无符号,没有补码,但是有各种各样的惊奇.倘若你真正的进入了浮点数的世界,一定会发现它原来是这么有意思,而不是像之前一样,觉得了解浮点数的内容没什么用,只要会简单的使用就行了.当然,这其中也可能有部分猿友是觉得这部分内容太难,而对它失去了学习的兴趣. 就像之前的LZ一样,曾经对IEEE标准望而

深入理解计算机系统(3)

深入理解计算机系统(3) 本文我们主要讲关于数据的的表示方式:原码,反码和补码. 本文在写作过程中,参考了园中的这篇文章<原码,反码,补码详解>,特此声明. 一原码 计算机中是使用二进制来表示数据的,对于C语言这样的强类型语言,每一个数值类型,都有其范围,例如一个int类型,在32位机器上,其表示的范围如下: 最小值 最大值 有符号整数 -2147483648 217483647 无符号整数 0 4294967295 而如果我们定义了一个int类型,给其的赋值,超出了这个范围,则会出现问题.一

深入理解计算机系统(1.1)------Hello World 是如何运行的

上一篇序章我谈了谈 程序员为啥要懂底层计算机结构 ,有人赞同也有人反对,但是这并不影响 LZ 对深入理解计算机系统研究的热情.这篇博客以案例驱动的模式,通过跟踪一个简单 Hello World 程序的生命周期开始系统的学习,包括它被程序员创建,到在系统上运行,输出简单的消息,然后终止.LZ 将沿着这个程序的声明周期,先简要的介绍一些逐步出现的关键概念.专业术语以及组成部分.后面将会详细展开. 1.计算机系统 我们知道计算机系统是由硬件和软件组成的.它们共同工作来运行应用程序.虽然系统的实现方式随

深入理解计算机系统之旅(一)计算机系统漫游

1.计算机系统是什么? 我们直观的可以看到的计算机系统就是一台电脑,包括主机.显示器.键盘鼠标,然后显示器上会显示不同界面,用鼠标点击,键盘输入界面就会相应操作. 2.深入一点,他们是如何工作的? 计算机系统是由硬件和系统软件组成,它们共同工作来运行应用程序. 3.再深入一点,硬件是什么?系统软件是什么?应用程序是什么? 硬件是组成计算机系统的基础,大家耳熟能详的就是CPU.内存.硬盘.主板等. 系统软件是运行在硬件之上的一层控制软件,就是我们经常听到的Windows XP.WIN7.Linux

深入理解计算机系统(2.5)------C语言中的有符号数和无符号数以及扩展和截断数字

上一篇博客我们讲解了计算机中整数的表示,包括无符号编码和补码编码,以及它们之间的互相转换,个人觉得那是非常重要的知识要点.这篇博客我们将介绍C语言中的有符号数和无符号数以及扩展和截断数字. 1.C语言中的有符号数和无符号数 上一篇博客我们给出了C语言中在32位机器和64位机器中支持的整型类型数据,我们这里只给出32位机器上的: 尽管 C 语言标准没有指定有符号数要采用某种编码表示,但是几乎所有的机器都使用补码.通常大多数数字是默认有符号的,比如当声明一个像12345或者0xABC这样的常量的时候

深入理解计算机系统学习(位扩展)

最近在复习计算机基础知识,断断续续的记录在这里吧. 深入理解计算机系统是本好书,对底层的实现原理从程序员的角度进行的细致的讲解,不愧为经典,深得大家的喜爱. 其中CPU对于有符号,无符号转化以及位的扩展引起的溢出问题需要理解,并在写c的时候避免,有时候编译不报错, 但是却存在隐患. 1.无符号扩展直接高位补0 例如,无符号扩展 unsigned short e = 100; int f = e; printf("e= %.2xH\n", e); show_bytes((byte_poi

CSAPP深入理解计算机系统第二章荟萃

1.可以利用数字的算数右移,然后利用0XFF这样的数字做掩码运算,可以获取到一个数字的符号位.计算机的移位运算有一种很重要的作用就是利用掩码运算去提取一个位模式的一段信息. 2.在C语言中的条件语句,以及三目的条件运算符,都可以用移位的方式来做. 3.在进行位扩展操作的时候,比较讲一个32位的有符号数扩展到64位,那么在保证原来的值不变的情况下,把31个低位扩展到64位的低位上,而最高的符号位扩展到高33位上. 4.利用位模式仅仅只能表示一些2的n次幂的一些浮点数,其他的比较特殊的有理数,会近似