二进制整数的乘除运算

前言

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

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

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

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

无符号乘法

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

补码乘法

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

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

证明过程略过了,就是利用无符号编码和补码编码的关系.

乘法运算的优化

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

上述有花的前提是对于一个w位的二进制数来说,它与2

的k次方的乘积,等同于这个二进制数左移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,更不幸的是,它有时候会比乘法运算更慢,但是我们只探讨只能针对除数可表示为2的k次方的除法运算进行优化,转换为算术右移或者逻辑右移k位的运算(无符号数为逻辑右移,为正数时,逻辑右移与算术右移效果一样).

因为是除法,因此我们会涉及到舍入的问题.

先看案例:

int a = 17;

int b = 8;

int c = a / b;

Console.WriteLine("a:" + Convert.ToString(a,2));

Console.WriteLine("b:" + Convert.ToString(b, 2));

Console.WriteLine("c:" + Convert.ToString(c, 2));

Console.WriteLine("a >> 3:" + Convert.ToString(a>>3, 2));

还是C#代码,这段程序的结果可以看出a/b的结果就是a右移3位的结果,就是结果等于a>>3

无符号数除以2的k次方等价于右移k位.证明过程不说了,记住结论.

不知道你发现了没有,乘法是左移,乘法是右移.

补码除法

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

案例:

int a = -17;

int b = 8;

int c = a / b;

Console.WriteLine("a:" + Convert.ToString(a,2));

Console.WriteLine("b:" + Convert.ToString(b, 2));

Console.WriteLine("c:" + Convert.ToString(c, 2));

Console.WriteLine("a >> 3:" + Convert.ToString(a>>3, 2));

Console.WriteLine("c:"+c);

Console.WriteLine("a >> 3:" + Convert.ToString(a>>3));

结果有点出人意料:

a:11111111111111111111111111101111

b:1000

c:11111111111111111111111111111110

a >> 3:11111111111111111111111111111101

c:-2

a >> 3:-3

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

其实这个问题的原因很简单,补码编码与无符号的编码类似,对于位表示相同.不过此时由于是负数,所以采用了向下舍入.

此时可以记住一个结论:当有舍入发生的时候,降一个负数右移k位不等价与把它除以2的k次方.

除法的补数

既然在舍入时,一个负数右移k位不等价于把它们除以2的k次方.那么为了使用这种优化,计算机界的大神们有出马了,于是他们又想出了一个办法,即”偏置”这个值.

在上面的基础上,”偏置”的含义是啥?

看个案例:

int a = -17;

int b = 8;

int c = a / b;

Console.WriteLine("a:" + Convert.ToString(a,2));

Console.WriteLine("b:" + Convert.ToString(b, 2));

Console.WriteLine("c:" + Convert.ToString(c, 2));

Console.WriteLine("a >> 3:" + Convert.ToString((a+b-1)>>3, 2));

Console.WriteLine("c:"+c);

Console.WriteLine("a >> 3:" + Convert.ToString((a + b - 1) >> 3));

此处我们将a”偏置”,也就是加上b-1的偏移量,结果如下:

a:11111111111111111111111111101111

b:1000

c:11111111111111111111111111111110

a >> 3:11111111111111111111111111111110

c:-2

a >> 3:-2

能够看出,在偏置之后,在负结果舍入时,移位运算的结果将会是我们期望得到的,这样我们便可以使用这一技巧进行优化了,什么歌意思呢?我们在做了将a+b-1这个处理之后,结果会在原来的基础上加1.这就是偏置的含义所在,它会将舍入偏置到向上舍入.

小结

二进制整数的运算较重要吗?不是,是很重要,也较难,多花点力气没错.

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-11-07 01:31:00

二进制整数的乘除运算的相关文章

二进制整数的加,减运算

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

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

本文转载地址:http://www.cnblogs.com/zuoxiaolong/p/computer10.html 2.5我们着重介绍了二进制整数的加.减运算,本次我们继续介绍乘.除运算. 引言 运算一直是程序运行当中一个重要的环节,而在二进制的运算过程当中,加法运算又是重中之重,它基本上奠定了二进制运算的基础.因为无论是减法还是乘法,都可以由加法运算来替代,唯有除法不能由加法替代. 了解计算机运算的规律,可以有助于我们理解很多程序代码上无法理解的内容.比如上章提到的溢出问题,在了解了加法运

