38.位运算
位操作符
& 与
都是1则为1
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
const a = 5; // 00000000000000000000000000000101
const b = 3; // 00000000000000000000000000000011
console.log(a | b); // 00000000000000000000000000000111
// 7
^ 异或
不相同才为1
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
~ 非
包括符号位取反, 包括符号位
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
// 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
// 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 填充,所以总是"按位非",负数将变成正数。
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 开始传?所以多字节出现才有字节序。
// 十六进制 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
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,全量比对并更新
}
}
应用
用户权限 | &
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
状态管理
| 添加状态 & 状态是否存在
- 状态权限的定义
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
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));
}
}
奇偶数判断
console.log(2 & 1) // 0
console.log(3 & 1) // 1
取整
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位范围:
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位有符号整数。
交换
// 交换变量值
let a = 1;
let b = 2;
a = a^b;
b = a^b;
a = a^b;
console.log(a, b) // 2 1
颜色值转化
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)')