位运算是在数字底层(即表示数字的 32 个数位)进行运算的。由于位运算是低级的运算操作,所以速度往往也是最快的(相对其它运算如加减乘除来说),并且借助位运算有时我们还能实现更简单的程序逻辑,缺点是很不直观,许多场合不能够使用。
位运算只对整数起作用,如果一个运算子不是整数,会自动转为整数后再运行。虽然在 JavaScript 内部,数值都是以64位浮点数的形式储存,但是做位运算的时候,是以32位带符号的整数进行运算的,并且返回值也是一个32位带符号的整数。
ECMAScript 整数有两种类型,即有符号整数(允许用正数和负数)和无符号整数(只允许用正数)。在 ECMAScript 中,所有整数字面量默认都是有符号整数,这意味着什么呢?
有符号整数使用 31 位表示整数的数值,用第 32 位表示整数的符号,0 表示正数,1 表示负数。数值范围从 -2147483648 到 2147483647。
可以以两种不同的方式存储二进制形式的有符号整数,一种用于存储正数,一种用于存储负数。正数是以真二进制形式存储的,前 31 位中的每一位都表示 2 的幂,从第 1 位(位 0)开始,表示 20,第 2 位(位 1)表示 21。没用到的位用 0 填充,即忽略不计。例如,下图展示的是数 18 的表示法。
那在js中二进制和十进制如何转换呢?如下
// 十进制 => 二进制
let num = 10;
console.log(num.toString(2));
// 二进制 => 十进制
let num1 = 1001;
console.log(parseInt(num1, 2));
对每对比特位执行与(AND)操作。只有 a 和 b 任意一位为1时,a | b 就是 1。如下表9 | 3 = 11
9 | = | 1 | 0 | 0 | 1 |
---|---|---|---|---|---|
3 | = | 0 | 0 | 1 | 1 |
11 | = | 1 | 0 | 1 | 1 |
应用场景:
取整
对于一般的整数,返回值不会有任何变化。对于大于2的32次方的整数,大于32位的数位都会被舍去。
function toInt(num) {
return num | 0
}
console.log(toInt(1.8)) // 1
console.log(toInt(1.23232)) // 1
假如我们有一个拖动事件,规定被拖动模块需要在容器内部运动,这时就有边界判断,这其中又包括上,下,左,右四种单一边界,同时还有类似上右,上左等叠加边界,如果我们需要记录这种状态,通过位运算要比使用if判断要简单一些,上右下左四种边界分别用1,2,4,8表示,代码如下:
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
}
9 | = | 1 | 0 | 0 | 1 |
---|---|---|---|---|---|
3 | = | 0 | 0 | 1 | 1 |
1 | = | 0 | 0 | 0 | 1 |
由上表我们可以清晰的看出按位与的计算规则,由此可以引出一系列应用场景
判断奇偶
我们知道奇数的二进制最后一位必然为1,所以任意一个奇数 & 1 一定等于1。
// 判断奇偶
return number & 1 === 1
业务场景:
我们假设某个管理系统有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, 则判断用户是否有某种权限可以如下判断
admin & 4 === 4
admin & 8 === 8
admin & 2 === 2
admin & 1 === 1
对于每一个比特位,当两个操作数相应的比特位有且只有一个1时,结果为1,否则为0。
==其运算法则相当于不带进位的二进制加法==
9 | = | 1 | 0 | 0 | 1 |
---|---|---|---|---|---|
3 | = | 0 | 0 | 1 | 1 |
10 | = | 1 | 0 | 1 | 0 |
应用场景:
切换变量0和1
假如我们通过某个条件来切换一个值为0或者1
function update(toggle) {
num = toggle ? 1 : 0;
}
update(true);
// 通过异或我们可以这么写
num = num ^ 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]
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及解密密钥传进去。==
对每一个比特位执行非(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)
应用场景:
取整 (位运算花样取整)
~~(-5.88) // -5
// 常用判断
if (arr.indexOf(item) > -1) {
// code
}
// 按位非 ~-1 = - (-1 + 1)
if (~arr.indexOf(item)) {
// code
}
按位移动操作符有两个操作数:第一个是要被移动的数字,而第二个是要移动的长度。移动的方向根据操作符的不同而不同。
按位移动会先将操作数转换为大端字节序顺序(big-endian order)的32位整数,并返回与左操作数相同类型的结果。右操作数应小于 32位,否则只有最低 5 个字节会被使用。
该操作符会将第一个操作数向左移动指定的位数。向左被移出的位被丢弃,右侧用 0 补充。
例如 3 << 2 的运算图示如下:
3 = 0000 0000 0000 0000 0000 0000 0000 0011
12 = 0000 0000 0000 0000 0000 0000 0000 1100
==ps: 对任一数值 x 进行左移n, 相当于十进制里的乘以10的倍数,在这儿是指==
x * 2^n
应用场景:
rgb和16进制颜色转换
首先我们需要知道RGB与十六进制之间的关系,例如我们最常见的白色RGB表示为rgb(255, 255, 255), 十六进制表示为#FFFFFFF, 我们可以把十六进制颜色除
‘#’外按两位分割成一部分,即FF,FF,FF, 看一下十六进制的FF转为十进制是多少呢?没错,就是255!
了解了十六进制和RGB关系之后,我们就会发现RGB转十六进制方法就很简单了
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}`;
}
该操作符会将第一个操作数向右移动指定的位数。向右被移出的位被丢弃,拷贝最左侧的位以填充左侧。由于新的最左侧的位总是和以前相同,符号位没有被改变。所以被称作“符号传播”。
==ps: 对任一数值 x 进行右移n, 相当于十进制里的除以10的倍数,在这里是指除以数之后取整==
x / 2^n
应用场景:
十六进制转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})`;
}
该操作符会将第一个操作数向右移动指定的位数。向右被移出的位被丢弃,左侧用0填充。因为符号位变成了 0,所以结果总是非负的。(译注:即便右移 0 个比特,结果也是非负的。)
我们都知道通过Math.floor()方法可实现数值的向下取整,得到小于或等于该数字的最大整数。除了Math.floor方法,还可以使用位运算|,>>来实现向下取整哦
扩展运算符( spread )是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。
位运算的方法在其它语言也是一样的,不局限于JS,所以本文提到的位运算也适用于其它语言。位运算是低级的运算操作,所以速度往往也是最快的
平常的数值运算,其本质都是先转换成二进制再进行运算的,而位运算是直接进行二进制运算,所以原则上位运算的执行效率是比较高的,由于位运算的博大精深,下面通过一些在js中使用位运算的实例
js实现:四舍五入、向上取整、向下取整等方法。parseInt、Math.ceil、Math.round、Math.floor、toFixed等的使用
JS经常会遇到延迟执行的动作,并且失败后自动尝试,尝试N次之后就不再尝试的需求,今天刚好又遇到,于是写个闭包,以后不断完善继续复用。检查并计数第一个参数用来标记是尝试哪个动作的,第二个参数是最大尝试次数
大多数语言都提供了按位运算符,恰当的使用按位运算符有时候会取得的很好的效果。在我看来按位运算符应该有7个:& 按位与、| 按位或、^ 按位异或、~ 按位非
PHP取整数函数常用的四种方法:1.直接取整,舍弃小数,保留整数:intval(); 2.四舍五入取整:round(); 3.向上取整,有小数就加1:ceil(); 4.向下取整:floor()。
ECMAScript 中的相等操作符由两个等于号 ( == ) 表示,如果两个操作数相等,则返回 true。相等操作符会先转换操作数(通常称为强制转型),然后比较它们的相等性。
前端工作中经常遇到数字计算保留小数问题,由于不是四舍五入的方式不能使用toFixed函数,本文采用正则表达式匹配字符串的方式,解决对数字的向上或向下保留小数问题:
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!