位运算

概述

按位操作符(Bitwise operators) 将其操作数(operands)当作32位的比特序列(由0和1组成),而不是十进制、十六进制或八进制数值。例如,十进制数9,用二进制表示则为1001。按位操作符操作数字的二进制形式,但是返回值依然是标准的JavaScript数值。

按位操作符

有符号 32 位整数

  1. 二进制编码

    // 其中 0b 代表二进制
     314 (base 10) = 0b00000000000000000000000100111010 (base 2)
    
  2. 反码

     314 (base 10) = 0b00000000000000000000000100111010 (base 2)
    ~314 (base 10) = 0b11111111111111111111111011000101 (base 2)
    
  3. 补码

     314 (base 10)  = 0b00000000000000000000000100111010 (base 2)
    ~314 (base 10)  = 0b11111111111111111111111011000101 (base 2)
    // -314 = 314 反码 + 1
    -314 (base 10)  = 0b11111111111111111111111011000110 (base 2)
    
  4. 符号位
    补码保证了当一个数是正数时,其最左的比特位是0,当一个数是负数时,其最左的比特位是1。因此,最左边的比特位被称为符号位

  5. 特殊数字

     0 (base 10) = 00000000000000000000000000000000 (base 2)
    -1 (base 10) = 11111111111111111111111111111111 (base 2)
    

按位逻辑操作符

从概念上讲,按位逻辑操作符按遵守下面规则:

  • 操作数被转换成32位整数,用比特序列(0和1组成)表示。超过32位的数字会被丢弃。
  • 第一个操作数的每个比特位与第二个操作数的相应比特位匹配:第一位对应第一位,第二位对应第二位,以此类推。
  • 位运算符应用到每对比特位,结果是新的比特值。

& (按位与)

a b a & b
0 0 0
0 1 0
1 0 0
1 1 1
     9 (base 10) = 00000000000000000000000000001001 (base 2)
    14 (base 10) = 00000000000000000000000000001110 (base 2)
                   --------------------------------
14 & 9 (base 10) = 00000000000000000000000000001000 (base 2) = 8 (base 10)

注意:

  • 0 & x === 0
  • -1 & x === x

| (按位或)

a b a 或 b
0 0 0
0 1 1
1 0 1
1 1 1
     9 (base 10) = 00000000000000000000000000001001 (base 2)
    14 (base 10) = 00000000000000000000000000001110 (base 2)
                   --------------------------------
14 | 9 (base 10) = 00000000000000000000000000001111 (base 2) = 15 (base 10)

注意:

  • 0 | x === x
  • -1 | x === -1
  • 2.2341543 | 0 === 2
  • 'adkfajdsf' | 0 === 0
  • '123456' | 0 === 123456

~ (按位非)

a ~a
0 1
1 0
 9 (base 10) = 00000000000000000000000000001001 (base 2)
               --------------------------------
~9 (base 10) = 11111111111111111111111111110110 (base 2) = -10 (base 10)

注意:

  • ~x === -(x + 1)
  • ~-1 === 0

^ (按位异或)

a b a ^ b
0 0 0
0 1 1
1 0 1
1 1 0
     9 (base 10) = 00000000000000000000000000001001 (base 2)
    14 (base 10) = 00000000000000000000000000001110 (base 2)
                   --------------------------------
14 ^ 9 (base 10) = 00000000000000000000000000000111 (base 2) = 7 (base 10)

注意:

  • 0 ^ x === x
  • -1 ^ x === ~x

标志位与掩码

标志位

位运算经常被用来创建、处理以及读取标志位序列——一种类似二进制的变量。标志位序列可以节省内存

例如,有 4 个标志位:

  • 标志位 A:我们有 ant
  • 标志位 B:我们有 bat
  • 标志位 C:我们有 cat
  • 标志位 D:我们有 duck

标志位通过位序列 DCBA 来表示

  • 当一个位被置位 (set) 时,它的值为 1
  • 当一个位被清除 (clear) 时,它的值为 0

例如:一个变量 flags 的二进制值为 0101:

const flags = 0b0101;

这个值表示:

  • 标志位 A 是 true (我们有 ant);
  • 标志位 B 是 false (我们没有 bat);
  • 标志位 C 是 true (我们有 cat);
  • 标志位 D 是 false (我们没有 duck);

掩码

掩码 (bitmask) 是一个通过与 | 或来读取标志位的位序列。典型的定义每个标志位的原语掩码如下:

const flag_A = 0b0001;
const flag_B = 0b0010;
const flag_C = 0b0100;
const flag_D = 0b1000;

新的掩码可以在以上掩码上使用逻辑运算创建。

应用:

  • 某个特定的位可以通过与掩码做 &运算得到,通过与掩码的 & 运算可以去掉无关的位,得到特定的位
    const flags = 0b0101;
    if (flags & flag_C) { // 0101 & 0100 => 0100 => true
      // do something
    }
    
  • 通过与掩码做 | 运算设置标志位,掩码中为 1 的位可以设置对应的位
    // 掩码 1100 可用来设置位 C 和 D
    let flags = 0b0101;
    const mask = flag_C | flag_D // 0b0100 | 0b1000 => 0b1100
    flags |= mask // 0b0101 | 0b1100 => 0b1101
    
  • 通过与掩码做 & 运算清除标志位,掩码中为 0 的位可以设置对应的位
    // 掩码 1010 可以用来清除标志位 A 和 C 
    let flags = 0b1101;
    const mask = ~(flag_A | flag_C) // ~(0b0001 | 0b0100) => 0b1010
    flags &= mask // 0b1101 & 0b1010 => 0b1000
    
  • 标志位可以使用 ^ 运算切换。所有值为 1 的位可以切换对应的位
    // 掩码 0110 可以用来切换标志位 B 和 C
    let flags = 0b1100;
    const mask = flag_B | flag_C // 0b0010 | 0b0100 => 0b0110
    flags ^= mask // 0b1100 ^ 0b0110 => 0b1010
    
  • 所有标志位可以通过非运算翻转
    let flags = 0b1010;
    flags = ~flags // ~0b1010 => 0b0101
    

德摩根定律

~(a & b) === ~a | ~b
~(a | b) === ~a & ~b

实用技能

~ + indexOf


const str = 'rawr';
const searchFor = 'a';

// ~-1 === 0
if (~str.indexOf(searchFor)) {
  // searchFor 包含在字符串中
} else {
  // searchFor 不包含在字符串中
}

// (~str.indexOf(searchFor))的返回值
// r == -1
// a == -2
// w == -3

通过某个条件来切换一个值为0或者1

let whether = false;
let number = 1;

// whether === true  => num === 0
// whether === false => num === 1
num = whether ^ 1;

// number 在 0 和 1 之间切换
number ^= 1;

判断奇偶

if(a & 1) {
  // 奇数
} else {
   // 偶数
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容