Skip to content

38.位运算

位操作符

& 与

都是1则为1

js
const a = 5;        // 00000000000000000000000000000101
const b = 3;        // 00000000000000000000000000000011

console.log(a & b); // 00000000000000000000000000000001
// 1

// 是否2的n次幂 
// (x & x - 1) === 0
console.log((4 & 2 - 1) === 0) // true

// 奇偶
// x & 1 === 0 偶数

// x & 1 === 1 奇数
console.log(2 & 1 === 0) // 0
// 求平均值,防溢出
function avg(x, y){
	return (x & y) + ((x ^ y) >> 1);
}

// 取模
// i % 4 === i & (4 - 1)
console.log(1%4 , 1&3) // 1 1

| 或

又一个是1就是1

js
const a = 5;        // 00000000000000000000000000000101
const b = 3;        // 00000000000000000000000000000011

console.log(a | b); // 00000000000000000000000000000111
// 7

^ 异或

不相同才为1

js
const a = 5;        // 00000000000000000000000000000101
const b = 3;        // 00000000000000000000000000000011

console.log(a ^ b); // 00000000000000000000000000000110
// 6

// 判断赋值
if(x === a){
	x = b
}else{
	x = a
}
// 等价于下面
x = a ^ b ^ x

~ 非

包括符号位取反, 包括符号位

js
const a = 5;     // 0000 0000 0000 0000 0000 0000 0000 0101
const b = -3;    // 1000 0000 0000 0000 0000 0000 0000 0011

// 5 取反
// 原   0000 0000 0000 0000 0000 0000 0000 0101
// 取反 1111 1111 1111 1111 1111 1111 1111 1010
// -6 补码转原码
// 补 1111 1111 1111 1111 1111 1111 1111 1010
// 反 1000 0000 0000 0000 0000 0000 0000 0101
// 原 1000 0000 0000 0000 0000 0000 0000 0110
console.log(~a); // 10000000000000000000000000000110
// -6
console.log(~b); // 00000000000000000000000000000010
// 2

// 取负数
console.log(~4 + 1) // -4
// 舍弃小数
console.log(~~1.5) // 1

<< 左移

x << y ===> x * 2 ^ y

js
// x * 2 ** y 
// x舍弃小数位,向整数位进1

// 9 * (2 ** 2) = 9 * (4) = 36
console.log(9 << 2) // 36

// 9 * (2 ** 3) = 9 * (8) = 72
console.log(9 << 3) // 72

>> 右移

x >> y ===> x / 2 ^ y

js
// x / 2 ** y 
// 舍弃小数位,负数向整数位进1,正数不进位

// -9 / (2 ** 2) = -9 / (4) = Math.floor(-2.25) = -3
console.log(-9 >> 2) // -3

// 9 / (2 ** 2) = 9 / (4) = Math.trunc(2.25) = 2
console.log(9 >> 2) // 2

// -9 / (2 ** 3) = -9 / (8) = -1.125
console.log(-9 >> 3) // -2

>>>(无符号右移)

作数向右位移,右位移出的数丢弃,左侧用 0 填充,因为用 0 填充,所以总是"按位非",负数将变成正数。

js
const a = 5;          //  00000000000000000000000000000101
const b = 2;          //  00000000000000000000000000000010
const c = -5;         // -00000000000000000000000000000101
											// -5 补码 10000000000000000000000000000101

console.log(a >>> b); //  00000000000000000000000000000001
// expected output: 1

console.log(c >>> b); //  00111111111111111111111111111110
// expected output: 1073741822

二进制

无符号数字:计算机保存的是最原始数字,也就是没有正数和负数之分,

取整

左移、右移都可以取整

原码

10 进制:5 正数:0101 负数:1101 左边第一位表示符号,1 是负数,0 代表正数

(+1) + (-1)= 0001 + 1001 = 1010 === -2 这个结果是不对的 未解决正负相加=0 问题,发明了反码

反码

正数反码=原码 负数反码=符号位不变,其他位置取反

补码

正数补码 = 原码 负数的补码 = 反码的末位加 1 去掉最高进位 负数 1101 = 1010+1 = 1011

0000 0000 0000 0000 0000 0000 0000 1101

-5 = 1111 1111 1111 1111 1111 1111 1111 1011

根据补码求原码

求补码 1001 即十进制 -7 的原码

补码求其补码 1001 => 1111 = -7

安全整数

安全整数可以使用 JS 的内置方法 Number.isSafeInteger() 来验证

子节

