进制转换:多项式换底

任何进制表示都是同一个数的不同基底多项式展开。十进制 42 = 4×10¹ + 2×10⁰,二进制 101010 = 1×2⁵ + 0×2⁴ + 1×2³ + 0×2² + 1×2¹ + 0×2⁰转换本质是换基底,算法是短除法(十→二)和累加(二→十)。

package main

import "fmt"

// 十进制转任意进制(不用标准库)
func toBase(n, base int) string {
    if n == 0 {
        return "0"
    }
    digits := "0123456789ABCDEF"
    result := []
byte{}
    for n > 0 {
        result = append([]byte{digits[n%base]}, result...)
        n /= base
    }
    return string(result)
}

// 任意进制转十进制
func fromBase(s string, base int) int {
    result := 0
    for _, ch := range s {
        d := int(ch - '0')
        if ch >= 'A' {
            d = int(ch-'A') + 10
        }
        result = result*base + d
    }
    return result
}

func main() {
    fmt.Println(toBase(42, 2))   // 101010
    fmt.Println(toBase(255, 16)) // FF
    fmt.Println(fromBase("101010", 2))  // 42
    fmt.Printf("%b %o %x\n", 42, 42, 42) // Go 内置:101010 52 2a
}

位运算六式

运算符号数学含义经典用途
AND&按位乘,两个都是 1 才为 1取低位,检查标志位
OR|按位加,有一个是 1 就为 1设置标志位
XOR^相同为 0,不同为 1翻转位,不用临时变量交换
NOT^x按位取反位掩码取反
SHL<<乘以 2 的幂快速乘以 2^n
SHR>>除以 2 的幂(取整)快速除以 2^n
口诀AND 取交集,OR 并集,XOR 差集,移位代替乘除。
package main

import "fmt"

func main() {
    // 快速乘除
    x := 100
    fmt.Println(x << 3)  // x * 8 = 800
    fmt.Println(x >> 2)  // x / 4 = 25

    // 判断奇偶(比 % 快)
    for _, n := range []int{3, 4, 7, 8} {
        fmt.Printf("%d 是%s\n", n, map[bool]string{true: "奇数", false: "偶数"}[n&1 == 1])
    }

    // 不用临时变量交换
    a, b := 0xAA, 0xBB
    a ^= b; b ^= a; a ^= b
    fmt.Printf("a=%X b=%X\n", a, b)  // a=BB b=AA

    // 取最低位的 1
    n := 0b10110100
    lowestBit := n & (-n)  // 0b00000100
    fmt.Printf("%08b\n", lowestBit)

    // 消去最低位的 1(Brian Kernighan trick)
    fmt.Printf("%08b\n", n & (n-1))  // 0b10110000
}

位掩码:用整数表达集合

一个 uint64 可以表示 64 个布尔值的集合,集合操作全部是位运算,时间复杂度 O(1)。这是权限系统、特性开关、状态机的标准实现方式。

package main

import "fmt"

// 权限系统:用位掩码
const (
    PermRead    = 1 << iota  // 0001
    PermWrite                // 0010
    PermExecute              // 0100
    PermAdmin                // 1000
)

func hasPermission(userPerm, required uint) bool {
    return userPerm&required == required
}

func main() {
    // 普通用户:读+写
    user := uint(PermRead | PermWrite)
    fmt.Printf("用户权限: %04b\n", user)

    // 检查权限
    fmt.Println(hasPermission(user, PermRead))    // true
    fmt.Println(hasPermission(user, PermAdmin))   // false

    // 添加权限
    user |= PermExecute
    fmt.Printf("添加执行后: %04b\n", user)  // 0111

    // 移除权限
    user &^= PermWrite  // 位清除(AND NOT)
    fmt.Printf("移除写后: %04b\n", user)    // 0101
}

Brian Kernighan 数 1 的个数

package main

import (
    "fmt"
    "math/bits"
)

// 手写:O(k) k 是 1 的个数
func countOnes(n int) int {
    count := 0
    for n != 0 {
        n &= n - 1  // 消去最低位的 1
        count++
    }
    return count
}

func main() {
    fmt.Println(countOnes(0b10110100))  // 4
    fmt.Println(bits.OnesCount(0b10110100)) // 4(标准库,编译为单条 POPCNT 指令)

    // 判断 2 的幂次
    isPowerOf2 := func(n int) bool { return n > 0 && n&(n-1) == 0 }
    fmt.Println(isPowerOf2(16))  // true
    fmt.Println(isPowerOf2(18))  // false
}
推荐做法
  • 权限和状态标志用位掩码而非多个 bool 字段
  • 乘除 2 的幂次用移位运算
  • bits.OnesCountbits.Len 等用标准库(编译为 CPU 指令)
不推荐
  • 在热路径之外为了"优化"而强行用位运算——可读性优先
  • 用 XOR 交换变量——现代编译器能优化 tmp 写法,且更易读
  • 忘记 Go 的 ^x 是取反(而不是 ~x
常见误区
  • 位移量超过类型宽度是未定义行为(Go 中结果为 0,但别依赖这个)

判断标准:能手写 countOnes 并解释 n&(n-1) 的含义 → 掌握本章。

位运算是计算机最原始的语言,掌握它,你就能和 CPU 直接对话。

— The Art of Computer Programming