二进制整数的乘除运算
前言
运算一直视程序执行其中一个重要的环节,而在二进制的运算过程其中,加法运算有时重中之重,他基本上奠定了二进制运算的基础.因此不管是减法还是乘法,都能够由加法运算来取代,只有除法不能取代.
了解了计算机运算的规律,能够有助于我们理解非常多程序代码上无法理解的内容能够.比方上一张提出的溢出问题,在了解了加法运算的原理之后,相信大家都能够轻松您的知道为何有些运算会得到意想不到的结果.
这里还须要提到一点,不同的处理器所採取的运算方式可能有细微的区别,因此也不能一概而论.因此我们大多数时候会尽量讨论运算的而抽象数据特性,抽象的东西大部分时候总是可靠的,这样的特性为跨平台提供了基础,只是也并不是是总是如此,毕竟本屌仅仅听说过浮点数运算标准,没听过整数运算标准.可能有,可是本屌没听过.
正因如此,我们了解一下运算的抽象性,会有助于我们理解程序代码级无法理解的东西.
无符号乘法
无符号的乘法与加法类似,他的运算方式是比較简单的,仅仅是也可能产生溢出.对于两个w位的无符号来说,他们的乘积范围在0到(2的w次方-1)的平方之间,因此可能须要2w位的二进制才干表示.因此因为位数的限制,如果两个w位的无符号数的真实乘积为pro,依据截断的规则,则实际得到的乘积为pro mod 2的w次方
补码乘法
与加法运算类似,补码乘法也是家里在无符号的基础之上的,因此我们能够非常easy得到,对于两个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的结果是我们所期望的,能够移位运算的结果在舍入的出现了问题.为啥?
事实上这个问题的解决办法非常easy,补码编码与无符号的编码类似,对于位表示同样.只是此时因为是负数,所以採用了向下舍入.
此时能够记住一个结论:当有舍入发生的时候,降一个负数右移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.这就是偏置的含义所在,它会将舍入偏置到向上舍入.
小结
二进制整数的运算较重要吗?不是,是非常重要,也较难,多花点力气没错.