从C语言的整数取值范围说开去

在ILP32中, char, short, int, long, long long, pointer分别占1, 2, 4, 4, 8, 4个字节,
在 LP64中, char, short, int, long, long long, pointer分别占1, 2, 4, 8, 8, 8个字节,
无论是在ILP32中还是LP64中, long long总是占8个字节,下面给出简单的C代码实现表征出整数的取值范围先。

o foo.c

 1 #include <stdio.h>
 2 /**
 3  * The size (n bytes) of basic types
 4  * =================================
 5  *        char short int long long long pointer
 6  * -----  ---- ----- --- ---- --------- -------
 7  *  LP64     1     2   4    8         8       8
 8  * ILP32     1     2   4    4         8       4
 9  */
10 typedef char                    __s8;
11 typedef short                   __s16;
12 typedef int                     __s32;
13 typedef long long               __s64;
14 typedef unsigned char           __u8;
15 typedef unsigned short          __u16;
16 typedef unsigned int            __u32;
17 typedef unsigned long long      __u64;
18 
19 #define SMAX8   ((__s8 )(((__u8 )~0) >> 1))
20 #define SMAX16  ((__s16)(((__u16)~0) >> 1))
21 #define SMAX32  ((__s32)(((__u32)~0) >> 1))
22 #define SMAX64  ((__s64)(((__u64)~0) >> 1))
23 
24 #define SMIN8   -SMAX8
25 #define SMIN16  -SMAX16
26 #define SMIN32  -SMAX32
27 #define SMIN64  -SMAX64
28 
29 #define UMAX8   ((__u8 )~0)
30 #define UMAX16  ((__u16)~0)
31 #define UMAX32  ((__u32)~0)
32 #define UMAX64  ((__u64)~0)
33 
34 #define UMIN8   ((__u8 )0)
35 #define UMIN16  ((__u16)0)
36 #define UMIN32  ((__u32)0)
37 #define UMIN64  ((__u64)0)
38 
39 int main(int argc, char *argv[])
40 {
41         __s8  smax8  = SMAX8;
42         __s16 smax16 = SMAX16;
43         __s32 smax32 = SMAX32;
44         __s64 smax64 = SMAX64;
45         __s8  smin8  = SMIN8;
46         __s16 smin16 = SMIN16;
47         __s32 smin32 = SMIN32;
48         __s64 smin64 = SMIN64;
49         printf("s64: [%llx, %llx]	[%lld, %lld]
", smin64, smax64, smin64, smax64);
50         printf("s32: [%x, %x]			[%d, %d]
",     smin32, smax32, smin32, smax32);
51         printf("s16: [%x, %x]				[%d, %d]
",   smin16, smax16, smin16, smax16);
52         printf("s8 : [%x, %x]				[%d, %d]
",   smin8,  smax8,  smin8,  smax8);
53         printf("
");
54 
55         __u8  umax8  = UMAX8;
56         __u16 umax16 = UMAX16;
57         __u32 umax32 = UMAX32;
58         __u64 umax64 = UMAX64;
59         __u8  umin8  = UMIN8;
60         __u16 umin16 = UMIN16;
61         __u32 umin32 = UMIN32;
62         __u64 umin64 = UMIN64;
63         printf("u64: [%llx, %llx]			[%lld, %llu]
", umin64, umax64, umin64, umax64);
64         printf("u32: [%x, %x]				[%d, %u]
",       umin32, umax32, umin32, umax32);
65         printf("u16: [%x, %x]					[%d, %u]
",     umin16, umax16, umin16, umax16);
66         printf("u8 : [%x, %x]					[%d, %u]
",     umin8,  umax8,  umin8,  umax8);
67 
68         return 0;
69 }

o 编译并执行

$ gcc -g -Wall -m32 -o foo foo.c
$ ./foo
s64: [8000000000000001, 7fffffffffffffff]       [-9223372036854775807, 9223372036854775807]
s32: [80000001, 7fffffff]                       [-2147483647, 2147483647]
s16: [ffff8001, 7fff]                           [-32767, 32767]
s8 : [ffffff81, 7f]                             [-127, 127]

u64: [0, ffffffffffffffff]                      [0, 18446744073709551615]
u32: [0, ffffffff]                              [0, 4294967295]
u16: [0, ffff]                                  [0, 65535]
u8 : [0, ff]                                    [0, 255]

注意: 二进制数在计算机中一律以补码表示。 这里简单说说二进制编码中的原码,反码以及补码(注:移码这里不谈)以帮助理解上面的输出。

1. 原码的编码规则
1.1 原码即"原始编码", 最高位为符号位,0表示整数,1表示负数;
1.2 +0和-0的原码表示是不同的。在16位机器上,

+0 = 0000 0000 0000 0000b 
-0 = 1000 0000 0000 0000b

2. 反码的编码规则
2.1 正数的反码等于其原码;
2.2 负数的反码是符号位不变,除符号外之外的其他位按位取反;
2.3 +0和-0的反码表示也是不同的。在16位机器上,

+0 = 0111 1111 1111 1111b
-0 = 1111 1111 1111 1111b

3. 补码的编码规则
3.1 正数的补码等于原码;
3.2 负数的补码是符号位不变,除符号外之外的其他位按位取反,再给最低位加1;
3.3 +0和-0的补码是唯一的,都是0。在16位机器上,

+0 = 0000 0000 0000 0000b ;= +0(反)
-0 = 0000 0000 0000 0000b ;= -0(反)+1

4. 为什么要引入补码?
4.1 无论是原码,还是反码,都无法解决0的的二义性问题。补码的引入,解决了这一问题,也就是0的表示是唯一的
4.2 让符号位参与运算。因此,所有减法都可以用加法器实现。

o 因为编译选项是-m32, 所以:

-127                 的补码表示是        0xffffff81 = (1111 1111 1111 1111 1111 1111 1000 0001b)
-32767               的补码表示是        0xffff8001 = (1111 1111 1111 1111 1000 0000 0000 0001b)
-2147483647          的补码表示是        0x80000001 = (1000 0000 0000 0000 0000 0000 0000 0001b)
-9223372036854775807 的补码表示是0x8000000000000001 = (1000 0000 0000 0000 0000 0000 0000 0000
                                                     0000 0000 0000 0000 0000 0000 0000 0001b)

以-127为例,在32位机器上,其原码、反码和补码可表示为:

o 1000 0000 0000 0000 0000 0000 0111 1111b ; 原码

o 1111 1111 1111 1111 1111 1111 1000 0000b ; 反码: 在原码的基础上, 符号位不变, 剩下31位按位取反

o 1111 1111 1111 1111 1111 1111 1000 0001b ; 补码: 在反码的基础上, 给最低位加1

小结: 将常见的整数的取值范围牢记于心,有利于在实际的程序设计中根据需求快速地确定变量(或结构体成员)的基本数据类型,写出优质无错的代码。

  • 对于占N个二进制位的有符号整数, 能表示的范围是[- (2^N-1 - 1), +((2^N-1 - 1)], N=8, 16, 32, 64, ... (因为符号位占了一位,所以是N-1
  • 对于占N个二进制位的无符号整数, 能表示的范围是[0,             +((2^N   - 1)], N=8, 16, 32, 64, ...

另外,在做算法设计的时候,将下面的表格(2的N次方)烂熟于心也有利于快速做出判断。 例如,一个将每个32位无符号整数映射为布尔值的hash表可以将一台计算机的内存填满。

2的N次方 准确值 近似值 K/M/G/T...表示
7 128    
8 256    
10 1024 千(Thousand) 1K
16 65, 536   64K
20 1, 048, 576 百万(Million) 1M
30 1, 073, 741, 824 十亿(Billion) 1G
32 4, 294, 967, 296   4G
40 1, 099, 511, 627, 776  万亿(Trillion) 1T
1K : 2^10 : 千
1M : 2^20 : 百万 (Million) ; 千千
1G : 2^30 : 十亿 (Billion) ; 千百万
1T : 2^40 : 万亿 (Trillion); 千十亿
1E : 2^50
1Z : 2^60
256: 2^8
64K: 2^16
4G : 2^32
4Z : 2^64
原文地址:https://www.cnblogs.com/idorax/p/6412867.html