从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...表示
7128  
8256  
101024千(Thousand)1K
1665, 536 64K
201, 048, 576百万(Million)1M
301, 073, 741, 824十亿(Billion)1G
324, 294, 967, 296 4G
401, 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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值