计算机底层是如何计算加法的?

2,250 阅读4分钟

众所周知在计算机中,所有的数据都是通过电信号表示的,自然加法计算也是通过电信号来进行表达过程和结果的,高电平表示1低电平表示0,也就是二进制数,如果我们要计算11+21=?,换算为二进制数即1011+10101=?

为什么使用二进制

已知电信号是通过二极管控制电流输出表示,计算机通过控制二极管的输出电流大小对应不同的状态值,早期的计算器出现了对应三种状态、五种状态的电路,也就是三进制计算机,五进制计算机,但是状态越多,就越难区分信号,当外界有干扰的时候,电信号可能会混乱在一起,晶体管本身就是高速运作的元件,会让混乱的情况更严重,为了尽可能减少这类问题的发生,只用高电平和低电平表示两种状态,也就是打开和关闭,这样大大的减少了出错的可能。

并且刚好在数学领域有一门分支,叫做布尔代数,用数学公式演算了很多逻辑运算,基本运算符有NOR、AND、OR,输出的值均为true和false,刚好匹配上晶体管电路控制中的开和关,下面是各个基础逻辑门的真值表:

半加器和全加器

有了逻辑门之后,就可以开始计算二进制位之间的加法,当10111+10101从低位第一次相加的时候,需要计算两个二进制数11加法,根据满二进一的原则,加法结果sum=0,需要向前进位carry=1,对应电路为,两个输入位A和B,两个输出位SUM和CARRY。

通过观察可以发现,当两个输入位A和B都为1或者0时sum=0,其他时候sum=1,正好对应XOR门的输出,所以XOR的输出即为SUM,并且当且仅当两个输入位A和B都为1时,才需要进位carry=1,对应AND门的输出,所以AND门的输出即为carry,整个电路即半加器:

对应js代码:

// 半加器,&代表and门,^代表XOR门
const halfAdder = (a: number, b: number) => [a & b, a ^ b];

计算完最低位往左移,计算10111+10101中的1+0,此时需要计算两个加数1和0,加上低位上的进位1,才能输出一个结果sum和一个进位carry,基于半加器的实现,需要先计算出a+b的sum1和carry1,然后与低位的进位进行一次半加器运算得到sum2和carry2,sum2即最终的sum输出,最后再用OR门判断第二次半加器运算的carry2和第一次运算的carry1是否有进位,只要有进位就输出carry=1,整个电路即全加器:

对应js代码:

// 全加器,|代表OR门
const fullAdder = (a: number, b: number, carry: number) => (
  ([a, b] = halfAdder(a, b)), ([carry, b] = halfAdder(b, carry)), [a | carry, b]
);

有了半加器和全加器,只需要把各个位上的输入链接到对应的半加器或者全加器上,然后接通电源就能得到最终的输出,电路即:

代码实现

有了基础实现,只要需要根据逻辑编写代码即可:

// 半加器
const halfAdder = (a: number, b: number) => [a & b, a ^ b];

// 全加器
const fullAdder = (a: number, b: number, carry: number) => (
  ([a, b] = halfAdder(a, b)), ([carry, b] = halfAdder(b, carry)), [a | carry, b]
);

// 位加法计算器
const bitsAdder = (addend1: number, addend2: number) => {
  let addend1Bits: string = addend1.toString(2);
  let addend2Bits: string = addend2.toString(2);
  let maxLength = 0;
  // 补0
  addend1Bits.length >= addend2Bits.length
    ? ((maxLength = addend1Bits.length - 1), (addend2Bits = addend2Bits.padStart(addend1Bits.length, '0')))
    : ((maxLength = addend2Bits.length - 1), (addend1Bits = addend1Bits.padStart(addend2Bits.length, '0')));
  // 避免重复创建变量
  let aBit = 0;
  let bBit = 0;
  let tempCarry = 0;
  let tempSum = 0;
  let result: number[] = [];
  let index = maxLength;
  while (index > -1) {
    aBit = +addend1Bits[index];
    bBit = +addend2Bits[index];
    [tempCarry, tempSum] = index === maxLength ? halfAdder(aBit, bBit) : fullAdder(aBit, bBit, tempCarry);
    result.unshift(tempSum);
    index--;
  }
  // 记得插入最后一个进位
  result.unshift(tempCarry);
  console.log(parseInt(result.join(''), 2));
};