029位运算实现加减乘除
位运算实现加减乘除
加法
a + b = a ^ b + 进位信息
可能相加后还有进位,就一直加到没有进位再停止

public int add(int a, int b){
int ans = a;
while (b != 0){//b既是第二个加数,也是进位信息
ans = a ^ b;//不能直接写a = a ^ b,因为下一步还要用到a
//a和b做与运算后左移一位,可以得到进位信息
b = (a & b) << 1;
//a更新为ans,如果还有进位信息,就继续与进位信息相加
a = ans;
}
return ans;
}
减法
a - b = a + (-b)
-b = ~b + 1
public int minus(int a, int b){
return add(a, neg(b));
}
public int neg(int n){
return add(~n, 1);
}
乘法
//a乘以被乘数的某一位,然后a左移,继续乘下一位
public int multiply(int a, int b){
int ans = 0;
while (b != 0){//被乘数还没有乘完
//被乘数当前位不为0
if((b & 1) != 0){
ans = add(ans, a);
}
// 乘数左移
a <<= 1;
// 被乘数右移,被乘数要相乘的那一位与a对齐,这样就可以判断b的最后一位是否为0
b >>>= 1;
}
return ans;
}
除法
非负数情况下:
\(280 = 25*2^3 + 25*2^1+25*2^0 ... 5\)
对于int,31位是符号位,从\(2^{30}\)开始判断这个数是否包含\(2^{30}\),如果有的话,继续用剩下的余数,判断29、28、一直往下判断
例如对于280,前面的数都太大了,一直到\(25*2^8\),280大于25*2^8$,然后280-\(25*2^8\)=80.
继续用80来判断,80包含了\(25*2^1\),然后余数30
30包含了\(25*2^0\),余数是5,5小于25,结束
得到280/25 = 1011
对于被除数y,\(y*2^{30}\),也就是y左移30位,如果y这个数稍微大一点,会导致y移除,超出int的范围
public int div(int a, int b){
//如果a和b小于0,就转成非负数
//最小负整数不能转,它没有对应的相反数,例如8bit,范围是-128~127
int x = a < 0 ? neg(a) : a;
int y = b < 0 ? neg(b) : b;
int ans = 0;
for (int i = 30; i>= 0; i = minus(i, 1)){
//x/y,要判断x是否大于等于y*(2^i),从比较大的数开始判断,x中是否包含这个比较大的数
//但是y进行左移容易造成溢出
//等同于x右移i位,如果大于等于y,就说明x大于等于y*(2^i)
if((x >> i) >= y){
ans |= (1 << i);//答案的i位置为1
x = minus(x, y << i);//余数
}
}
//只有ab异号时,答案才是负数
//-280/25
//280/-25
//-280/-25
return a < 0 ^ b < 0 ? neg(ans) : ans;
}
讨论了整数最小值的除法
public int devide(int a, int b){
if(a == MIN && b == MIN){
return 1;
}
if (a != MIN && b != MIN){
return div(a, b);
}
//a不是最小,b是最小
if (b == MIN){
return 0;
}
//a是最小,b是-1,返回整数最大(题目要求)
if (b == neg(1)){
return Integer.MAX_VALUE;
}
//a是整数最小,b不是最小也不是-1
//如果b是正数,那么a+b使得a没有那么小,a/b = (a+b)/b-1
//如果b是负数,那么a-b使得a没有那么小,a/b = (a-b)/b+1
a = add(a, b > 0 ? b :neg(b));
int ans = div(a, b);
int offset = b > 0 ? neg(1) : 1;
return add(ans, offset);
}

浙公网安备 33010602011771号