從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]\t[%lld, %lld]\n", smin64, smax64, smin64, smax64);
50         printf("s32: [%x, %x]\t\t\t[%d, %d]\n",     smin32, smax32, smin32, smax32);
51         printf("s16: [%x, %x]\t\t\t\t[%d, %d]\n",   smin16, smax16, smin16, smax16);
52         printf("s8 : [%x, %x]\t\t\t\t[%d, %d]\n",   smin8,  smax8,  smin8,  smax8);
53         printf("\n");
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]\t\t\t[%lld, %llu]\n", umin64, umax64, umin64, umax64);
64         printf("u32: [%x, %x]\t\t\t\t[%d, %u]\n",       umin32, umax32, umin32, umax32);
65         printf("u16: [%x, %x]\t\t\t\t\t[%d, %u]\n",     umin16, umax16, umin16, umax16);
66         printf("u8 : [%x, %x]\t\t\t\t\t[%d, %u]\n",     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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM