1. 加法运算
13 + 9 = 21
// 13--0000 1101
// 9--0000 1001
十进制思想:
- 不考虑进位,分别对各位数进行相加,结果为sum:
- 个位数3加上9为2;十位数1加上0为1; 最终结果为12;
- 只考虑进位,结果为carry:
- 3 + 9 有进位,进位的值为10;
- 如果步骤2所得进位结果carry不为0,对步骤1所得sum,步骤2所得carry重复步骤1、 2、3;如果carry为0则结束,最终结果为步骤1所得sum.
步骤:(1) 不考虑进位,分别对各位数进行相加:sum = 22; (2) 只考虑进位: 上一步没有进位,所以carry = 0; (3) 步骤2carry = 0,结束,结果为sum = 22.
不考虑进位,分别对各位数进行相加: <二进制同理考虑>
13 + 9 = 0000 1101 + 000 01001 = 0000 0100
考虑进位:
有两处进位,第0位和第3位,只考虑进位的结果为:
carry = 0001 0010
carry == 0, is true or false,不为0,重复步骤1 、2 、3;为0则结束,结果为sum:
本例中,
(a)不考虑进位sum = 0001 0110;
(b)只考虑进位carry = 0;
(c)carry == 0,结束,结果为sum = 0001 0110 = 22;
结论:十进制的三板斧同样适用于二进制,第一步不考虑进位的加法其实就是异或运算;而第二步只考虑进位就是与运算并左移一位;第三步就是重复前面两步操作直到第二步进位结果为0。
// 递归写法
int add(int num1, int num2)
{
if(num2 == 0) return num1;
int sum = num1 ^ num2;
int carry = (num1 & num2) << 1;
return add(sum, carry);
}
// 迭代写法
int add(int num1, int num2)
{
int sum = num1 ^ num2;
int carry = (num1 & num2) << 1;
while(carry != 0)
{
int a = sum;
int b = carry;
sum = a ^ b;
carry = (a & b) << 1;
}
return sum;
}
2.减法运算
思想:减法运算转变成加法运算.
减法运算可转变成一个正数加上一个负数,那首先就要来看看负数在计算机中是怎么表示的。
+8在计算机中表示为二进制的1000,那-8怎么表示呢?
很容易想到,可以将一个二进制位(bit)专门规定为符号位,它等于0时就表示正数,等于1时就表示负数。比如,在8位机中,规定每个字节的最高位为符号位。那么,+8就是00001000,而-8则是10001000。这只是直观的表示方法,其实计算机是通过2的补码来表示负数的,那什么是2的补码(同补码,英文是2’s complement,其实应该翻译为2的补码)呢?它是一种用二进制表示有号数的方法,也是一种将数字的正负号变号的方式,求取步骤:
-
第一步,每一个二进制位都取相反值,0变成1,1变成0(即反码)。
-
第二步,将上一步得到的值(反码)加1。
8 ---- 00000000 00000000 00000000 00001000 原码,补码,反码
-8 ---- 10000000 00000000 00000000 00001000 原码
11111111 11111111 11111111 11110111 反码
11111111 11111111 11111111 11111000 补码
利用的补码可以将数字的正负号变号的功能,这样我们就可以把减法运算转变成加法运算了,因为负数可以通过其对应正数求补码得到。计算机也是通过增加一个补码器配合加法器来做减法运算的,而不是再重新设计一个减法器。
int sub(int num1, int num2)
{
int subtractor = add(~num2, 1);// 先求减数的补码(取反加一)
int result = add(num1, subtractor);
return result ;
}
3.乘法运算
加法运算的位运算实现,是将乘法运算转换成加法运算,被乘数加上乘数倍的自己。这里还有一个问题,就是乘数和被乘数的正负号问题,我们这样处理,先处理乘数和被乘数的绝对值的乘积,然后根据它们的符号确定最终结果的符号即可。步骤如下:
- 计算绝对值得乘积
- 确定乘积符号(同号为证,异号为负)
int multiply(int num1, int num2)
{
// 取绝对值
int multiplicand = num1 < 0 ? add(~num1, 1) : num1;
int multiplier = num2 < 0 ? add(~num2 , 1) : num2;// 如果为负则取反加一得其补码,即正数
// 计算绝对值的乘积
int product = 0;
int count = 0;
while(count < multiplier) {
product = add(product, multiplicand);
count = add(count, 1);// 这里可别用count++,都说了这里是位运算实现加法
}
// 确定乘积的符号
if((num1 ^ num2) < 0) {// 只考虑最高位,如果a,b异号,则异或后最高位为1;如果同号,则异或后最高位为0;
product = add(~product, 1);
}
return product;
}
优化:
int multiply(int num1, int num2) num2
{
//将乘数和被乘数都取绝对值
int multiplicand = num1 < 0 ? add(~num1, 1) : num1;
int multiplier = num2 < 0 ? add(~num2 , 1) : num2;
//计算绝对值的乘积
int product = 0;
while(multiplier > 0) {
if((multiplier & 0x1) > 0) {// 每次考察乘数的最后一位
product = add(product, multiplicand);
}
multiplicand = multiplicand << 1;// 每运算一次,被乘数要左移一位
multiplier = multiplier >> 1;// 每运算一次,乘数要右移一位(可对照上图理解)
}
//计算乘积的符号
if((num1 ^ num2) < 0) {
product = add(~product, 1);
}
return product;
}
4.除法运算
除法运算可以转换成减法运算,即不停的用除数去减被除数,直到被除数小于除数时,此时所减的次数就是我们需要的商,而此时的被除数就是余数。这里需要注意的是符号的确定,商的符号和乘法运算中乘积的符号确定一样,即取决于除数和被除数,同号为证,异号为负;余数的符号和被除数一样。
int divide(int num1, int num2)
{
// 先取被除数和除数的绝对值
int dividend = num1 > 0 ? num1 : add(~num1, 1);
int divisor = num2 > 0 ? num2 : add(~num2, 1);
int quotient = 0;// 商
int remainder = 0;// 余数
// 不断用除数去减被除数,直到被除数小于被除数(即除不尽了)
while(dividend >= divisor)// 直到商小于被除数
{
quotient = add(quotient, 1);
dividend = substract(dividend, divisor);
}
// 确定商的符号
if((num1 ^ num2) < 0)// 如果除数和被除数异号,则商为负数
{
quotient = add(~quotient, 1);
}
// 确定余数符号
remainder = b > 0 ? dividend : add(~dividend, 1);
return quotient;// 返回商
}
优化;
int divide_v2(int num1,int num2) {
// 先取被除数和除数的绝对值
int dividend = num1 > 0 ? num1 : add(~num1, 1);
int divisor = num2 > 0 ? num2 : add(~num2, 1);
int quotient = 0;// 商
int remainder = 0;// 余数
for(int i = 31; i >= 0; i--)
{
//比较dividend是否大于divisor的(1<<i)次方,不要将dividend与(divisor<<i)比较,而是用(dividend>>i)与divisor比较,
//效果一样,但是可以避免因(divisor<<i)操作可能导致的溢出,如果溢出则会可能dividend本身小于divisor,但是溢出导致dividend大于divisor
if((dividend >> i) >= divisor)
{
quotient = add(quotient, 1 << i);
dividend = substract(dividend, divisor << i);
}
}
// 确定商的符号
if((num1 ^ num2) < 0)
{
// 如果除数和被除数异号,则商为负数
quotient = add(~quotient, 1);
}
// 确定余数符号
remainder = b > 0 ? dividend : add(~dividend, 1);
return quotient;// 返回商
}
写在最后: 我们的计算机其实就是通过上述的位运算实现加法运算的(通过加法器,加法器就是使用上述的方法实现加法的),而程序语言中的+ - * /运算符只不过是呈现给程序员的操作工具,计算机底层实际操作的永远是形如0101的位,所以说掌握位运算很有必要。