C语言的有符号与无符号,二进制整数的扩展与截断

前一节说了整数的表示方式,,也就是无符号编码与补码编码.这一届说一下二进制整数的扩展与截断,这部分内容与C语言挂钩.so,我们先看以下C语言的有符号和无符号数. C语言中的有符号数和无符号数 有符号数和无符号数的本质区别其实就是采用的编码不同,前者采用补码编码,后者采用无符号编码. 在C语言中,有符号数和无符号数是可以隐式转换的,不需要手动实施强制类型转换.不过也正是因为如此,可能你一不小心就将一个无符号数赋给了有符号数.就会造成出乎意料的结果,就像下面这样: #include <stdio.h

Python 整数和浮点数运算

和数学运算不同的地方是,Python的整数运算结果仍然是整数,浮点数运算结果仍然是浮点数:1 + 2 # ==> 整数 31.0 + 2.0 # ==> 浮点数 3.0 整数和浮点数混合运算的结果就变成浮点数了:1 + 2.0 # ==> 浮点数 3.0 为什么要区分整数运算和浮点数运算呢?这是因为整数运算的结果永远是精确的,而浮点数运算的结果不一定精确,因为计算机内存再大,也无法精确表示出无限循环小数,比如 0.1 换成二进制表示就是无限循环小数.

C/C++ 移位计算代替乘除运算

测试移位和乘除的比较,发现移位比乘除运算快一个位数的速度,但是难点在于判断是否是2的幂次级的数,如果不是还得通过代码拆分到2的幂次+上分子的累和,然后通过移位得到2的次幂数这样; 下列代码只是简单的判断是幂级数则移位运算,不是则正常计算,不是完全都是移位算,(之后如果找到方法判断更快在更新) C/C++: 1 #include <iostream> 2 3 4 5 uint64_t Multiply_Dived(uint64_t member, uint64_t denominator, ch

java与C在整数累加的运算对比

java与C在整数累加的运算对比 先来没事,今天晚上突发奇想,在同样平台ubuntu14.04,同等运算负荷的情况下测试了一下C与java的性能.可是,最后的实验结果让我大吃一惊,在我的心里也慢慢再一次对詹姆斯.高斯林(java语言的作者)的敬意.实验本身很简单,各位看官也可以去做同样的实验.有了心得之后,不妨一起本着学习的心态,共同交流,共同成长.下面贴出测试代码: C语言: <span style="font-size:18px;"><strong> int

位运算与乘除运算的对比实验

Java 的JDK中存在很多位运算的使用,其目的是为了让代码跑的更快一些,我们做一个实验,看看到底可以节省多少计算时间. 以下代码中,分别使用了乘除运算和位运算来进行了一亿次基础操作,并打印了所消耗的时间. /** * 比较乘除运算和位运算时间消耗上的差距 */ public class TestBitOpr { public static void main(String[] args){ int j = 10; long start = System.currentTimeMillis();

【Leetcode】2的幂(整数的二进制形式,与运算)

class Solution { public: bool isPowerOfTwo(int n) { if(n <= 0) return false; return (n&(n-1)) == 0; } }; 注: 1) 2的幂函数,其y值大于0: 2) 2的幂函数,若 x < 0, y = (0,1); 2)n&(n-1) == 0 和 (n&(n-1)) == 0 ,逻辑是不一样的. 原文地址:https://www.cnblogs.com/gdut-gordon/

二进制整数中的“1”

这里主要通过一些位运算的技巧来找出一个整数的二进制表示中1的个数或其中某些特殊的1的位置. 1 的个数 求二进制表示中1的个数存在时间复杂度为O(n)(n为1的个数)的算法. 其要点是 若整数 x 不为0, 表达式(指C语言表达式) x&(x-1) 的运算结果是将 x 的二进制表示中最右侧的1消除后的值, 若x为0, 表达式的值为0. 这样, 若 x 不为0, 我们就可以通过表达式  x = x&(x-1) 反复消除 x 最右侧的1直到x为0, 消除的次数即为所求1的个数. C语言代码(输