JS中的位运算

本文部分内容取自以下文章
深入研究js中的位运算及用法
js中位运算的运用
Js中的位运算和权限设计

首先了解下计算机中常用进制 二进制 八进制 十进制 十六进制

进制基数(radix)前缀示例
二进制 binary0b 0B0b11 = 1*2¹+1*2º = 3
八进制 octal0o 0O 00o11 = 1*8¹+1 = 9
十进制 decimal无前缀11 = 11
十六进制 hex0x 0X0x11 = 1*16¹+1*16º = 17
// 二进制:前缀 0b,0B
0b10000000000000000000000000000000 // 2147483648
0b01111111100000000000000000000000 // 2139095040
0B00000000011111111111111111111111 // 8388607

// 八进制:前缀 0o,0O(以前支持前缀 0)
0o755 // 493
0o644 // 420

// 十进制
123456789
0

// 十六进制:前缀 0x,0X
0xFFFFFFFFFFFFFFFFF // 295147905179352830000
0x123456789ABCDEF   // 81985529216486900
0XA                 // 10

其中十六进制中

  • A 代表十进制的10、二进制的1010
  • B 代表十进制的11、二进制的1011
  • C 代表十进制的12、二进制的1100
  • D 代表十进制的13、二进制的1101
  • E 代表十进制的14、二进制的1110
  • F 代表十进制的15、二进制的1111

例: 0x1f = 1*16¹+15*16º = 31

那在js中二进制和十进制如何转换呢?如下

// 十进制 => 二进制
let num = 10;
console.log(num.toString(2));
// 二进制 => 十进制
let num1 = 1001;
console.log(parseInt(num1, 2)); 

再来了解一下 Number

JavaScript的Number类型为双精度IEEE 754 64位浮点类型
结构借用维基百科:
在这里插入图片描述

  • sign bit(符号): 用来表示正负号
  • exponent(指数): 用来表示次方数
  • mantissa(尾数): 用来表示精确度

也就是说一个数字的范围只能在 -(2^53 -1) 至 2^53 -1 之间。

最近出了stage3BigInt 任意精度数字类型,已经进入stage3规范

既然讲到这里,就多说一句:0.1 + 0.2 算不准的原因也在于此。浮点数用二进制表达时是无穷的,且最多 53 位,必须截断,进而产生误差。可以用位运算

位运算:

JavaScript 使用 32 位按位运算数
JavaScript 将数字存储为 64 位浮点数,但所有按位运算都以 32 位二进制数执行。

在执行位运算之前,JavaScript 将数字转换为 32 位有符号整数。

执行按位操作后,结果将转换回 64 位 JavaScript 数。

上面的例子使用 4 位无符号二进制数。所以 ~ 5 返回 10。

由于 JavaScript 使用 32 位有符号整数,JavaScript 将返回 -6。

00000000000000000000000000000101 (5)

11111111111111111111111111111010 (~5 = -6)

有符号整数使用最左边的位作为减号。

运算符名称描述
&AND如果两位都是 1 则设置每位为 1
|OR如果两位之一为 1 则设置每位为 1
^XOR如果两位只有一位为 1 则设置每位为 1
~NOT反转所有位
<<零填充左位移通过从右推入零向左位移,并使最左边的位脱落
>>有符号右位移通过从左推入最左位的拷贝来向右位移,并使最右边的位脱落
>>>零填充右位移通过从左推入零来向右位移,并使最右边的位脱落

实例:

操作结果等同于结果
5 & 110101 & 00010001
5150101
5 ^ 140101 ^ 00010100
~ 510~01011010
5 << 1100101 << 11010
5 >> 120101 >> 10010
5 >>> 120101 >>> 10010

由于 JavaScript 使用 32 位有符号整数,JavaScript 将返回 -6。

00000000000000000000000000000101 (5)

11111111111111111111111111111010 (~5 = -6)

有符号整数使用最左边的位作为减号。

js中都有哪些位运算?

1. 按位与: &

1. 判断奇偶:

&&运算符我们都知道,只有两个都为真,结果才为真。&道理是一样的,只有两个数的值为1时,才返回1。例如1和3的按位与操作:

			    0001
			 &  0011
			---------
			    0001