位和子节:(1 byte = 8 bit)8个二进制位(Bit)构成一个子节单元(Byte) 8 bit 存储范围是 0~255 无符号数值范围 0~255 有符号数值范围(符号占1位,1表示负数,0表示正数) -128~127 子节和文字:一个字节可以存储一个英文字母或半个汉字

十六进制转二进制,1位十六进制对应4 bit二进制, 1个 Unicode 字符由4位十六进制组成。所以 Unicode 都需要 2个字节(Byte)

在基本使用统一的字符集 Unicode ,规定的是字符的十六进制,基本常用字符的在Plane 0(0000–FFFF)里面, 如英文 A 字母 U+0041 , 汉字 范围是 U+4E00 ~ U+9FA5 , 是 4个十六进制数即可表示一个字符 。

字符占用两个子节

可以看到 Unicode 不管英文、汉字都是需要16 bit来存储,也就是2 byte。大家看到 A 的是 0041 ,高位字节 0 其实没有作用,在传输、存储时可以省略。那如何省略,变成1个字节?这时候就出现编码方式,就是 UTF-8 、 GBK 等,通过编码压缩长度。

一个字节8位,将其转成二进制,就是32位 0001 0010 0011 0100 0101 0110 0111 1000

汉字占两个子节(Byte)

上图 UTF-8 编码方式: 数字、英文是1个字节,汉字是3个字节。 而 GBK 编码方式:数字、英文是1个字节,汉字是2个字节,1个字节范围 00–7F 。 MySQL 4.0 以下 varchar(20) 是指20个字节,可以存储数字英文20个,utf-8汉字6个,在MySQL 5.0 及以上 varchar(20) 是指20个字符,可以存储数字英文汉字都是20个。

子节序

举个例子:十六进制 0x12345678 存储,内存最小的单位一个字节,一个字节8位,将其转成二进制 0001 0010 0011 0100 0101 0110 0111 1000 就是32位,就是4个字节,所以分为 0x12 、 0x34 、 0x56 、 0x78 (只是为了表示是十六进制所以写成 0x12 ,实际是 12 存储是8 bits)4个字节存储。但是存储网络传输时是先从 0x12 开始传,还是 0x78 开始传?所以多字节出现才有字节序。

js
// 十六进制 0x12345678
// 十进制 305419896
// 二进制 0b00010010001101000101011001111000

// 0001 0010 0011 0100 0101 0110 0111 1000
console.log(0b0001, 0b0010) // 1 2

记忆点

  • 基数 R,R 进制

  • 左移乘,右移除

  • 左右移都可去整

  • 原码:10进制5 正数:0101,负数 1101

  • 正数:反码补码都是原码(自己)

  • 反码:负数反码符号为不变其他位置取反 1110

  • 补码:先计算反码,然后末尾 + 1 去掉最高位 1111

  • 反码是解决:正负相加等于 0 的问题 // (+1) + (-1) = 0001+ 1001 = 1010 === -2 // 0001 + (-1)= 0001 + 1111 = 10000 = -0(反码)

  • 补码是解决:怎么表示+0 和-0的问题

  • 数据在内存中是在补码上存储,方便换算

  • 补码反求源码:🤔️

  • 安全整数:Number.isSafeInteger(2**53-1) true

js
export const enum PatchFlags {
  // 代表元素的 text 会变化 1
  TEXT = 1,
  // 代表元素的 class 会变化 10
  CLASS = 1 << 1,
  // 代表元素的 style 会变化 100
  STYLE = 1 << 2,
  // 代表元素的 props 会变化 1000
  PROPS = 1 << 3,
  // ...
}

function patchElement(n1, n2){
    if(n2.patchFlag > 0){
        // 有 PatchFlag,只需要更新动态部分
        if (patchFlag & PatchFlags.TEXT) {
            // 更新 text
        }
        
        if (patchFlag & PatchFlags.CLASS) {
            // 更新 class
        }
        
        if (patchFlag & PatchFlags.PROPS) {
            // 更新 props
        }
        ...
        
    } else {
        // 没有 PatchFlag,全量比对并更新
    }
}

应用

用户权限 | &

js
enum Permission {
 Read = 1, // 0001
 Write = 2, // 0010
 Execute = 4, // 0100
 Delete = 8 // 1000
}

let userPermission: Permission = Permission.Read | Permission.Write; // 用户权限:读、写

function hasPermission(permission: Permission, checkPermission: Permission): boolean {
 return (permission & checkPermission) === checkPermission;
}

