js趣玩:位运算符实现js二进制加法减法(js加减法)

4,125 浏览发布于 作者 Yang (欢迎转载-请注明出处链接)留下评论分享按钮

1、本文缘起

昨天看到群里有朋友在讨论说“实现 js 小学数学运算搞的头大”,误以为他是要实现底层算法(后面发现只是实现小学加减乘除表相关数学 web 功能,可能是遇到js数学运算的精度问题了吧),去网上搜了一圈发现并没有 javascript 的底层加减法文章。索性自己实现一个例子,记录如下。

2、预备知识

原码反码、和补码相互之间的关系:
1)计算机底层的运算都是 0、1 的运算,数字最终都会被转成二进制的0和1来进行数学运算。
2)3种二进制码制:

原码:如果机器字长为 n,那么一个数的原码就是一个 n 位的二进制有符号数,其中最高位为符号位:正数为 0,负数为 1。剩下的 n-1 位为数值位,表示真值的绝对值;凡不足 n-1 位的,在最高位左边加零;整数则在最高位左边加零来补足 n-1 位。
例如:8 位机器上:X = +101011,[X]原 = 0010 1011;X = -101011,[X]原 = 1010 1011

反码:正数的反码与其原码相同;
负数的反码是在原码的基础上,符号位不变,其他位按位取反(就是 0 变 1,1 变 0)就可以了。
例如:X = -101011,[X]原 = 1010 1011,[X]反 = 1101 0100

补码:正数的补码与其原码相同;
负数的补码就是在反码的基础上,按照正常的加法运算加 1。
例如:X = -101011,[X]原 = 1010 1011 ,[X]反 = 1101 0100,[X]补 = 1101 0101

需要特别说明的是:

1)原码不能直接参加运算,可能会出错。例如数学上,1+(-1)=0,而在二进制中:
00000001+10000001=10000010,换算成十进制为 -2。显然是错误的。

2)而补码可以直接加减运算,采用补码加减法运算,可将“正数加负数”的操作,转化为“正数加正数”的操作。最后把得到的补码运算结果取反码然后还原为原码就是正确结果,示例:
25+(-36) = -11
25的补码:00011001
-36的补码:11011100
00011001
11011100
相加得到:
11110101
11110101的原码为:10001011( 十进制就是 -11 )

3、js 实现加法:


function Add(nNum1, nNum2) {
    //如果其中一个相加参数数为 0,则返回另一参数;
    if(nNum1 == 0 || nNum2 == 0) return nNum1 || nNum2;
    var ntempNum;
    while (nNum2 != 0) {
        ntempNum = nNum1 ^ nNum2;
        nNum2 = (nNum1 & nNum2) << 1;
        nNum1 = ntempNum;
    }
    return nNum1;
}

console.log( Add(5,7) ); //12
console.log( Add(11,21) ); //32

上例 console.log( Add(5,7) ); 中 5+7=12 运算原理解释:
1)首先看十进制是如何运算的:(三步走)

第一步:相加各位的值,不算进位,得到 2;
第二步:计算进位值,5+7 进 10,得到 10(如果这一步的进位值为 0,那么第一步得到的值就是最终结果);
第三步:重复上述两步,将进位值与个位值相加,所以只是相加的值变成上述两步的得到的结果 2 和 10 ,得到 12。

2)再来看二进制值相加: (三步走)

首先,数字 5 和 7 转换为8位二进制:
5 =< 0000 0101
7 =< 0000 0111

第一步:相加各位的值,不算进位,
0000 0101
0000 0111
———-(相加,但不算进位)
0000 0010
因为,二进制每位相加就相当于各位做异或操作^(二进制数的异或操作是2个数对应位只有一个为 1,则在该位返回 1,其它均返回 0,所以异或的结果正是我们想要的各位相加不算进位的值;当然 js 还有其它进制形式的异或,相关知识另文详解)
所以,00000101^00000111 得到 00000010,转为十进制就是 2,测试如下:
console.log( 5^7 );
打印出 2

第二步:计算进位值,这里我们将用到 &按位与 和 左移位运算符来计算进位值:
& 运算符:2 个数对应位都是1,则在该位返回 1;
<< 运算符:将数往左移动一位;
首先 00000101 & 00000111 得到 00000101,所以2个数对应位都是1的会在该位返回1,理应向上进 1 位而没有进位却在该位返回 1,所以我们就将得到的结果再左移 1 位,即得到进位值,得到 1010,二进制的 (101&111)<<1 得到 1010
第三步重复上述两步,将各位值与进位值相加;先计算各位值(不算进位) 00000010 ^ 00001010 = 00001000,进位值为 (00000010 & 00001010)<<1 = 00000100
继续 while 循环重复上述两步:00001000 ^ 00000100 = 00001100,直到进位值为 0,跳出循环,返回 00001100 为最终结果 12。

4、js 实现减法

a-b 的减法运算可以利用 a+(-b) 来实现,其中 b 的负数就是 -b,也就是 b 的补码,即 b 需要转补码后与 a 相加;
又因为补码是由反码加1得到的,所以我们就先把 b 转反码,然后再加1(反码就是各位取反,也就是要用 ~按位取反运算符,再最后结果加上 1),原理我们知道,就可以开动了:


function Sub(nNum1, nNum2) {
    if(nNum1 == 0 || nNum2 == 0) return nNum1 || -nNum2 ;
    nNum2 = ~nNum2;	//取反码
    var ntempNum;
    while (nNum2 != 0) {
        ntempNum = nNum1 ^ nNum2;
        nNum2 = (nNum1 & nNum2) << 1;
        nNum1 = ntempNum;
    }
    return nNum1+1;
}

console.log( Sub(5,18) ); //-13
console.log( Sub(160,18) ); //142

上例关键点就是 取反码 处。
(题外话,其实 加法 Add() 函数第二个参数传入负值既是减法操作了,比如 console.log( Add(10,-20) ); 打印出 -10 )

至于其它运算,原理大致相似:
乘法:循环利用加法,实现b个a相加
整数除法:循环相减,直至减至非正数。

更多位运算符用于提升 js 性能的讲解将在另文介绍,比如 使用 & 运算符使得取模效率提高50%。移位运算符在计算中高效运用使效率翻倍等等。

想要打赏,请点击这里

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注