彻底掌握JavaScript 位运算【&>> ^<< |】程序高效运算的好符号,别当成颜文字了呀!

822 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

前言

最近在看《剑指offer》的时候,发现位运算的执行效率和性能都很友好,使用位运算的代码看起来也更加整洁。之前也有用过一些位运算的操作,现在这篇文章,来整理总结一下知识点啦。

一、位运算知识介绍

【&>> ^<< |~】可能没了解位运算的同学会以为这是颜文字表情包。在计算机里位运算和四则运算一样的重要,其他编程语言也都有位运算,那么下面就和大家一起感受一下这些表情包符号的魅力哈哈哈。。

1、各个符号的意思

首先,先看看下表格,了解每个符号的作用。

符号描述运算规则示列结果
&两个位都为1时,结果才为14 & 6 = 100 & 110100 = 4
|两个位都为0时,结果才为04 | 6 = 100 | 110110 = 6
异或两个位相同为0,相异为14 ^ 6 = 100 ^ 110010 = 2
~取反0变1,1变0~1=0~0 =1
<<左移各二进位全部左移若干位,高位丢弃,低位补04 << 11000 = 8
>>右移各二进位全部右移若干位,对无符号数,高位补0,有符号数,各编译器处理方法不一样,有的补符号位(算术右移),有的补0(逻辑右移)4 >> 110 = 2

2、具体示例

通常来讲,与(&) ,或(|),异或(^)的使用可能会更多,下面表格再具体列出,二进制中它们的表现, 下列以表展示,

表-----------

与(&)0 & 0 =01 & 0 =00 & 1 =01 & 1 =1
或(|)0 | 0 =01 | 0 =10 | 1 = 11 | 1 = 1
异或(^)0 ^ 0 =01 ^ 0 =10 ^ 1 =11 ^ 1 =0

图---------------

image.png

3、用途介绍

1、与运算的用途:

1)清零

如果想将一个单元清零,即使其全部二进制位为0,只要与一个各位都为零的数值相与,结果为零。

2)取一个数的指定位

比如取数 X=1010 1110 的低4位,只需要另找一个数Y,令Y的低4位为1,其余位为0,即Y=0000 1111,然后将X与Y进行按位与运算(X&Y=0000 1110)即可得到X的指定位。

3)判断奇偶

只要根据最未位是0还是1来决定,为0就是偶数,为1就是奇数。因此可以用if ((a & 1) == 0)代替if (a % 2 == 0)来判断a是不是偶数。

2、或运算的用途:

1)常用来对一个数据的某些位设置为1

比如将数 X=1010 1110 的低4位设置为1,只需要另找一个数Y,令Y的低4位为1,其余位为0,即Y=0000 1111,然后将X与Y进行按位或运算(X|Y=1010 1111)即可得到。

3、异或运算的用途:

1)翻转指定位

比如将数 X=1010 1110 的低4位进行翻转,只需要另找一个数Y,令Y的低4位为1,其余位为0,即Y=0000 1111,然后将X与Y进行异或运算(X^Y=1010 0001)即可得到。

2)与0相异或值不变

例如:1010 1110 ^ 0000 0000 = 1010 1110

3)交换两个数

见目录第四部分、在程序中使用位运算

二、位运算运行性能好

因为性能好,所以一些框架里经常可以看到会使用位运算【&>> ^<< |】,来代替加减乘除求余数等运算【+-*/%】,下图纯属好看

pie title 为什么位运算性能好
"Vue" : 386
"React" : 85
"jQuery" : 15

因为位运算是汇编级的运算速度,操作的是二进制数值,因此框架源码为提高性能会采用位运算。不过在实际工作中,少量的计算的话速度优化不会很明显,如果考虑代码可读性等方面的话,就以实际需要使用就好了。

三、面试题和算法题中的位运算

下面我们以《剑指offer》和leetcode中几道常见的算法考察题来巩固上面学习的位运算知识

1、二进制中1的个数

image.png

题目解答

开始,判断二进制的最右边一位是否为 1。是 1 就计数加一。n 向右移动一位(除于2),下一轮判断时就是判断原始n的倒数第二位了, 这种解法是每次改变了n的大小, n为25时二进制表示为11001,运算如下:

11001->1100->110->11->1->0结束,1的个数为3

function NumberOfOne(n) {
  let count = 0;
  while (n) {
    if (n & 1) { // (1) 判断二进制的最右边一位是否为 1。
      count++;  // (2) 是 1 就计数加一
    }
    n = n >> 1; // (3)n 向右移动一位(除于2),下一轮判断时就是判断原始n的倒数第二位了
  }
  return count;
}

