信息存储
程序将内存视为一个非常大的字节数组,称为虚拟内存。内存的每一个字节都有一个唯一的数字来标识,称为地址。所有可能地址的集合就是虚拟地址空间。
浮点运算是不可结合的。
1
2
(3.14 + 1e20) - 1e20 => 0.0
3.14 + (1e20 - 1e20) => 3.14
整数运算和浮点运算会有不同的数学属性是因为它们处理数字表示有限性的方式不同:
- 整数的表示只能编码一个相对较小的数值范围,但是这种表示是精确的。
- 浮点数可以编码一个较大的数值范围,但是这种表示是近似的。
大量的计算机漏洞都是由于计算机算术运算的微妙细节引起的。
移动k位,K超过机器的位宽w时
对于一个由w
位组成的数据,如果要移动k≥w
位,会得到什么结果呢?
C标准对这种行为的结果是未定义的。但是一般的实现是实际位移量=k%w
。比如
1
2
3
4
// 在w=32位的机器上
int val1 = 0xABCDEF98 << 32; // 实际左移了0位
int val1 = 0xABCDEF98 >> 36; // 实际右移了4位
int val1 = 0xABCDEF98 >> 40; // 实际右移了8位
整数表示
在各种整数类型(unsigned)char
,(unsigned)short
,(unsigned)int
,(unsigned)long
,int32_t
,int64_t
等中,只有(unsigned)long
的位宽在32-bit
机器和64-bit
机器上是有区别的。
补码
非负整数的补码是其二进制表示本身。负数补码是其相反数的补码取反加一。
有符号数和无符号数转换
对于大多数C的实现,有符号数和无符号数之间的转换规则是:位模式不变,但是解释这些位的方式改变。
在C语言中,如果一个运算的操作数,一个是有符号数,一个是无符号数,那么C语言会隐式地将有符号数转为无符号数。
整数运算
计算机执行的“整数”运算实际上是一种模运算形式。因为表示数字的有限字长限制了可能的取值范围,运算结果可能溢出。
TMin
的加法逆元(相反数)是自身。
整数除法总是舍入到0,例如7/2=3
, 7/-2=-3
,-7/2=-3
,-7/-2=3
。
对于正整数x
除以2^k
,可以优化为表达式x >> k
。
对于负整数x
除以2^k
,就没有上面那么简单了。例如-7 >> 1 = -4
,而-7/2=-3
。为了仍然能够使用右移对除法进行优化,需要在右移前加上一个偏置2^k - 1
。
将两种场景综合起来,可以用下面的表达式。
1
(x < 0 ? (x + (1 << k) - 1) : x) >> k
浮点数
舍入,当一个数不能精确地表示为某种格式时,就必须向上调整或向下调整。
IEEE浮点数表示
- 符号,
s
决定这数是整数(s=0
)还是负数(s=1
)。对于数字0的符号位的解释作为特殊情况处理。 - 尾数,
M
是一个二进制小数,范围是[1, 2)
或者[0, 1)
。 - 阶码,
E
的作用是对浮点数加权。
整型与浮点型的转换
int → float
,不会发生溢出,但是可能会被舍入,因为float的小数字段是23位,可能会出现无法保留精度的情况。int/float → double
,可以精确的转换。double → float
,可能会溢出,也可能会舍入。float/double → int
,可能会溢出,也可能会向零舍入。