blog
blog copied to clipboard
巧用 JAVASCRIPT 中的位运算
巧用 JAVASCRIPT 中的位运算
最近边忙边啃朴神写的《深入浅出 Node.js》,在讲述 Node.js 中模块机制的章节中看到了一段话:
Javascript 的一个典型弱点就是位运算。Javascript 的位运算参照 Java 的位运算实现,但是 Java 位运算是在 int 型数字的基础上进行的,而 Javascript 中只有 double 型的数据类型,在进行位运算的过程中,需要将 double 型转换为 int 型,然后在进行。所以,在 Javascript 层面上做位运算的效率不高。
在书中,这段话告诉我们,为了提高模块的性能,在频繁出现位运算的需求时,我们可以使用 C/C++ 编写模块以提高性能(相对于直接用 Javascript 写)。然而这段话倒是激发了我对 Javascript 位运算的兴致。
虽然平时也常常用 ~~
做向上取整等操作,但终究对位运算还是有点生疏,所以利用这个机会,再次温习一遍位运算。
## 与(&)运算
& 运算与我们熟悉的 && 运算类似,两个都是 1 才会得到 1。比如 2 & 3:
十进制 | 二进制 | |
---|---|---|
2 | 10 | |
& | 3 | 11 |
= | 2 | 10 |
运算后结果就是 2 了。
### 巧用——判断奇偶数
利用 & 运算的特点,我们可以用以简单的判断奇偶数,公式:
n & 1 === 0 //true 为 奇数
奇偶数的判断其实就是判断数字的二进制值最后一位是 0 还是 1。所以利用 & 运算得到数字的最后一位值即可。
比如 11 & 1 = 1
十进制 | 二进制 | |
---|---|---|
11 | 1011 | |
& | 1 | 0001 |
= | 1 | 0001 |
## 或(|)运算
| 运算与我们熟悉的 || 运算类似,两个中有一个是 1 就会得到 1。比如 2 & 3:
十进制 | 二进制 | |
---|---|---|
2 | 10 | |
3 | ||
= | 3 | 11 |
运算后结果就是 3 了。
### 巧用——向下取整
通常我们会使用 Math.floor
对一个数字向下取整,但是利用 | 运算也可以做到。
(3.14 | 0) === 3 //true
这里面其实做了两步操作,由于浮点数是不支持位运算的,所以会先把 3.14 转成整数 3 再进行位运算。所以 3.14 | 0 的结果就是 3。
## 非(~)运算
~ 运算的特点是将所有位取反,包括符号位。比如 ~3:
十进制 | 二进制 | |
---|---|---|
~ | 3 | 00000000000000000000000000000011 |
= | -4 | 11111111111111111111111111111100 |
最高位表示符号位,最高位为 1 表示是负数。负数的二进制转化为十进制的规则是,符号位不变,其他位取反后加 1。取反之后为 10000000000000000000000000000011
,加1之后为 10000000000000000000000000000100
,所以十进制为 -4。
### 巧用——向上取整
我们可以利用两次 ~ 运算来取代 Math.floor 方法。
Math.floor(3.14) //3
~~3.14 //3
感谢 @chechecarer 同学的指正 :+1:
## 异或(^)运算
^ 运算的规则是两个数中只有一个为 1 时才得到 1,其他情况得到 0,比如 2 ^ 3:
十进制 | 二进制 | |
---|---|---|
2 | 10 | |
^ | 3 | 11 |
= | 1 | 01 |
### 巧用——交替变量
var a = 1, b = 2;
a ^= b; // a = a ^ b = 1 ^ 2 = 3
b ^= a; // b = b ^ (a ^ b) = 2 ^ (1 ^ 2) = 1
a ^= b; // a = a ^ b = 3 ^ 1 = 2
资料整理到这里自己有点晕 :joy: ,两个变量在不使用第三个变量的前提下交换,还有其他多种方法。比如:
var a = 1, b = 2;
a = a + b; // 3
b = a - b; // 1
a = a - b; // 2
好像更加直观点 :joy: 。
## 左移(<< 运算即将数字二进制值左移,多出的位数以 0 补位,并不影响符号位。比如 3 << 2
十进制 | 二进制 | |
---|---|---|
3 | 11 | |
<< | 2 | |
= | 12 | 1100 |
### 巧用——求 2 的 N 次方
function power(n) {
return 1 << n;
}
power(4); // 16
以 2 为底数,左移的位数即底数的次方。
## 右移(>>)运算
运算即将数字二进制值右移,多出的位数以 0 补位,并不影响符号位。比如 10 >> 2
十进制 | 二进制 | |
---|---|---|
10 | 1010 | |
>> | 2 | |
= | 2 | 10 |
### 巧用——求一个数字的 N 等分(向下取整)
function half(num, n) {//平分 n 次
return num >> n;
}
half(4, 1); // 2
half(4, 2); // 1
half(5, 1); // 2
## 无符号右移(>>>)运算
正数的无符号右移与有符号右移结果是一样的。负数的无符号右移会把符号位也一起移动,而且无符号右移会把负数的二进制码当成正数的二进制码:
var num = -64; // 11111111111111111111111111000000
num = num >>> 5; // 134217726
### 巧用——判断数字正负
function isPos(n) {
return (n === (n >>> 0)) ? true : false;
}
isPos(-1); // false
isPos(1); // true
## 其他一些位运算的使用
网上翻的资料中还有很多利用位运算简化代码的例子,这里再罗列一些:
### 强制转成整数类型
由于做位运算时需要强制转换成整数类型,所以原本通过 parseInt
等方法得到值是 NaN 的类型,会在位运算时得到 0。
parseInt((function () {})) //NaN
(function () {}) | 0 //0
(function () {}) & 0 //0
### 转换字母大小写
//假设变量 char 是一个字母
char.charCodeAt(0) | 32 // 大写转小写
char.charCodeAt(0) & ~32 // 小写转大写
由于一些国家使用的字母(比如土耳其)无法通过 toLowerCase
和 toUpperCase
正确的转换字母大小写,所以使用位运算来实现该功能。
var manualLowercase = function(s) {
return isString(s) ? s.replace(/[A-Z]/g, function(ch) {
return String.fromCharCode(ch.charCodeAt(0) | 32);
}) : s;
};
var manualUppercase = function(s) {
return isString(s) ? s.replace(/[a-z]/g, function(ch) {
return String.fromCharCode(ch.charCodeAt(0) & ~32);
}) : s;
};
AngularJS 中的源码
## 参考文献 - [js中位运算的运用](http://www.tuicool.com/articles/VJryYb) - [javascript的变态位运算](http://blog.sina.com.cn/s/blog_53d3c24a0100mpnv.html) - [小代码大学问之JavaScript位运算](http://www.tuicool.com/articles/yEJbyq)
## Thanks
向上取整,用~~似乎不对吧。可以用
var num = 3.14; var t = num >> 0; num = (num === t)? t ; t + 1;
在非(~)运算那,Math.ceil(3.14) 不应该是等于4么?
@chechecarer 向上取整可以用 -~3.14 把第一个~替换成-即可。
向上取整可以 ~~3.14 + 1
或者(3.14 | 0) + 1
作者想说的应该是Math.floor
(n & 1) === 0 //true n为偶数
function isPos(n) {
return (n === (n >>> 0)) ? true : false;
}
isPos(-1); // false
isPos(1); // true
只能判断整数的正负,小数的就不灵了 ~
我在想《深入浅出 Node.js》“ Javascript 中只有 double 型的数据类型,在进行位运算的过程中,需要将 double 型转换为 int 型”这句话是不是有问题,应该是“ Javascript 中只有 int 型的数据类型,在进行位运算的过程中,需要将 double 型转换为 int 型”吧?