最大、最小整数的二进制表示

深入了解数字的二进制表示,请查看:📔【深入理解计算机系统】第 2 章:信息的处理与表示

计算机中的数字表示分为无符号数与有符号数。以 32 位整数为例,对于无符号数,最小值自然是所有位全为 0,写成十六进制即 0x00000000;最大值是所有位全为 1,写成十六进制即 0xffffffff,相当于 $2^{32}-1$ == 4294967295

对于有符号数,最高位是符号位。符号位为 0 表示正数,为 1 表示负数。因此有符号数的最大值是“符号位为 0、剩余位全 1”的正数,写成十六进制即 0x7fffffff,相当于 $2^{31}-1$ == 2147483647

0111 1111 1111 1111 1111 1111 1111 1111 # 二进制
7    f    f    f    f    f    f    f    # 十六进制

有符号数的最小值是“符号位为 1、剩余位全 0”的负数,写成十六进制即 0x80000000,相当于 $-2^{31}$ == -2147483648

为什么有符号数的最小值是这样表示呢?简而言之,无符号数的每一位都是正权值,而有符号数则不同,其符号位是负权值,剩余位是正权值。所以有符号数中,让符号位为 1、剩余位 全 0,就能得到最小的负数 0x800000000;让每一位都为 1,就能得到最大的负数 -1 == 0xffffffff。更多内容请阅读这篇文章

总结

  无符号数 Unsigned Int 有符号数 Int
最大值 0xffffffff == 4294967295 0x7fffffff == 2147483647
0 0x00000000 == 0 0x00000000 == 0
最小值 0x00000000 == 0 0x80000000 == -2147483648

将无符号数、有符号数的最大值、最小值分别记为:UMax/UMin,TMax/TMin,可以看到有这样的关系:

TMin == -TMax - 1

在编程语言中表示最大、最小整数

这一节,我们以 C 语言和 Golang 为例,描述如何在编程语言中表示最大、最小整数。

C 语言(32位)

无符号数的最大、最小值:

unsigned int UMin = 0;

unsigned int UMax = ~0; // 1. 取反,让每一位为 1
unsigned int UMax = 0xffffffff; // 2. 或者直接写出十六进制补码表示

测试:

printf("%#x, %u\n", UMin, UMin);
printf("%#x, %u\n", UMax, UMax);

输出:

0, 0
0xffffffff, 4294967295

有符号数的最大、最小值:

int TMin = -TMax - 1; // 1. TMin = -TMax - 1
int TMin = 1 << 31; // 2. 移位。写成 -1 << 31 也可以,取决于编译器版本
int TMin == 0x80000000; // 3. 直接写出十六进制补码表示

int TMax = ~TMin; // 1. 将 TMin 每一位取反
int TMax = 0x7fffffff; // 2. 直接写出十六进制补码表示

测试:

printf("%#x, %d\n", TMin, TMin);
printf("%#x, %d\n", TMax, TMax);

输出:

0x80000000, -2147483648
0x7fffffff, 2147483647

Go 语言(32位)

无符号数的最大、最小值:

var UMin uint32 = 0

var UMax uint32 = ^uint32(0)
var UMax uint32 = 0xffffffff

测试:

fmt.Printf("%#x, %d\n", UMin, UMin)
fmt.Printf("%#x, %d\n", UMax, UMax)

输出:

0x0, 0
0xffffffff, 4294967295

有符号数的最大、最小值:

var TMax int32 = 1 << 31 - 1
var TMax int32 = int32(UMax >> 1) 

var TMin int32 = -TMax - 1 
var TMin int32 = -1 << 31 // -(1 << 31) 也可以
var TMin int32 = -0x80000000 // 要加负号

测试:

fmt.Printf("%#x, %d\n", TMin, TMin)
fmt.Printf("%#x, %d\n", TMax, TMax)

输出:

0x80000000, -2147483648
0x7fffffff, 2147483647

深入了解

细心的读者会观察到,在 C 语言和 Go 语言中,表示 TMin 的方法有微妙的不同:

int TMin == 0x80000000 // C
var TMin int32 = -0x80000000 // Go

为什么 C 语言可以直接用 0x80000000 表示 TMin,而 Go 需要多加一个负号呢?实际上,这是因为 C 语言中,将一个无符号数赋给有符号数,会发送隐式类型转换无符号数到有符号数的类型转换,不会改变数的位级表示,只是会换一种解释方法。而 Go 语言不会隐式转换,必须显示转换类型。

首先,无论是 C 还是 Go,在编译阶段,编译器都会将十六进制常量解释为一个无符号数,因此 0x80000000 相当于无符号数 2147483648

C 语言中,由于会发生隐式类型转换,因此下面的代码会直接将 0x80000000 的二进制解释为补码,故变量 TMin 的值等于 -2147483648

int TMin == 0x80000000;

但是 Go 语言不会做隐式类型转换,编译器会先将 0x80000000 看作 2147483648,然后尝试使用一个 int32 类型的变量来表示这个值。显然,这个「正数」超出了 32 位 int 的表示范围,因此下面的代码会直接报错:

var TMin int32 = 0x80000000 // Error: 0x80000000 (untyped int constant 2147483648) overflows int32

-0x80000000 在编译阶段会被视为 -2147483648,这个数在 32 位 int 的表示范围内,因此下面的代码不会报错:

var TMin int32 = -0x80000000

在 Go 语言中,将无符号数转换为有符号数时,同样不会改变数的位级表示,只是使用补码来解释:

var UMax uint32 = 0x80000000
fmt.Println(UMax == 2147483648) // => true
var TMin int32 = int32(UMax) // 显式类型转换
fmt.Println(TMin == -2147483648) // => true

所以 int(UMax) 也是 Go 语言中表示 TMin 的一种方法。


参考资料: