YIXUNFE / blog

文章区
151 stars 25 forks source link

巧用 JAVASCRIPT 中的位运算 #63

Open YIXUNFE opened 8 years ago

YIXUNFE commented 8 years ago

巧用 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 11
= 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  // 小写转大写

由于一些国家使用的字母(比如土耳其)无法通过 toLowerCasetoUpperCase 正确的转换字母大小写,所以使用位运算来实现该功能。

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 中的源码


参考文献


Thanks


chechecarer commented 8 years ago

向上取整,用~~似乎不对吧。可以用 var num = 3.14; var t = num >> 0; num = (num === t)? t ; t + 1;

haocong commented 8 years ago

在非(~)运算那,Math.ceil(3.14) 不应该是等于4么?

ranrantu commented 8 years ago

@chechecarer 向上取整可以用 -~3.14 把第一个~替换成-即可。

ricosmall commented 7 years ago

向上取整可以 ~~3.14 + 1 或者(3.14 | 0) + 1

ricosmall commented 7 years ago

作者想说的应该是Math.floor

maimaiking commented 7 years ago

(n & 1) === 0 //true n为偶数

hellozhangran commented 6 years ago
function isPos(n) {
  return (n === (n >>> 0)) ? true : false;  
}

isPos(-1); // false
isPos(1); // true

只能判断整数的正负,小数的就不灵了 ~

Lie8466 commented 6 years ago

我在想《深入浅出 Node.js》“ Javascript 中只有 double 型的数据类型,在进行位运算的过程中,需要将 double 型转换为 int 型”这句话是不是有问题,应该是“ Javascript 中只有 int 型的数据类型,在进行位运算的过程中,需要将 double 型转换为 int 型”吧?