本次笔记内容:
03.整数的计算机表示与运算
注意在存储领域,不是以1024进位的,是以1000进位的。
在x86中,默认一个Word为16个比特:
机器字(machine word)长:
地址按照字节(byte)来定位:
一个机器字内的各个字节如何排序?
Big Endian:Sun,PowerPC,Internet
Little Endian:x86
例子如下。
0x100 | 0x101 | 0x102 | 0x103 |
---|---|---|---|
01 | 23 | 45 | 67 |
67 | 45 | 23 | 01 |
如上,要存储数值为0x01234567的数,上面是LSB,下面是x86的存储方式。
socket就有将本地字节序与网络字节序的功能。
再举一个例子:
Decimal: 15213
Binary: 0011 1011 0110 1101
Hex: 3 B 6 D
对于以下三种实现:
int A = 15321; int B = -15213; long int C = 15213;
在IA32, x86-64, Sun设备上的存储分别为:
C语言中基本数据类型的大小(in Bytes):
C Data Type | Typical 32-bit | x86-32 | x86-64 |
---|---|---|---|
char | 1 | 1 | 1 |
short | 2 | 2 | 2 |
int | 4 | 4 | 4 |
long | 4 | 4 | 8 |
long long | 8 | 8 | 8 |
float | 4 | 4 | 4 |
double | 8 | 8 | 8 |
long double | 8 | 10/12 | 10/16 |
char * or any other pointer | 4 | 4 | 8 |
w表示字长,对于无符号数: B 2 U ( X ) = ∑ i = 0 w − 1 x i ⋅ 2 i B2U(X) = \sum^{w-1}_{i=0} x_i \cdot 2^i B2U(X)=i=0∑w−1xi⋅2i
对于带符号数(补码,Two’s Complement): B 2 T = − x w − 1 ⋅ 2 w − 1 + ∑ i = 0 w − 2 x i ⋅ 2 i B2T = -x_{w-1} \cdot 2^{w-1} + \sum^{w-2}_{i=0} x_i \cdot 2^i B2T=−xw−1⋅2w−1+i=0∑w−2xi⋅2i
对于下列实现:
short int x = 15213; short int y = -15213;
其存储为:
Decimal | Hex | Binary | |
---|---|---|---|
x | 15213 | 3B 6D | 00111011 01101101 |
y | -15213 | C4 93 | 11000100 10010011 |
对于负数(补码),即取反再加一。
符号位(sign bit):
在带符号数中,负数范围个数总比整数范围个数多1,举例:
无符号数与带符号数之间的转换:
上图为无符号数和带符号数的关系。
常数(Constants):
如果无符号数与带符号数混合使用,则带符号数默认转换为无符号数,包括比操作符。
下面是例题,比较常数1和常数2的关系:
Constant_1 | Constant_2 | Relation | Evaluation |
---|---|---|---|
0 | 0U | == | unsigned |
-1 | 0 | < | signed |
-1 | 0U | > | unsigned |
2147483647 | -2147483647-1 | > | signed |
2147483647U | -2147483647-1 | < | unsigned |
-1 | -2 | > | signed |
(unsigned) -1 | -2 | > | unsigned |
2147483647 | 2147483648U | < | unsigned |
2147483647 | (int) 2147483648 | > | signed |
将负的带符号数转换为无符号数时,总比正的带符号数大,可以参考上面的映射示意图。
建议:不能仅仅因为取值范围是非负而使用。只在两种情况使用:
不能乱用unsigned的例子:
// example I unsigned i; for (i = cnt - 2; i >= 0; i--) a[i] += a[i+1]; // 永远无法结束,i恒大于等于0 // example II #define DELTA sizeof(int) int i; for (i = CNT; i-DELTA >= 0; i-= DELTA) ... // sizeof()返回unsigned,而i-=DELTA运算后,i被转换为unsigned,导致for无法退出
无符号数和带符号数(补码)加法所用的x86机器指令都是同一条类型。加法就是两个位串加起来。
在编译时,如果遇到无符号整数除以2的k次幂,不调用除法指令(硬件的除法非常慢),而调用逻辑右移。
u >> k gives ⌊ u / 2 k ⌋ \lfloor u / 2^k \rfloor ⌊u/2k⌋
// C Function unsigned udiv8(unsigned x) { return x / 8; } // Compiled Arithmetic Operations shrl $3, %eax // Explanation # Logical shift return x >> 3;
u >> k gives ⌊ u / 2 k ⌋ \lfloor u / 2^k \rfloor ⌊u/2k⌋
但是如果 x < 0 x < 0 x<0,出现舍入错误。
解决方案:
// C Function int idiv8(int x) { return x / 8; } // Compiled Arithmetic Operations testl %eax, %eax js L4 L3: sarl $3, %eax ret L4: addl $7, %eax jmp L3 // Explanation # Logical shift if x < 0 x += 7; # Arithmetic shift return x>>3;
int x = foo(); int y = bar(); unsigned ux = x; unsigned uy = y;
不对,可能溢出。
对。
对。
7即111,x & 7 == 111,即x低位的3位为111。
x是int类型(32位),111向左移30位,则符号位为1,是负数。
不对,ux一定是小于等于-1的(-1转换为unsigned后,大)。
不对,因为不一定。负数的表示范围比正数大1,若y为-128,推论将不成立。
不对,可能溢出,高位被截断。
不对,可能溢出。
对。
不对,-128为反例。
不对,0位反例。
对。
不对,负数为反例。
不对,x取0为反例。
已知某32位整数X,其值为-101,则其16进制补码为(0xFFFFFF9B),另一32位整数Y的补码为0xFFFFFF6A,则X+Y的16进制补码(32位)为(0xFFFFFF05),X-Y的16进制补码为(0x31)。
解: