深入理解计算机底层:如何在 Java 中不使用 + - * / 实现加法?
前言 最近在刷《剑指 Offer》时,遇到了一道非常有意思的题目:“不用加减乘除做加法”。 初看觉得这是脑筋急转弯,但深入思考后发现,这其实是在考察我们对 计算机组成原理 和 位运算 的理解。 今天就来复盘一下位运算中的“黑魔法”,以及它是如何帮我们解决棘手问题的。
1. 问题的引入
平时我们写代码,算 a + b 简直是天经地义。但如果面试官在这个最基础的操作上加了限制:
“请写一个函数,求两个整数之和,要求在函数体内不得使用 +、-、*、/ 四则运算符号。”
这时候,我们就必须跳出高级语言的思维,像 CPU 一样去思考问题。
2. 重新认识“加法”
回想一下小学数学,我们在算 5 + 7 时,其实分了两步:
- 本位和(不进位): 5 + 7 = 12,个位写 2。
- 进位: 满 10 进 1,进位是 10。
- 结果: 2 + 10 = 12。
在二进制世界里,逻辑是一模一样的!
核心公式推导
假设我们要算 a + b。
第一步:算“不进位加法”
观察二进制异或(^)的性质:
0 ^ 0 = 01 ^ 0 = 10 ^ 1 = 11 ^ 1 = 0(注意:这里本该进位,但异或只保留了本位 0)
发现了吗?异或运算 (^) 等价于“无进位加法”。
第二步:算“进位”
什么时候会产生进位?只有 1 + 1 的时候。
观察二进制与(&)的性质:
1 & 1 = 1(只有这种情况为 1)- 其他情况都是 0
所以,a & b 可以找出所有“需要进位”的位置。
但是,进位是要加到下一位去的(比如个位的进位要给十位)。
所以,(a & b) << 1 等价于“进位值”。
结论
也就是:和 = 无进位和 + 进位。
3. 代码实现 (Java)
既然不能用 +,那如果计算过程中还有“进位”,我们就循环重复上述过程,直到进位为 0 为止。
public class Solution {
public int encryptionCalculate(int dataA, int dataB) {
// dataA: 这里的角色是“本位和”
// dataB: 这里的角色是“进位”
// 只要还有进位,就继续循环
while (dataB != 0) {
// 1. 计算进位 (只记录撞车的位置,并左移一位)
int carry = (dataA & dataB) << 1;
// 2. 计算本位 (只记录不撞车的部分)
dataA = dataA ^ dataB;
// 3. 下一轮任务:把进位加到本位上
dataB = carry;
}
return dataA;
}
}
4. 延伸:位运算的“消消乐”
除了加法器,位运算里还有一个非常经典的“魔术”:n & (n - 1)。
它的作用是:消除二进制表示中最低位的那个 1。
原理分析
假设 n = 12 (二进制 1100)。
n - 1会借位,变成1011。1100 & 1011 = 1000。- 看!最右边的那个
1不见了。
实战应用:统计二进制中 1 的个数
(题目:LCR 133. 位 1 的个数)
如果我们用普通的移位检查,需要循环 32 次。但用这个魔术,有几个 1 就只循环几次,效率极高。
public int hammingWeight(int n) {
int count = 0;
while (n != 0) {
count++;
n = n & (n - 1); // 每次消掉一个 1
}
return count;
}
5. 总结
位运算虽然看起来晦涩,但它是计算机底层的基石。
^(异或) :不仅是“无进位加法”,还可以用来做“找不同”(两个相同的数异或为0)。n & (n - 1):是处理二进制位统计的神器。
掌握这些底层逻辑,不仅能应对面试,更能让我们写出更高效、更“Geek”的代码。