只有对应的数为1时,结果才为1,其他都为0。
判断一个数是奇数还是偶数,我们会用求余数来判断:

function assert(n) {
        if (n % 2 === 1) {
	console.log("n是奇数");
    } else {
	console.log("n是偶数");
   }
}

assert(3); // "n是奇数"

我们也可以用一个数和1进行按位&操作来判断,而且速度更快:

function assert(n) {
if (n & 1) {
    console.log("n是奇数");
} else {
    console.log("n是偶数");
}
}

assert(3); // "n是奇数"

下面是位运算过程:

			 1 = 0001
			 3 = 0011
			 --------
		     & = 0001

奇数的二进制码的最后一位数肯定是1,而1只有最后一位为1,按位&操作之后,结果肯定只有最后一位数为1。而偶数的二进制表示的最后一位数是0,和1进行按位&操作,结果所有位数都为0。

2. 系统权限:

业务场景:
我们假设某个管理系统有a, b, c三级权限,其中不同帐号分别有不同的权限(可能有1个或多个),例如admin 账户有a + b +c 三级权限,admin用户有a + b权限,那这时候应该怎么设计更简单一些呢?

let a = 0b100
let b = 0b010
let c = 0b001

// 给用户赋 r w 两个权限
let user = a | b
// user = 6
// user = 0b110 (二进制)

console.log((user & a) === a) // true  有 a 权限
console.log((user & b) === b) // true  有 b 权限
console.log((user & c) === c) // false 没有 c 权限

系统权限相关扩展可以跳转Js中的位运算和权限设计 博主写的很详细

2. 按位或: |

1. 取整:
|与||操作符的道理也是一样的,只要两个数中有一个数为1,结果就为1,其他则为0。

				0001
			 |  0011
			---------
			    0011

对浮点数向下求整,我们会用下面的方法:

var num = Math.floor(1.1); // 1

我们也可以用位运算来求整:

var num = 1.1 | 0; // 1

其实浮点数是不支持位运算的,所以会先把1.1转成整数1再进行位运算,就好像是对浮点数向下求整。所以1|0的结果就是1。

3. 按位非: ~

按位非就是求二进制的反码:

对任一数值 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. 取整 (位运算花样取整)
~~(-5.88) // -5
2. 判断数组中某项是否存在(装大佬专用)
// 常用判断
if (arr.indexOf(item) > -1) {
    // code
}
// 按位非    ~-1 = - (-1 + 1)
if (~arr.indexOf(item)) {
    // code
}

4. 按位移动操作符: << >>

按位移动操作符有两个操作数:第一个是要被移动的数字,而第二个是要移动的长度。移动的方向根据操作符的不同而不同。

按位移动会先将操作数转换为大端字节序顺序(big-endian order)的32位整数,并返回与左操作数相同类型的结果。右操作数应小于 32位,否则只有最低 5 个字节会被使用。

1. 左移 <<

该操作符会将第一个操作数向左移动指定的位数。向左被移出的位被丢弃,右侧用 0 补充。

例如 3 << 2 的运算图示如下:
3 = 0000 0000 0000 0000 0000 0000 0000 0011
12 = 0000 0000 0000 0000 0000 0000 0000 1100

对任一数值 x 进行左移n, 相当于十进制里的乘以10的倍数,在这儿是指

x * 2^n
1. rgb和16进制颜色转换

首先我们需要知道RGB与十六进制之间的关系,例如我们最常见的白色RGB表示为rgb(255, 255, 255), 十六进制表示为#FFFFFFF, 我们可以把十六进制颜色除‘#’外按两位分割成一部分,即FF,FF,FF, 看一下十六进制的FF转为十进制是多少呢?没错,就是255!

了解了十六进制和RGB关系之后,我们就会发现RGB转十六进制方法就很简单了

将RGB的3个数值分别转为十六进制数,然后拼接,即 rgb(255, 255, 255) => ‘#’ + ‘FF’ + ‘FF’ + ‘FF’。
巧妙利用左移,我们把十六进制数值部分当成一个整数,即FFFFFF,我们可以理解为FF0000 + FF00 + FF, 如同我们上面解释,如果左移是基于十六进制计算的,则可以理解为FF << 4, FF << 2, FF, 而实际上我们转为二进制则变为 FF << 16,如下:

