Golang中的位操作技巧与实践

Golang中的位操作技巧与实践 Bit Hacking with Go - Learning the Go Programming Language - Medium

Bit Hacking with Go - Learning the Go Programming Language - Medium

在计算机的旧日美好时光里,内存昂贵,处理能力稀缺,直接进行位操作是首选方法…

阅读时间:8 分钟

func main() {
var x uint8 = 0xAC    // x = 10101100
x &= 0xF0             // x = 10100000
}

什么是 uint8? 什么是 0xAC? 10101100 …172? 什么是 &=? 什么是 0xF0? 10100000…160?

AND 和 & 是一样的吗?

https://play.golang.org/p/1OU2kehp5EW 输出中的数字是从哪里来的?

| 和 OR 是一样的吗?

什么是 MSB?

我不想停下!但我必须停下来了。回头见!


更多关于Golang中的位操作技巧与实践的实战教程也可以访问 https://www.itying.com/category-94-b0.html

20 回复

有趣。最高有效位是什么?

更多关于Golang中的位操作技巧与实践的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


func main() {
    fmt.Println("hello world")
}

但这意味着什么?

如果可能的话,请与我小学时学的乘法进行比较/对比

非常有趣!感谢指教!

那么,是那个额外的1让它变成负数的吗?

这个问题并非Go语言特有。这是一个关于位、二进制数及其运算的通用问题。

是的。我目前正在学习它(自己抽时间学,时间有限)。过程很慢。

cherilexvold1974:

010 1 (十进制 5) AND 001 1 (十进制 3) = 000 1 (十进制 1)"

5 x 3 怎么会等于 1?

这就像按位对它们进行乘法运算,正如引用的例子所示。

我说 -(MIN + 1) == MAX,这可以重写为 MIN == -MAX - 1,这基本上意味着,对于任何有符号整数,可表示的最小值等于可表示的最大值的负值减一……

有意思。我很想了解更多关于“可表示的最小值”和“可表示的最大值”的信息。

使用4位有符号数,可以表示从 7-8 的值。

有趣。

你已经从维基百科引用了它。

你有数字5和3,它们的二进制表示(4位)分别是 01010011

现在你逐位相乘这些二进制表示(0 * 0 = 01 * 0 = 00 * 1 = 0 以及 1 * 1 = 1)。然后将它们组合回一个二进制数 0001,其十进制表示为 1

cherilexvold1974:

什么是最有效位?

https://en.wikipedia.org/wiki/Bit_numbering#Most_significant_bit


现在,请尝试理解这一点,并找出是哪一位负责标记符号。

hollowaykeanho:

“嘿,让我们使用最高有效位(在我们的例子中是 0b10000000)作为符号标志”。这也意味着一个8位所能表示的有效数字现在只剩下7位,使得有符号数的范围为:

0b11111111 == -127
0b10000000 == 0b00000000 = 0
0b01111111 == 127

让我看看我是否正确理解了你的意思。

“二进制补码”基本上意味着对每一位取反然后加 1

0 -> 000 -> 111

我在这里停一下。所以在 000 中,你对每个 0 取反并加 1,因此得到 111?

我不理解第四列。

我想我理解了 1 的前三列。

我不理解 2 的第三列。不应该是 011 吗?

-4 -> 100 -> 011 -> 100 // 警告!4 在 3 位有符号表示中是不可表示的!

为什么会这样?

我不理解 -2 和 -2 的第三列。

很抱歉我的知识如此欠缺。这背后有原因。感谢你的耐心。

cherilexvold1974:

What is == MAX?

这只是等式的一半。

我说的是 -(MIN + 1) == MAX,它可以重写为 MIN == -MAX - 1,这基本上意味着,对于任何有符号整数,可表示的最小值等于可表示的最大值的负数减一……

cherilexvold1974:

Is there a way to differentiate 4 from -4 ?

嗯,如果你的有符号整数位数超过3位,那么你当然可以表示 4,因此可以轻松地将 -4 与之区分开来。我们考虑4位有符号整数:

 4 -> 0100 -> 1011 -> 1100
-4 -> 1100 -> 0011 -> 0100

使用4位有符号整数,你可以表示从 7-8 的值。

“取反”一个比特位意味着将 0 变为 1,反之亦然,并且对每一位都进行此操作。

谢谢 🙏 非常有帮助。

不,我是将每个 0 取反为 1,这样我得到 111然后 我加上 1,由于进位和溢出,结果得到 000

明白了 😊 酷。

当我真正理解时,我感到非常开心。

我仔细查看了图表,现在理解了。

因为,正如你从表中看到的,4-4 具有相同的表示形式。这就是为什么在任何有符号表示中 -(MIN + 1) == MAX

不用担心最后一个问题,因为你所说的已经回答了那个问题。

出于某种原因,我还没完全理解这一点。我确实看到 4 和 -4 有相同的表示形式。我知道有符号表示是什么。== MAX 是什么意思?有没有办法区分 4 和 -4?

Go 编程语言规范 - Go 编程语言

在查看您这里的回答时,我点开了这个链接。

我看到了这样一段陈述:“一个 n 位整数的值是 n 位宽,并使用二进制补码算术表示。”

然后我点开了这个链接:二进制补码算术

其中包含了这个图表:

三位有符号整数 十进制值 二进制(补码表示) 补码 (2³ − n) 0 000 000 1 001 111 2 010 110 3 011 101 −4 100 100 −3 101 011 −2 110 010 −1 111 001

这里有点不清楚。您可能需要点开链接看看。 我不理解负数的表示方式,也不理解补码表示。

“二进制补码”基本上意味着将每一位取反,然后加 1

以下将再次假设为3位:

十进制 -> 二进制表示 -> 所有位取反 -> 加一(等同于乘以十进制的-1)
 0 -> 000 -> 111 -> 000
 1 -> 001 -> 110 -> 111
 2 -> 010 -> 101 -> 110
 3 -> 011 -> 100 -> 101
-4 -> 100 -> 011 -> 100 // 注意!4无法用3位有符号数表示!
-3 -> 101 -> 010 -> 011
-2 -> 110 -> 001 -> 010
-1 -> 111 -> 000 -> 001

(2^m - n)_2 这种表示法让很多没有数学背景的人感到困惑,因此在我的计算机科学学位第一学期甚至没有涉及,直到第三或第四学期才粗略地提到,尽管二进制补码作为基础内容在第一学期就已经介绍了。

它是以我在此解释的两步方式引入的。对于至少理解二进制数和单比特取反基础知识的人来说,这通常是普遍可以理解的。

为了准确回答您的问题:

uint8 在 Go 语言规范中定义于此:https://golang.org/ref/spec#Numeric_types

0xAC 是一个十六进制数字字面量,同样在语言规范中定义于此:https://golang.org/ref/spec#Integer_literals

&= 是一个赋值运算符,在规范中定义于此:https://golang.org/ref/spec#Assignments

单个与符号 & 对其操作数执行按位与操作,在算术运算符部分定义:https://golang.org/ref/spec#Arithmetic_operators

按位或运算符 | 在同一部分定义。

综上所述,我建议您查阅维基百科以获取更多解释:https://en.wikipedia.org/wiki/Bitwise_operation。我经常使用维基百科和 YouTube 来熟悉新概念,因为这些平台通常有图表可以直观地演示正在发生的事情。

最后,关于您提出的示例中数字来源的问题,在我回答之前,您能先说明一下示例中哪部分对您来说不清楚吗?

cherilexvold1974:

我很想了解更多关于“最小可表示值”和“最大可表示值”的信息。

具体是指什么?

核心在于,如果你拥有的数字位数是有限的,那么你能表示的值也是有限的。这就像你的汽车里程表一样。你有6位数字来表示里程,从0开始,最大值将是999999。之后,它会重新归零。二进制数也是类似的。唯一的例外是,我们只有0和1作为数字,而不是0到9。

另一件事是,我们没有 - 号来表示一个值的负性。但一些工程师们约定,在有符号整数类型中,将最高有效位作为“负号”。他们还约定使用二进制补码,而不是简单地反转每一位,因为使用二进制补码可以直接进行正负数的加法运算:

让我们用3位有符号整数计算 `-2 + 3`

十进制    二进制补码    普通反转
 -2       110           101
+ 3       011           011
===       ===           ===
  1     (1)001        (1)000

如你所见,在二进制补码表示法中,我们自然地得到了 1 的二进制表示,而在普通反转法中,我们需要跟踪我们越过了 0 的边界这一事实,并且必须手动再加一个 1 来修正结果。

“取反”一个比特位意味着将 0 变为 1,反之亦然,并且对每一位都进行此操作。所以这还和加法没有任何关系,它只是构建二进制补码的两个步骤中的第一步。只有在所有位都翻转之后,你才加上 1,并处理进位。

cherilexvold1974: 我在这里停一下。那么在 000 中,你对每个 0 取反并加 1,因此得到 111?

不,我将每个 0 取反为 1,这样我得到 111然后我加上 1,由于进位和溢出,结果得到 000

cherilexvold1974: 我不明白 -2 的第三列。不应该是 011 吗?

不,那只是直接 一,而没有先对所有位取反。

cherilexvold1974: -4 -> 100 -> 011 -> 100 // 警告!4 无法用 3 位有符号数表示! 为什么?

因为,正如你从表中看到的,4-4 有相同的表示形式。这就是为什么在任何有符号表示中 -(MIN + 1) == MAX

cherilexvold1974: 我不明白 -2 和 -2 的第三列

我想你指的是 -22 吧?

你具体哪里不明白?还是因为你误解了“对每位取反”的意思?或者是其他原因?

uint8 在 Go 语言规范中定义于此:https://golang.org/ref/spec#Numeric_types

感谢为我指明了正确的方向!我点击了链接并将其保存到文件中。我之前读过它,但这次我理解得更好了。

在十六进制字面量中,字母 a-fA-F 代表值 10 到 15……哦。我之前接触过这个,但它对我来说太新了,以至于我没有认出来。