上面的程序中,如果遇到负数,一直右移运算的话可能会导致死循环,因为负数和整数的的边界值是这样的,正数【1、0X7FFFFFFF】, 负数【0X80000000、0XFFFFFFFF】

考虑负数后优化 使用flag变量来和n 相与,同样当n为25,11001时 步骤如下:

11001 & 1 =1, 11001 & 10 =0, 11001 & 100 =0, 11001 & 1000=1, 11001 & 10000 =1, 结束,得到1的个数为3

// 如果输入的n 是负数的话
function NumberOfOne_planB(n) {
  let count = 0, flag = 1
  while (flag) { 
    if (n & flag) count++;
    flag = flag << 1
  }
  return count;
}

2、判断一个整数是否为2的整数次方

写一个函数,用一条语句判断一个整数是否为2的整数次方,

例如 4 、 8、16、32、64返回true. 92,74返回false 解析

众所周知,2 的整次方转为二进制的话,它就是一个 1,后面几次方就更着几个零,如: 2的三次方8的二进制表示为1000,2的四次方16的二进制表示为10000,这样我们结合与运算&,判断它和它减一,进行相与是否为了零, 为什么是n-1和n相与呢? 因为众所周知,例如7 = 8-1 = 111,相与的时候往前面补零就是0111 15 =16-1 = 01111

function isTwoPower(n) {
  return ((n - 1) & n) ? false : true; // 有值就是非零,就是false,零的话就是true
}

// test 

console.log(isTwoPower(64));
console.log(isTwoPower(35));
console.log(isTwoPower(4));
console.log(isTwoPower(128));

image.png

3、判断m和n的二进制转换需几步

解析

  • (1) 获取m、n 的异或运算结果
  • (2) 返回m、n异或运算结果中 1 的个数就需要改变的位数。
function m_To_n_NeedStep(m, n) {
  let res = m ^ n // (1) 获取m、n 的异或运算结果
  let count = 0
  while (res) {
    if (res & 1) count++;
    res = res >> 1
  }
  return count  // (2) 返回m、n异或运算结果中 1 的个数就需要改变的位数。
}

console.log(m_To_n_NeedStep(10, 13)); // 3      1010 -> 1101 改动三位
console.log(m_To_n_NeedStep(4, 6)); // 1      100 -> 110 改动一位

4、不用四则运算实现两数相加函数

解析 发散性思维,不能使用+-*/,那只能使用位运算了

function Add(num1, num2) {
  let sum, carry

  while (num2 != 0) {
    sum = num1 ^ num2
    carry = (num1 & num2) << 1
    num1 = sum
    num2 = carry
  }

  return num1
}

// test

console.log(Add(10, 82));
console.log(Add(0, -2));
console.log(Add(-10, 2));

image.png

四、在程序中使用位运算

1、去除小数点

  • 使用~~
let b = 12.22
console.log(~~b); // 12
  • 使用>> 0
let c = 3.14
console.log(c >> 0); // 3

2、位运算代替乘除法

  • 除法
let d = 24
console.log(d >> 1); // 12
  • 乘法
let f = 60
console.log(f << 1); //120

3、判断奇数偶数

二进制中,最后一位数是1就是奇数,是零就是偶数

 if (n & 1) 奇数
 else  偶数
 // 同
 if(n % 2 != 0) j奇数
 else 偶数

4、不增加变量实现两数交换

使用四则运算的实现方式

function swap_planA(a, b) {
  console.log(a, b);
  a = a + b
  b = a - b
  a = a - b
  console.log(a, b);
}

使用位运算之异或运算的方式

function swap_planB(a, b) {
  console.log(a, b);
  a = a ^ b
  b = a ^ b
  a = a ^ b
  console.log(a, b);
}

五、总结

地址

代码和解析放在了这个仓库里啦,适龄小伙伴们,有兴趣的话,可以一起来分享和贡献代码到这个仓库。

算法题-面试题-手写题

小结

  • theme: awesome-green【初夏主题的感觉吧】 我们先介绍了每个位运算符的含义,以图表的形式展示,然后从算法题和面试题中巩固对位运算的认识,接着我们看了几个在程序中用到的位运算妙招,相信读完上面的内容,我们对位运算有了较好的认识了。那就完成一些下面的流程图吧哈哈哈。
graph TD
点赞 --> 收藏--> 评论

参考链接菜鸟教程

【剑指offer】