概述
本文部分内容取自以下文章
深入研究js中的位运算及用法
js中位运算的运用
Js中的位运算和权限设计
首先了解下计算机中常用进制 二进制 八进制 十进制 十六进制
进制基数(radix) | 前缀 | 示例 |
---|---|---|
二进制 binary | 0b 0B | 0b11 = 1*2¹+1*2º = 3 |
八进制 octal | 0o 0O 0 | 0o11 = 1*8¹+1 = 9 |
十进制 decimal | 无前缀 | 11 = 11 |
十六进制 hex | 0x 0X | 0x11 = 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 & 1 | 1 | 0101 & 0001 | 0001 |
5 | 1 | 5 | 0101 |
5 ^ 1 | 4 | 0101 ^ 0001 | 0100 |
~ 5 | 10 | ~0101 | 1010 |
5 << 1 | 10 | 0101 << 1 | 1010 |
5 >> 1 | 2 | 0101 >> 1 | 0010 |
5 >>> 1 | 2 | 0101 >>> 1 | 0010 |
由于 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:
负数存储采用的形式是二进制补码。计算数字二进制补码的步骤有三步:
-
确定该数字的非负版本的二进制表示(例如,要计算 -18的二进制补码,首先要确定 18 的二进制表示)
-
求得二进制反码,即要把 0 替换为 1,把 1 替换为 0(相当于~操作)
-
在二进制反码上加 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)
最后
以上就是自由黑猫为你收集整理的JS中的位运算的全部内容,希望文章能够帮你解决JS中的位运算所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复