在Go语言的数值计算场景中,当需要处理远超int64、uint64范围的整数时,标准库的math/big包下的big.Int类型是首选方案。部分业务场景下需要统计big.Int二进制表示中值为1的位的数量,也就是设置位数BitCount,下面介绍具体的实现方法。

big.Int的基础特性
big.Int的内部实现采用变长的字数组存储数值,每个字通常是平台相关的无符号整数类型,默认是uint类型。它支持任意精度的整数运算,包括加减乘除、位运算等,但是标准库的big.Int没有直接提供BitCount方法,需要开发者自行实现。
计算BitCount的常用方法
方法一:逐位检查法
这种方法的核心思路是遍历big.Int的每一个二进制位,判断该位是否为1,累计计数。可以通过big.Int的Bit(i int) uint方法获取第i位的值,该方法返回0或1。
package main
import (
"fmt"
"math/big"
)
// 逐位检查法计算big.Int的BitCount
func bitCountByCheck(b *big.Int) int {
if b.Sign() == 0 {
return 0
}
count := 0
// 获取二进制表示的长度,避免无效遍历
bitLen := b.BitLen()
for i := 0; i < bitLen; i++ {
if b.Bit(i) == 1 {
count++
}
}
// 处理负数的情况,big.Int的Bit方法返回的是绝对值的位,负数需要额外判断符号位
if b.Sign() < 0 {
// 无穷多个前导1,实际计算时可以根据需求调整,这里仅统计绝对值的1的个数
// 如果需要统计补码形式的1的个数,需要结合具体位宽处理
}
return count
}
func main() {
num := big.NewInt(123) // 123的二进制是1111011,有6个1
fmt.Println(bitCountByCheck(num)) // 输出6
}
方法二:字数组遍历法
big.Int内部存储的字数组可以直接访问,通过遍历每个字,统计每个字中1的个数,再累加得到总结果,这种方式比逐位检查效率更高,因为减少了方法调用和循环次数。
package main
import (
"fmt"
"math/big"
)
// 统计单个uint类型的1的个数,使用查表法提升效率
var byteBitCount = [256]int{
0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4,
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8,
}
func countUintBits(n uint) int {
count := 0
for n > 0 {
count += byteBitCount[n&0xff]
n >>= 8
}
return count
}
// 字数组遍历法计算big.Int的BitCount
func bitCountByWord(b *big.Int) int {
if b.Sign() == 0 {
return 0
}
count := 0
words := b.Bits()
for _, w := range words {
count += countUintBits(uint(w))
}
return count
}
func main() {
num := big.NewInt(123)
fmt.Println(bitCountByWord(num)) // 输出6
}
两种方法的对比
| 方法 | 实现复杂度 | 执行效率 | 适用场景 |
|---|---|---|---|
| 逐位检查法 | 低 | 较低 | 数值较小、对性能要求不高的场景 |
| 字数组遍历法 | 中等 | 较高 | 大数值、高频调用的性能敏感场景 |
注意事项
- big.Int的
Bit(i int)方法返回的是数值绝对值的第i位,对于负数,默认统计的是绝对值的1的个数,如果需要统计补码形式的1的个数,需要结合具体的位宽做额外处理。 - 当big.Int的值为0时,BitCount结果为0,实现时需要先判断数值的符号,避免无效的遍历。
- 字数组遍历法中,统计单个字的1的个数可以根据平台调整实现,比如64位平台可以优先处理8字节的字,进一步提升效率。
实际开发中可以根据业务需求选择合适的方法,普通场景下逐位检查法已经足够使用,性能敏感场景建议采用字数组遍历结合查表法的实现。