console.log(hasPermission(userPermission, Permission.Read)); // 输出: true
console.log(hasPermission(userPermission, Permission.Execute)); // 输出: false

状态管理

| 添加状态 & 状态是否存在

  • 状态权限的定义
js
const MANAGE = 1
const SPREAD = 1 << 1
const SETTING = 1 << 2
const USETESTAPP = 1 << 3
const DEVELOP = 1 << 4
const USEDEVELOPAPP = 1 << 5
const VIEWSTATISTICS = 1 << 6
java
public class OrderStatus {
    // int是四个字节,有32个状态位。新增状态在对应的位上定义就行了。
    private static final int CREATED = 1;   // 0001
    private static final int PAID = 2;      // 0010
    private static final int SHIPPED = 4;   // 0100
    private static final int COMPLETED = 8// 1000

    private int status;

    public OrderStatus() {
        this.status = CREATED; // 默认状态为已创建
    }

    // 添加状态
    public void addStatus(int status) {
        this.status |= status;
    }

    // 移除状态
    public void removeStatus(int status) {
        this.status &= ~status;
    }

    // 检查是否有特定状态
    public boolean hasStatus(int status) {
        return (this.status & status) == status;
    }

    // 示例输出
    public static void main(String[] args) {
        OrderStatus orderStatus = new OrderStatus();

        System.out.println("-------订单已支付-----------");
        // 假设订单已支付
        orderStatus.addStatus(PAID);
        System.out.println("创建订单是否创建 " + orderStatus.hasStatus(CREATED));
        System.out.println("创建订单是否支付 " + orderStatus.hasStatus(PAID));

        // 假设订单已发货
        System.out.println("-------订单已发货-----------");
        orderStatus.addStatus(SHIPPED);
        System.out.println("创建订单是否发货 " + orderStatus.hasStatus(SHIPPED));

        // 假设订单已完成
        System.out.println("-------假设订单已完成-----------");
        orderStatus.addStatus(COMPLETED);
        System.out.println("创建订单是否完成 " + orderStatus.hasStatus(COMPLETED));
    }
}

奇偶数判断

js
console.log(2 & 1)    // 0
console.log(3 & 1)    // 1

取整

js
console.log(6.83 >> 0)  // 6
console.log(6.83 << 0)  // 6
console.log(6.83 | 0)   // 6
console.log(~~ 6.83)    // 6
// >>>不可对负数取整
console.log(6.83 >>> 0)   // 6

在JavaScript中,可以使用位运算符进行判断一个数字是否超出了32位有符号整数的范围。通常情况下,当一个数字超出了32位范围时,进行位运算的结果会出现不准确的情况。 在32位有符号整数的范围内,最大的正数是 2^31 - 1,即 2147483647;而最小的负数是 -2^31,即 -2147483648。 以下是一个示例,演示如何判断一个数字是否超出了32位范围:

javascript
function isOutOfRange(num) {
    return num !== (num | 0);
}

let num = 1234567891;
let shiftedNum = num << 1; // 进行位左移操作
console.log(shiftedNum); // 输出: 2469135782

if (isOutOfRange(shiftedNum)) {
    console.log("数字超出了32位范围");
} else {
    console.log("数字未超出32位范围");
}

在 JavaScript 中,数字类型是基于双精度浮点数表示的,这意味着它们是64位的。每个数字都以64位双精度浮点数的形式存储。这包括了整数和浮点数。 然而,在某些情况下,比如进行位运算时,JavaScript 会将数字自动转换为32位有符号整数。这是因为位运算是针对整数的,而JavaScript中的整数通常会被限制为32位以确保操作的正确性。 所以,虽然数字是以64位双精度浮点数的形式存储,但在某些操作中(例如位运算),JavaScript会将其转换为32位有符号整数。

交换

js
// 交换变量值
let a = 1;
let b = 2;
a = a^b;
b = a^b;
a = a^b;
console.log(a, b) // 2 1

颜色值转化

js
function hexToRGB(hex) {
    let hexx = hex.replace('#', '0x')
    let r = hexx >> 16
    let g = hexx >> 8 & 0xff
    let b = hexx & 0xff
    return `rgb(${r}, ${g}, ${b})`
}

function RGBToHex(rgb) {
    let rgbArr = rgb.split(/[^\d]+/)
    let color = rgbArr[1]<<16 | rgbArr[2]<<8 | rgbArr[3]
    return '#'+ color.toString(16)
}

hexToRGB('#ffffff')    
RGBToHex('rgb(255,255,255)')

在 MIT 许可下发布