https://golang.org/ref/spec#Assignments 这对我来说简直是天书。我的老师教过 & 是指向一个整数的指针,它显示一个内存地址。我的理解非常基础。所以我不知道 &= 是什么意思。

单个的与符号 &,对其操作数执行按位与运算,定义在算术运算符下:https://golang.org/ref/spec#Arithmetic_operators 开始明白了。按位的概念对我来说是新的。

感谢提供算术运算符的链接。我实际上一直在找这个。我不知道我怎么错过了它。

正在阅读这个:https://en.wikipedia.org/wiki/Bitwise_operation。 “在简单的低成本处理器上,通常,按位运算比除法快得多,比乘法快几倍,有时甚至比加法快得多。” 这很酷。难怪人们会使用它。 我明天继续(希望如此)

从维基百科的文章中……"按位与是一种二元运算,它接受两个等长的二进制表示,并对每一对相应的位执行逻辑与操作,这相当于将它们相乘。因此,如果比较位置的两个位都是 1,则结果二进制表示中的该位是 1 (1 × 1 = 1);否则,结果为 0 (1 × 0 = 0 和 0 × 0 = 0)。例如:

010 1 (十进制 5) AND 001 1 (十进制 3) = 000 1 (十进制 1)"

5 x 3 怎么会等于 1?

位操作核心概念解析

1. 基础类型与表示

var x uint8 = 0xAC  // uint8是无符号8位整数,范围0-255
// 0xAC是十六进制表示:
// A = 10(十进制) = 1010(二进制)
// C = 12(十进制) = 1100(二进制)
// 0xAC = 10101100(二进制) = 172(十进制)

2. 位运算符详解

// & 是按位AND运算符
x &= 0xF0  // 等价于 x = x & 0xF0
// 0xF0 = 11110000(二进制) = 240(十进制)

// 计算过程:
//   10101100 (x = 0xAC)
// & 11110000 (0xF0)
//   ---------
//   10100000 = 0xA0 = 160(十进制)

3. 完整示例演示

package main

import "fmt"

func main() {
    var x uint8 = 0xAC  // 10101100
    
    fmt.Printf("原始值: %08b = 0x%X = %d\n", x, x, x)
    
    // AND操作
    x &= 0xF0
    fmt.Printf("AND 0xF0后: %08b = 0x%X = %d\n", x, x, x)
    
    // OR操作示例
    var y uint8 = 0x0A  // 00001010
    y |= 0xF0           // OR操作
    fmt.Printf("OR 0xF0后: %08b = 0x%X = %d\n", y, y, y)
    
    // XOR操作示例
    var z uint8 = 0xAA  // 10101010
    z ^= 0xFF           // XOR操作
    fmt.Printf("XOR 0xFF后: %08b = 0x%X = %d\n", z, z, z)
}

4. 常用位操作技巧

// 1. 检查特定位是否设置
func isBitSet(num, pos uint8) bool {
    return (num & (1 << pos)) != 0
}

// 2. 设置特定位
func setBit(num, pos uint8) uint8 {
    return num | (1 << pos)
}

// 3. 清除特定位
func clearBit(num, pos uint8) uint8 {
    return num & ^(1 << pos)
}

// 4. 切换特定位
func toggleBit(num, pos uint8) uint8 {
    return num ^ (1 << pos)
}

// 5. 提取低位字节
func getLowByte(value uint16) uint8 {
    return uint8(value & 0xFF)
}

// 6. 判断奇偶(最低位检查)
func isEven(num uint8) bool {
    return num&1 == 0
}

5. 实际应用场景

// 标志位管理
const (
    FlagRead    = 1 << iota  // 00000001
    FlagWrite                // 00000010  
    FlagExecute              // 00000100
)

func main() {
    var permissions uint8 = 0
    
    // 设置权限
    permissions |= FlagRead | FlagWrite  // 00000011
    
    // 检查权限
    canRead := permissions&FlagRead != 0  // true
    canExecute := permissions&FlagExecute != 0  // false
    
    // 移除权限
    permissions &^= FlagWrite  // 清除写权限
    
    fmt.Printf("权限位: %08b\n", permissions)
}

6. MSB(最高有效位)操作

// 获取MSB(第7位,从0开始计数)
func getMSB(num uint8) uint8 {
    return (num >> 7) & 1
}

// 检查是否为负数(对于有符号数)
func isNegative(num int8) bool {
    return num&0x80 != 0  // 检查符号位
}

7. 性能优化示例

// 使用位操作实现快速乘除
func multiplyByPowerOfTwo(num, power uint) uint {
    return num << power  // 相当于 num * 2^power
}

func divideByPowerOfTwo(num, power uint) uint {
    return num >> power  // 相当于 num / 2^power
}

// 交换两个变量(不使用临时变量)
func swap(a, b *int) {
    *a ^= *b
    *b ^= *a
    *a ^= *b
}

Go中的位操作直接对应CPU指令,执行效率高。&对应AND,|对应OR,^对应XOR,&^对应AND NOT。这些操作在底层编程、协议解析、性能优化等场景中非常实用。

回到顶部