深入研究js中的位运算及用法

论坛 期权论坛 期权     
程序猿show   2019-7-21 15:59   3613   0
[h2]什么是位运算?[/h2]位运算是在数字底层(即表示数字的 32 个数位)进行运算的。由于位运算是低级的运算操作,所以速度往往也是最快的(相对其它运算如加减乘除来说),并且借助位运算有时我们还能实现更简单的程序逻辑,缺点是很不直观,许多场合不能够使用。
位运算只对整数起作用,如果一个运算子不是整数,会自动转为整数后再运行。虽然在 JavaScript 内部,数值都是以64位浮点数的形式储存,但是做位运算的时候,是以32位带符号的整数进行运算的,并且返回值也是一个32位带符号的整数。
关于二进制==以下来源于w3shool==:
ECMAScript 整数有两种类型,即有符号整数(允许用正数和负数)和无符号整数(只允许用正数)。在 ECMAScript 中,所有整数字面量默认都是有符号整数,这意味着什么呢?
有符号整数使用 31 位表示整数的数值,用第 32 位表示整数的符号,0 表示正数,1 表示负数。数值范围从 -2147483648 到 2147483647。
可以以两种不同的方式存储二进制形式的有符号整数,一种用于存储正数,一种用于存储负数。正数是以真二进制形式存储的,前 31 位中的每一位都表示 2 的幂,从第 1 位(位 0)开始,表示 20,第 2 位(位 1)表示 21。没用到的位用 0 填充,即忽略不计。例如,下图展示的是数 18 的表示法。

==以上来源于w3shool==:
那在js中二进制和十进制如何转换呢?如下
  1. // 十进制 => 二进制let num = 10;console.log(num.toString(2));// 二进制 => 十进制let num1 = 1001;console.log(parseInt(num1, 2));
复制代码
[h2]js中都有哪些位运算?[/h2]
[h3]按位或 |[/h3]
对每对比特位执行与(AND)操作。只有 a 和 b 任意一位为1时,a | b 就是 1。如下表9 | 3 = 11
9=10013=001111=1011应用场景:
1.取整
对于一般的整数,返回值不会有任何变化。对于大于2的32次方的整数,大于32位的数位都会被舍去。
  1. function toInt(num) {    return num | 0}console.log(toInt(1.8))        // 1console.log(toInt(1.23232))    // 1
