信息与存储
数据在计算机内部以十六进制的形式进行存储的。
不同的数据类型有不同的字节数。同一数据类型,在不同机器上也有不同的字节数:比如 long
类型在32位中是4个字节,在64位中是8个字节。
大小端
大端法:
100 | 101 | 102 | 103 | 地址 |
---|---|---|---|---|
12 | 34 | 56 | 78 | 内容 |
小端法:
100 | 101 | 102 | 103 | 地址 |
---|---|---|---|---|
78 | 56 | 34 | 12 | 内容 |
如何判断一个机器是大端还是小端呢?可以编写程序,程序内部声明一个联合体,内部两个成员:
int small_endian(void) {
union{
int a;
char b;
}c;
c.a = 1;
return (c.b == 1); //如果返回1,则说明是小端机器
}
因为联合体所有的成员都引用的是内存中同一个位置。将a赋值为1,如果是小端存储,则这个1会位于最高有效字节上。当联合体以char
的形式读数时,就会读到1。以此来说明是小端存储。
逻辑运算
其中有逻辑右移和算数右移两种右移方式。两者的不同在于右移的补位。
逻辑右移,补位和符号位相同;算术右移,补位为0。
当一个以w位组成的数据类型来说,如果移位的数量大于w时,它的移位数量按溢出来算。
整数
表示方式
整数的两种类型:有符号和无符号。
无符号整数
无符号最为简单。但这里有个小公式,计算一个整数t所需的最小位数n:
n = [ l o g 2 t ] n = [log_2t] n=[log2t]
其中 [] 代表取整。
有符号整数
有符号整数有很多种表达。
- 原码添加符号位
- 这种存在缺陷,存在+0和-0,并且算术运算复杂,不统一。
- 使用反码
- 反码就是按位取反,用来表示负数。
- 反码进行运算要使用循环进位,即计算后结果要加一。
- 缺点依旧存在+0和-0。
- 使用补码
- 补码的最高有效的权是负权,后面的有效位是正权。exp:1001:第一个1代表-8,最后一个1代表1,相加得到-7。
- 一个w位的补码能代表的范围:-2^(w-1) < x < 2^(w-1)-1。可以看出,左右两端并不对称。
- 原码与补码的转换:补码 = ~原码 + 1。
- 这个表示方式,具有唯一0,改进了上面的缺点。
运算
常数2的乘除。
比起直接用算术运算,使用移位更快捷。
比如乘以 2 3 2^3 23,就可以写成<<3。除法是右移。
这里不用考虑是否有无符号,行为都一样。(多亏了逻辑右移)
隐式转换
当有符号数和无符号数进行运算时,有符号数会强制转换为无符号数。此时的隐式转换会导致一些 BUG 。
sizeof 返回的一个无符号的数,当在for循环时,会常声明 int 变量 i ,当i - sizeof 之后,i 会变成一个无符号数,i < 0 的条件永远无法完成,导致死循环。
浮点数
位格式
float:
sign | exp | frac |
---|---|---|
1位(31) | 8位(30:23) | 23位(22:0) |
double:
sign | exp | frac | frac |
---|---|---|---|
1(63) | 11(62:52) | 20(51:32) | 32(31:0) |
表示方式
V = ( − 1 ) s ∗ M ∗ 2 E V = (-1)^s*M*2^E V=(−1)s∗M∗2E
看不懂不要紧,下面会解释。
规格化
浮点数三种情况:exp != 0 时的规格化;exp == 0 时的非规格化;exp 全为1时的无穷和NaN(Not a Number)。
![[Pasted image 20220707223948.png]]
其中,规格化用来表示 >1 的数;非规格化用来表示 <1 的数。为什么这么说呢?看看上面那个[[#表示方式|表达式]],其中的 M 为什么不直接写成 frac?
因为规格化时,M = frac + 1,相当于把 1 给省略了,这样就多出一个位用来表达;非规格化时,M = frac,直接是一个小数。
指数偏置
在[[#表示方式|表达式]]中,E 也不直接写成 exp,同样因为规格化。
当规格化时,E = exp - Bias,Bias = 2^(k-1) - 1。
当非规格化时,E = 1 - Bias。这样设置目的是为了将浮点数的位级行为与整数行为对应。
符号
浮点数存在 +0 和 -0 ,可能是为了产生 -∞ 和 +∞。