整数的二进制表示方法

173 阅读4分钟

问题引入

很久没有巩固计算机的基础理论知识了,今天突然想起一道编程题:如何将一个整数转换成二进制。看这道题的第一眼,觉得这可太简单了。用这个整数对2取余并循环除以2,直到最后循环的数等于0,把取余的结果拼接起来即可。

上面的方法我估计大部分人都想得出来,但考虑不全。如果加一个条件,要考虑负数,这个问题突然看着不是那么简单了。

说到负数的时候,一时忘记了负数在计算机中的表现形式。那么来巩固一下这个非常基础的计算机基础知识。

正数和负数

在计算机中,整数的表达方式区分正数和负数,正数用原码表示,负数用补码表示。除此之外,正数和负数还可以通过符号位区分,即最高位为符号位,最高位为0表示正数,最高位为1表示负数。

原码、反码和补码

正数的原码、反码、补码相同,如10的原码、反码、补码都是 00001010;

负数的原码为其对应正数的原码,但符号位为1,比如-10,其对应的正数10的原码为 00001010,那么-10的原码为 10001010;

负数的反码为其原码除符号位之外各位取反,如-10的原码为 10001010,除符号位外其余全部取反 11110101;

负数的补码为其反码加1,如-10的反码为 11110101,加1后,得到 111101010。

为什么有反码和补码

可以看出,对正数而言,反码和补码是没有意义的。反码和补码的意义只是针对负数,因为在计算机中负数的表现形式就是补码。

那么就有一点疑问了,负数为什么要用补码表示,用原码行不行?

来看看,负数用原码表示会有什么问题。还是以-10为例,其原码为 10001010,也就是符号位为1,其余位和正数一致。用原码这种方式很容易理解,但有个问题。一个字节能表示的最大负数只能为 11111111,也就是-127。这么看貌似就不对了,一个字节的表示范围只能是-127~127,-128表示不出来。而0的表示却有正0(00000000)和负0(10000000)两种表达方式,也就是说,用原码表示负数,负0是没什么用的。

那么如果用负0表示-128可以吗?-127为 11111111,-128为 10000000,两个数字跨度太大,对计算机而言不方便计算。

所以计算机的负数用补码表示。-127为 10000001,-128为 10000000,两个数字相差为1,方便计算。且不用去记原码表示时负数表示数字的特殊性,补码对所有负数表示的方式是一致的。

如何将一个整数转换成二进制

回归到开头引入的问题,如何将一个整数转换成二进制呢?

根据上面的规则,正数的二进制是其原码,负数的二进制是其补码。那么我们就可以根据这个规则来算,用golang来实现:

// toBinary 将正数转换成二进制
func toBinary(n int) string {
	// 正数,求原码
	if n >= 0 {
		var s string
		for n > 0 {
			if n%2 == 1 {
				s = s + "1"
			} else {
				s = s + "0"
			}
			n /= 2
		}
		return s
	}
	// 负数,先求其正数的原码
	origin := toBinary(-n)
	// 根据原码求反码
	var back string
	for _, v := range origin {
		if v == '0' {
			back += "1"
		} else {
			back += "0"
		}
	}
	// 根据反码求其补码
	carry := "1"
	var completion string
	for i := len(back) - 1; i >= 0; i-- {
		if back[i] == '0' {
			if carry == "1" {
				completion = completion + "1"
				carry = "0"
			} else {
				completion = completion + "0"
			}
		} else {
			if carry == "1" {
				completion = completion + "0"
				carry = "1"
			} else {
				completion = completion + "1"
			}
		}
	}
	return completion
}

上面的实现看着很复杂,但完全根据 正数的二进制是其原码,负数的二进制是其补码规则来完成。

那么是否还有更好的办法来实现呢?我们想想在计算机中,整数都是用二进制表示的,只是我们看到的是10进制,那么是否可以通过一些二进制的计算来完成呢?答案是可以,即通过移位运算加上与运算,就可以得到一个整数的二进制,同样以golang为例:

// toBinary2 将正数转换成二进制
func toBinary2(n int) string {
	// 这里认为n暂用8个字节
	var s string
	for i := 63; i >= 0; i-- {
		bit := (n >> i) & 1
		s += fmt.Sprintf("%d", bit)
	}
	return s
}

上面的代码看着简洁很多,通过移位运算,可以得到第i+1位的值,并跟1做与运算可以求得第i+1位是0还是1