复制代码
2.边界判断假如我们有一个拖动事件,规定被拖动模块需要在容器内部运动,这时就有边界判断,这其中又包括上,下,左,右四种单一边界,同时还有类似上右,上左等叠加边界,如果我们需要记录这种状态,通过位运算要比使用if判断要简单一些,上右下左四种边界分别用1,2,4,8表示,代码如下:
  1. let flag = 0;if (pos.left < left) flag = flag | 8;if (pos.right > right) flag = flag | 2;if (pos.bottom > bottom) flag = flag | 4;if (pos.top < top) flag = flag | 1;switch(flag) {    // 上    case 1:    // 右    case 2:    // 右上    case 3:    // 下    case 4:    // 右下    case 6:    // 左    case 8:    // 左上    case 9:    // 左下    case 12:    // code}
复制代码
同理,假如我们有一系列控制开关,通过 a | b | c的形式要比 '{a: true, b: true, c: true}' 简单的多。
[h3]按位与 &[/h3]
对每对比特位执行与(AND)操作。只有 a 和 b 都为1时,a & b 就是 1。如下表9 & 3 = 1
9=10013=00111=0001由上表我们可以清晰的看出按位与的计算规则,由此可以引出一系列应用场景
1.判断奇偶我们知道奇数的二进制最后一位必然为1,所以任意一个奇数 & 1 一定等于1。
  1. // 判断奇偶return number & 1 === 1
复制代码
2.系统权限业务场景:
我们假设某个管理系统有a, b, c, d四级权限,其中不同帐号分别有不同的权限(可能有1个或多个),例如admin 账户有a + b +c +d 四级权限,guest用户有b + c权限,那这时候应该怎么设计更简单一些呢?
按位与:是时候登场了!
基本思路:
我们把权限分别用0001, 0010, 0100, 1000表示(即最通俗的1,2,4,8),如果admin用户有a, b, c, d四种权限,则admin的权限为 1 | 2 | 4 | 8 = 15,而guest用户权限为 4 | 8 = 12, 则判断用户是否有某种权限可以如下判断
  1. admin & 4 === 4admin & 8 === 8admin & 2 === 2admin & 1 === 1
复制代码
[h3]按位异或 ^[/h3]
对于每一个比特位,当两个操作数相应的比特位有且只有一个1时,结果为1,否则为0。
==其运算法则相当于不带进位的二进制加法==
9=10013=001110=1010应用场景:
1.切换变量0和1假如我们通过某个条件来切换一个值为0或者1
  1. function update(toggle) {    num = toggle ? 1 : 0;}update(true);// 通过异或我们可以这么写num = num ^ 1;
复制代码
2.交换两个变量的值(不用第三个变量)
  1. let a = 5,    b = 6;a = a ^ b;b = a ^ b;a = a ^ b;// 还可以通过运算a = a + b;b = a - b;a = a - b;// es 6[a, b] = [b, a]
复制代码
原理剖析:a = a ^ b; b = a ^ b 相当与 b = a ^ b ^ b = a ^ (b ^ b) = a ^ 0 = a;
3.简单字符串加密
  1.   const key = 313;  function encryption(str) {      let s = '';      str.split('').map(item => {        s += handle(item);      })      return s;  }    function decryption(str) {    let s = '';    str.split('').map(item => {        s += handle(item);    })    return s;  }    function handle(str) {      if (/\d/.test(str)) {        return str ^ key;      } else {        let code = str.charCodeAt();        let newCode = code ^ key;        return String.fromCharCode(newCode);      }  }  let init = 'hello world 位运算';  let result = encryption(init);             // 乴軩窮  let decodeResult = decryption(result);     // hello world 位运算
复制代码
可以看到,我们利用字符串Unicode值的异或运算实现了一个简要的字符串加密效果。
==ps: 上面代码仅为演示,实际解密时应该把key及解密密钥传进去。==
[h3]按位非 ~[/h3]
对每一个比特位执行非(NOT)操作。NOT a 结果为 a 的反转(即反码)。
==ps: 对任一数值 x 进行按位非操作的结果为 -(x + 1)。例如,~5 结果为 -6:==
负数存储采用的形式是二进制补码。计算数字二进制补码的步骤有三步:
1.确定该数字的非负版本的二进制表示(例如,要计算 -18的二进制补码,首先要确定 18 的二进制表示)
2.求得二进制反码,即要把 0 替换为 1,把 1 替换为 0(相当于~操作)
3.在二进制反码上加 1
我们可以看到一个数a取负相当于 ~a + 1, 即 -a = ~a + 1, 因此~a = -(a + 1)
应用场景:
1.取整 (位运算花样取整)
  1. ~~(-5.88) // -5
复制代码
2.判断数组中某项是否存在
  1. // 常用判断if (arr.indexOf(item) > -1) {    // code}// 按位非    ~-1 = - (-1 + 1)if (~arr.indexOf(item)) {    // code}
复制代码
[h2]按位移动操作符[/h2]按位移动操作符有两个操作数:第一个是要被移动的数字,而第二个是要移动的长度。移动的方向根据操作符的不同而不同。
按位移动会先将操作数转换为大端字节序顺序(big-endian order)的32位整数,并返回与左操作数相同类型的结果。右操作数应小于 32位,否则只有最低 5 个字节会被使用。
[h3]左移 [/h3]
该操作符会将第一个操作数向右移动指定的位数。向右被移出的位被丢弃,左侧用0填充。因为符号位变成了 0,所以结果总是非负的。(译注:即便右移 0 个比特,结果也是非负的。)
[h3]题外话[/h3]想起之前小组内的一道算法题,题目是这样的:
1.一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法?
解题思路是:
/*因为n级台阶,第一步有n种跳法:跳1级、跳2级、到跳n级
跳1级,剩下n-1级,则剩下跳法是f(n-1)
跳2级,剩下n-2级,则剩下跳法是f(n-2)
所以f(n)=f(n-1)+f(n-2)+...+f(1)
那么f(n-1)=f(n-2)+f(n-3)+...+f(1)
所以算法为:
[code]function jumpFloorII(number){    return 1
分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:5
帖子:1
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP