Gahing's blog Gahing's blog
首页
知识体系
  • 前端基础
  • 应用框架
  • 工程能力
  • 应用基础
  • 专业领域
  • 业务场景
  • 前端晋升 (opens new window)
  • Git
  • 网络基础
  • 算法
  • 数据结构
  • 编程范式
  • 编解码
  • Linux
  • AIGC
  • 其他领域

    • 客户端
    • 服务端
    • 产品设计
软素质
  • 面试经验
  • 人生总结
  • 个人简历
  • 知识卡片
  • 灵感记录
  • 实用技巧
  • 知识科普
  • 友情链接
  • 美食推荐 (opens new window)
  • 收藏夹

    • 优质前端信息源 (opens new window)
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Gahing / francecil

To be best
首页
知识体系
  • 前端基础
  • 应用框架
  • 工程能力
  • 应用基础
  • 专业领域
  • 业务场景
  • 前端晋升 (opens new window)
  • Git
  • 网络基础
  • 算法
  • 数据结构
  • 编程范式
  • 编解码
  • Linux
  • AIGC
  • 其他领域

    • 客户端
    • 服务端
    • 产品设计
软素质
  • 面试经验
  • 人生总结
  • 个人简历
  • 知识卡片
  • 灵感记录
  • 实用技巧
  • 知识科普
  • 友情链接
  • 美食推荐 (opens new window)
  • 收藏夹

    • 优质前端信息源 (opens new window)
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 前端基础

    • 编程语言

      • CSS

      • HTML

      • JavaScript

        • ECMAScript6入门
        • JS 手写题

        • JS技巧
        • JS 学习笔记
        • Promise then 原理分析
        • async 函数编译原理
        • 你不知道的JavaScript(上)
        • 再谈闭包
        • 浏览器剪切板协议
        • 前端实现相对路径转绝对路径的几种方法
        • 为什么 0.._ 等于 undefined
        • 前端项目中常用的位操作技巧
          • 前言
          • 1. 检测两个整数是否异号
          • 2. 判断一个整数是否为 2 的幂
          • 3. 计算无符号整数的比特位中有多少个 1
            • 原始方法
            • Brian Kernighan's way
          • 4. 获取最高位 1 所在位置
            • 获取某个范围内最大的 2 的幂
          • 5. 获取最低位 1 所在位置
          • 6. 获取某个位置的比特位
          • 7. 判断两个位置的比特位是否一致
          • 8. 交换比特位
            • 交换单个比特位
            • 交换指定长度的比特位
          • 9. 反转比特序列
            • 原始方法
            • 分治法
          • 10. 查找数字
            • 仅一个元素出现一次,其余元素出现2次
            • 仅一个元素出现一次,其余元素出现 k 次
            • 恰好有两个元素只出现一次,其余所有元素均出现两次
          • 拓展阅读
        • 如何利用前端剪切板实现文件上传
        • 快速理解 JS 装饰器
        • 趣味js-只用特殊字符生成任意字符串
        • 重学 JS 原型链
        • 面试官问:怎么避免函数调用栈溢出
      • Rust

      • TypeScript

      • WebAssembly

    • 开发工具

    • 前端调试

    • 浏览器原理

    • 浏览器生态

  • 应用框架

  • 工程能力

  • 应用基础

  • 专业领域

  • 业务场景

  • 大前端
  • 前端基础
  • 编程语言
  • JavaScript
gahing
2020-02-16
目录

前端项目中常用的位操作技巧

# 前言

大部分例子引用自 Bit Twiddling Hacks (opens new window)

一些位操作优化的技巧在 js 中不一定能得到体现,本文仅展示一些自己在前端项目中用过且较常用的例子

某些例子比如交换,计算奇偶,计算最值的,使用位操作提升不大,但可读性变差了,本文不做记录

# 1. 检测两个整数是否异号

本处例子中,0 与正数相当

const isOppositeSign = (x, y) => (x ^ y) < 0
isOppositeSign(1, -2)  // true
isOppositeSign(1, 2)   // false
isOppositeSign(-0, -1) // true
1
2
3
4

# 2. 判断一个整数是否为 2 的幂

注意 0 不是 2 的幂,加上 !!num 判断

const isPowerOf2 = (num) => !!num && (num & (num - 1)) === 0
isPowerOf2 (0) // false
isPowerOf2 (1) // true
isPowerOf2 (3) // false
1
2
3
4

# 3. 计算无符号整数的比特位中有多少个 1

常用于统计一组开关中状态为开的个数

# 原始方法

对 num 不断右移,每次判断最后一位是否为1,循环次数为 num 的比特位个数

const countBits = (num) => {
  let res = 0
  for (; num; num >>= 1) {
    res += num & 1;
  }
  return res
}
countBits (4) // 1
1
2
3
4
5
6
7
8

# Brian Kernighan's way

每次清除一个最低的 1 的比特位,循环次数与 num 的比特位有多少个 1 相关

const countBits = (num) => {
  let c = 0;
  for (; num; c++) {
    num &= num - 1; // 清除最低比特位
  }
  return c
}
countBits (5) // 2
1
2
3
4
5
6
7
8

# 4. 获取最高位 1 所在位置

得到一个索引值

const getHighest1BitIndex = (num) => {
  let c = -1
  while (num) {
    num >>= 1
    c++
  }
  return c
}
1
2
3
4
5
6
7
8

同时可以用来计算一个整数以 2 为底的对数,其他拓展应用:

# 获取某个范围内最大的 2 的幂

const hignBit = (num) => {
  let index = getHighest1BitIndex(num)
  return 1 << index
}
hignBit(14) // 8
hignBit(8) // 8
1
2
3
4
5
6

另一种解法

const hignBit = (num) => {
  let res;
  while (num) {
    res = num
    num = num & (num - 1)
  }
  return res
}
hignBit(14) // 8
hignBit(8) // 8
1
2
3
4
5
6
7
8
9
10

# 5. 获取最低位 1 所在位置

利用 n & -n (又称 lowbit 函数)得到最低位 1 形成的数,然后不断右移得到索引值

const lowBit = (num) => {
  return num & -num
}
const getLowest1BitIndex = (num) => {
  let n = lowBit(num)
  // return getHighest1BitIndex(n)
  let c = -1
  while (n) {
    n >>= 1
    c++
  }
  return c
}
1
2
3
4
5
6
7
8
9
10
11
12
13

树状数组中经常用到 lowbit

# 6. 获取某个位置的比特位

const getBit = (num, i=0) => {
  // i 从 0 开始
  return (num >> i) & 1
}
getBit(5,1)
1
2
3
4
5

# 7. 判断两个位置的比特位是否一致

const getBit = (num, i = 0) => {
  return (num >> i) & 1
}
const judgeSameBit = (num, i, j) => {
  // const lo = getBit(num, i)
  // const hi = getBit(num, j)
  // return !(lo ^ hi)
  return !(((num >> i) ^ (num >> j)) & 1)
}
judgeSameBit(6, 0, 2) // false
judgeSameBit(6, 1, 2) // true
1
2
3
4
5
6
7
8
9
10
11

# 8. 交换比特位

# 交换单个比特位

原始序列为 S, 将第 i 个(从右往左数,起始索引为0) 与 第 j 个比特位进行交换

先判断两个位置比特位是否一致,得到一个异或的结果 x

将所有位置置 0,i、j 置为 x : (x << i) | (x << j) ,得到结果 res

最后将原始序列与 res 进行异或,即可实现交换比特位的效果

const swapBit = (num, i, j) => {
  let x = ((num >> i) ^ (num >> j)) & 1;
  return num ^ ((x << i) | (x << j));
}
swapBit(4, 0, 2) //1
1
2
3
4
5

# 交换指定长度的比特位

原始序列为 S, 将第 i 位(从右往左数,起始索引为0)开始,长度为 len 的序列与 第 j 位开始,长度为 len 的序列进行交换

举例,S = 00010111 i=0 j=4 len=3 交换后得到 01110001

const swapIndividualBits = (num, i, j, len) => {
  let x = ((num >> i) ^ (num >> j)) & ((1 << len) - 1);
  return num ^ ((x << i) | (x << j));
}
swapIndividualBits (23,0,4,3) //113 23=>0010111(2) 113=>01110001(2)
1
2
3
4
5

常用于移动一组开关的位置

# 9. 反转比特序列

由于 js 中不能指定一个数字类型的存储大小,这里需要手动指定一共有多少比特位

# 原始方法

头尾两两交换 O(n)

const swapBit = (num, i, j) => {
  let x = ((num >> i) ^ (num >> j)) & 1;
  return num ^ ((x << i) | (x << j));
}
const reverseBits = (num, n = 8) => {
  let len = n >> 1
  let res = num
  for (let i = 0; i < len; i++) {
    res = swapBit(res, i, n - i - 1)
  }
  return res
}
reverseBits(111) // 246  due to 01101111 => (0)11110110
1
2
3
4
5
6
7
8
9
10
11
12
13

# 分治法

仅适用于固定字节,在 js 中不太适用

# 10. 查找数字

# 仅一个元素出现一次,其余元素出现2次

var singleNumber = function(nums) {
  return nums.reduce((pre,cur)=>pre^cur)  
};
1
2
3

# 仅一个元素出现一次,其余元素出现 k 次

思路见 leetcode-137. 只出现一次的数字 II (opens new window)

代码如下:

var singleNumber = function (nums, k=3) {
  if (nums.length === 1) {
    return nums[0]
  }
  let base = 1
  let res = 0
  for (let i = 0; i < 32; i++) {
    let cnk = 0
    for (let j = 0; j < nums.length; j++) {
      cnk += nums[j] & base ? 1 : 0
    }
    if (cnk % 3 !== 0) {
      res |= base
    }
    base <<= 1
  }
  return res
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 恰好有两个元素只出现一次,其余所有元素均出现两次

思路见 leetcode-260. 只出现一次的数字 III (opens new window)

代码如下:

var singleNumber = function (nums) {
  if (nums.length === 2) {
    return nums
  }
  let res = nums.reduce((a, b) => a ^ b)
  // 取最低非0位,两个数在该位上 一个为0 一个为1,  
  // 进行 数组划分 该位为1的一组,该位为0的一组
  let tmp = res & -res
  let res1 = 0;
  let res2 = 0;
  for (let i = 0; i < nums.length; i++) {
    if (nums[i] & tmp) {
      res1 ^= nums[i]
    } else {
      res2 ^= nums[i]
    }
  }
  return [res1, res2]
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

未完待续...

# 拓展阅读

  1. Bit Twiddling Hacks (opens new window)
  2. Bit Hacks:关于一切位操作的魔法(上) (opens new window)
编辑 (opens new window)
#位运算#JavaScript
上次更新: 2024/09/01, 23:56:56
为什么 0.._ 等于 undefined
如何利用前端剪切板实现文件上传

← 为什么 0.._ 等于 undefined 如何利用前端剪切板实现文件上传→

最近更新
01
浅谈代码质量与量化指标
08-27
02
快速理解 JS 装饰器
08-26
03
Vue 项目中的 data-v-xxx 是怎么生成的
09-19
更多文章>
Theme by Vdoing | Copyright © 2016-2024 Gahing | 闽ICP备19024221号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式