go中的算术移位

使用代码统计给定整型值中 1 的个数时,对于负数,发现程序会陷入死循环。思考后发现,go 语言中进行算术移位时,如果操作数为负数,移位后结果最终会停留到-1

问题复现

使用代码统计给定整型值中 1 的个数时,陷入死循环的代码:

for xor != 0 {
	xor >>= 1
	if xor&1 == 0 {
		count++
	}
}

问题剖析

go 中整型数的移位采用算术移位。关于逻辑移位与算术移位,对比如下:

左移低位补位(不溢出前提下) 右移高位补位
算术移位 补 0 补 0
逻辑移位 低位补 0 符号位不变,使用符号位补最高位

根据补位规则,当操作数为负数时,最终值为-1,所以代码会陷入死循环。

更通用的判断方法

统计数字比特位中 1 个数时,以下代码更加通用:

for xor != 0 {
	xor &= xor - 1
	count++
}

代码中xor &= xor - 1可去除xor中的最低位1比特位。其原理如下:

补码的提出,就是为了将+-操作都统一转换为+操作以方便计算机进行计算。所以将代码中xor - 1转换为xor + (-1)-1在机器中的补码存储形式为0x1111....1111。假设 xor 形式为0x.... 0100xor + (-1)结果为0x.... 0011。最低位1左边其余比特位均保持不变,右边比特位均变为0。将xorxor + (-1)结果异或,则可将最低位1去除。

该方法通过避免使用移位操作,避免了算术移位过程中引入的多余符号位,进而避免程序陷入死循环。

参考

算术移位和逻辑移位详解