【深入理解计算机系统】如何表示最大、最小的整数
最大、最小整数的二进制表示
深入了解数字的二进制表示,请查看:【深入理解计算机系统】第 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 的一种方法。
参考资料: