二进制存储
在计算机中,数据是用二进制0、1来存储的,第一位用来表示符号位,当第一位是1时,则为负数,当第一位是0是,为整数。
八位的二进制中,除了首位的符号位,还剩七位可以表示数字,所以八位二进制能表示的范围为
1111 1111 -> 0111 1111 即为 -127 -> 127 ,这255个数字
比如在八位的二进制数中
3 在二进制中表示为 0000 0011
-3 在二进制中表示为 1000 0011
二进制加法
在计算二进制加法的时候,也是按照十进制的方式,只不过十进制是逢十进一,二进制十逢二进一
0000 1001 + 0000 0001 = 0000 1010 -> 9 + 1 = 10
上面的计算过程是:
- 右边第一位:
1 + 1 = 2向左进一位,减2剩0,当前位置写0 - 右边第二位:
0 + 0 + 上面的进位1 = 1当前位置写1 - 右边第三位:
0 + 0 = 0当前位置写0 - 右边第四位:
0 + 1 = 1当前位置写1 - 再左边的每项相加的都是0,则结果为
0000 1010
0000 0011 + 0000 0011 = 0000 0110 -> 3 + 3 + 6
上面的计算过程是:
- 右边第一位:
1 + 1 = 2向左进一位,减2剩0,当前位置写0 - 右边第二位:
1 + 1 + 上面的进位1 = 3向左进一位,减2剩1,当前位置写1 - 右边第三位:
0 + 0 + 上面的进位1 = 1当前位置写1 - 再左边的每项相加的都是0,则结果为
0000 0110
原码
将一个整数转换成二进制形式,就是其原码。例如5的原码就是0000 0101更改 a 的值;-19的原码是1001 0011,通俗的理解,原码就是一个整数本来的二进制形式。
反码
正数与负数的反码不一样。
对于正数,它的反码就是其原码(原码和反码相同); 负数的反码是将原码中除符号位以外的所有位(数值位)取反,也就是 0 变成 1,1 变成 0。例如 5原码和反码都是 0000 0101, -5反码是 0111 1010。
补码
如果按照上面的方式计算减法就会出现问题,比如 -1 + 2
1000 0001 + 0000 0010 = 1000 0011 最终的结果是-3
对于0和-0的存储如果按照上面的方式就是0000 0000和1000 0000,这样存储的话,0这个数字在计算机中的编码就不是唯一的了。
为了解决上面的两个问题,就引入了补码,采用补码成功解决了数字 0 在计算机中非唯一编码的问题,也实现了减法变加法。
正数的补码等于源码等于反码,负数的补码等于负数的反码+1
对于有符号数,内存要区分符号位和数值位,要是能把符号位和数值位等同起来,让它们一起参与运算,不再加以区分,只用加法器就可以同时实现加法和减法运算,这样硬件电路就变得简单了。
8 - 3 等价于 8 + (-3)。
二进制减法计算
计算-7 + 2
- 将十进制转化为二进制
-7:1000 01113:0000 0011
- 将原码转化为补码
1000 0111反码1111 1000补码1111 10010000 0011反码0000 0011补码0000 0011
- 补码相加
1111 1001 + 0000 0011 = 1111 1100 - 可以看出补码相加结果为负数,要想得到我们想要的结果,需要将补码转化为原码(补码减一再取反)
1111 1100 - 0000 0001 = 1111 1011除了符号位取反结果为1000 0100十进制表示为-4
计算4 + (-2)
- 将十进制转化为二进制
-2:1000 00104:0000 0100
- 将原码转化为补码
1000 0010反码1111 1101补码1111 11100000 0100反码0000 0100补码0000 0100
- 补码相加
1111 1110 + 0000 0100 = 0000 0010(溢出忽略) - 可以看出补码相加结果为正数,正数的原码和补码相同,则十进制表示为
2
利用js实现一个加法器
/**
* 二进制相加
* @param {string} num1 二进制字符串
* @param {string} num2 二进制字符串
* @returns 二进制字符串之和
*/
function sum(num1, num2) {
if (num1.length < num2.length) {
return sum(num2, num1);
}
num2 = num2.padStart(num1.length, '0')
let i = num1.length - 1;
let strArr = [];
let carry = 0;
while (i >= 0) {
let n1 = +num1.at(i);
let n2 = +num2.at(i);
let curSum = n1 + n2 + carry;
if (curSum < 2) {
strArr.unshift(curSum);
carry = 0;
}
else if (curSum === 2) {
strArr.unshift(0)
carry = 1;
} else if (curSum === 3) {
strArr.unshift(1)
carry = 1;
}
i--;
}
return strArr.slice(0, num1.length).join('');
}
/**
* 按位取反
* @param {string} str 二进制字符串
* @returns {string} 取反后的二进制字符串
*/
function BitwiseInversion(str) {
return str.split('')
.map(item => item === '0' ? '1' : '0')
.join('');
}
/**
* 原码转补码|补码转源码(除了符号位按位取反 + 1)
* @param {string} source 原码字符串
* @returns {string} 补码字符串
*/
function conversion(source) {
return sum('1' + BitwiseInversion(source.slice(1)), '1')
}
/**
* 二进制转十进制
* @param {string} binaryStr 二进制字符串
* @returns {string} 十进制字符串
*/
function binaryToDecimal(binaryStr) {
let symbol = binaryStr.at(0);
decimal = parseInt('' + binaryStr.slice(1), 2)
if (symbol === '1') {
decimal = '-' + decimal
}
return decimal;
}
/**
* 计算-127到127以内的二进制加减法
* 也就是 1111,1111 到 0111,1111
* @param {string} num1 二进制字符串
* @param {string} num2 二进制字符串
* @returns {string} 两个二进制字符串之和的十进制字符串
*/
function adder(num1, num2) {
if (num1.startsWith(1)) {
num1 = conversion(num1);
}
if (num2.startsWith(1)) {
num2 = conversion(num2);
}
let complement = sum(num1, num2);
// 如果是负数,将补码转化为原码
if (complement.startsWith('1')) {
complement = conversion(complement.slice(1));
}
return binaryToDecimal(complement);
}
// 测试
console.log(adder('10000111', '00000010'));// -7 + 2
console.log(adder('10000111', '10000010'));// -7 + -2
console.log(adder('00000111', '10000010'));// 7 + -2
console.log(adder('00000111', '00000010'));// 7 + 2
console.log(adder('10000000', '00000010'));// -0 + 2
console.log(adder('01111110', '00000001'));// 126 + 1