x * 16^4  = x * 2 ^ 16

了解了原理以后,代码如下:

function RGBToHex(rgb){
    // 取出rgb中的数值
    let arr = rgb.match(/\d+/g);
    if (!arr || arr.length !== 3) {
        console.error('rgb数值不合法');
        return
    }
    let hex = (arr[0]<<16 | arr[1]<<8 | arr[2]).toString(16);
    // 自动补全第一位
    if (hex.length < 6) {
        hex = '0' + hex;
    }
    return `#${hex}`;
}
2. 有符号右移: >>

该操作符会将第一个操作数向右移动指定的位数。向右被移出的位被丢弃,拷贝最左侧的位以填充左侧。由于新的最左侧的位总是和以前相同,符号位没有被改变。所以被称作“符号传播”。

对任一数值 x 进行右移n, 相当于十进制里的除以10的倍数,在这里是指除以数之后取整

x / 2^n
1. 十六进制转RGB

原理见上方RGB转十六进制

function hexToRGB(hex){
    if (!/^#([0-9a-fA-F]{3}){1,2}$/.test(hex)) {
        console.error('颜色不合法'); 
        return
    };
    // #f00 转为 #ff0000
    if (hex.length == 4) {
        hex = hex.replace(/([0-9a-fA-F])/g, '$1$1');
    };
    let num = hex.replace('#', '0x');
    let r = num >> 16;
    // 0xff = 255
    let g = num >> 8 & 0xff;
    let b = num  & 0xff;    
    return `rgb(${r},${g},${b})`;
}
2. 无符号右移: >>

该操作符会将第一个操作数向右移动指定的位数。向右被移出的位被丢弃,左侧用0填充。因为符号位变成了 0,所以结果总是非负的。(译注:即便右移 0 个比特,结果也是非负的。)

题外话

想起之前小组内的一道算法题,题目是这样的:
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)

所以算法为:

function jumpFloorII(number){
    return 1<<(number-1);
}

WTF? 什么意思?
其实很简单,看下面过程

f(n)=f(n-1)+f(n-2)+...+f(1)

f(n-1)=f(n-2)+f(n-3)+...+f(1)

f(n) = 2*f(n-1) = 4 * f(n-2) = 8 * f(n-3) ..... = 2(n-1)次方乘f(1),转为位运算即为 1 << (n - 1)
  • 5
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
在 JavaScript 位运算使用以下语法: 1. 按位与(AND):使用 `&` 运算符。它对两个操作数的每个对应位执行逻辑 AND 操作,并返回结果。例如: ```javascript const result = 5 & 3; // 1 ``` 在上述示例,`5` 的二进制表示为 `101`,`3` 的二进制表示为 `011`,按位与运算后,得到二进制 `001`,即十进制的 `1`。 2. 按位或(OR):使用 `|` 运算符。它对两个操作数的每个对应位执行逻辑 OR 操作,并返回结果。例如: ```javascript const result = 5 | 3; // 7 ``` 在上述示例,`5` 的二进制表示为 `101`,`3` 的二进制表示为 `011`,按位或运算后,得到二进制 `111`,即十进制的 `7`。 3. 按位异或(XOR):使用 `^` 运算符。它对两个操作数的每个对应位执行逻辑 XOR 操作,并返回结果。例如: ```javascript const result = 5 ^ 3; // 6 ``` 在上述示例,`5` 的二进制表示为 `101`,`3` 的二进制表示为 `011`,按位异或运算后,得到二进制 `110`,即十进制的 `6`。 4. 按位非(NOT):使用 `~` 运算符。它对操作数的每个位执行逻辑 NOT 操作,并返回结果。例如: ```javascript const result = ~5; // -6 ``` 在上述示例,`5` 的二进制表示为 `00000000000000000000000000000101`,按位非运算后,得到二进制 `11111111111111111111111111111010`,即十进制的 `-6`。 需要注意的是,位运算符操作的是操作数的每个位,因此操作数通常是整数类型。如果操作数不是整数,JavaScript 会在进行位运算之前将其转换为整数。 希望以上信息对你有所帮助!如果还有其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值