深入理解计算机系统(原书第3版)- 第2章 信息的表示和处理 笔记

2.1 信息存储

1字节(byte)= 8位(bit)

2.1.1 十六进制表示法

十六进制(简写为"hex")使用数字 '0' ~ '9' 以及字符 'A' ~ 'F' 来表示。
1 个十六进制数表示 4 个二进制数。
当 x=2n 时,很容易将 x 写成十六进制形式,x 的二进制表示就是 1 后面跟 n 个 0 。十六进制数字 0 代表 4 个二进制 0。所以,当 n 表示成 i+4j 的形式,其中 0 ≦ i ≦ 3,x 开头十六进制为1(i=0)、2(i=1)、4(i=2)、8(i=3),后面跟随 j 个十六进制的 0。

2.1.2 字数据大小

C声明 C声明 字节数 字节数
有符号 无符号 32位 64位
[signed] char unsigned char 1 1
short unsigned short 2 2
int unsigned 4 4
long unsigned long 4 8
int32_t uint32_t 4 4
int64_t uint64_t 8 8
char * 4 8
float 4 4
double 8 8

2.1.3 寻址和字节顺序

最低有效字节在最前面的方式,为小端法(little endian)。
最高有效字节在最前面的方式,为大端法(big endian)——与书写习惯一致。

假设变量 x 的类型为 int,位于地址 0x100 处,它的十六进制值为 0x01234567。地址范围 0x100〜0x103 的字节顺序依赖于机器的类型:
注意,在字 0x01234567 中,高位字节的十六进制值为 0x01,而低位字节值为 0x67。

// 判断大小端
int is_little_endian(void) {
    int i = 0xff;
    unsigned char *p = &i;
    return p[0] == 0xff;
}

2.1.4 表示字符串

C语言中字符串被编码为一个以null(其值为0)字符结尾的字符数组。
字母 'a' ~ 'z' 的 ASCII 码为 0x61 ~ 0x7A。

2.1.5 表示代码

不同的机器类型使用不同的且不兼容的指令和编码方式。

2.1.6 布尔代数简介

1 为 true(真)
0 为 false(假)
图2-7↓ 布尔代数的运算。二进制值 1 和 0 表示逻辑值 TRUE 或者 FALSE ,而运算符 〜、&、丨和 ^ 分别表示逻辑运算 NOT、 AND、OR 和 EXCLUSIVE-OR

可以将上述 4 个布尔运算扩展到位向量的运算,位向量就是固定长度为 W 、由 0 和 1 组成的串。位向量的运算可以定义成参数的每个对应元素之间的运算。
举个例子,假设 w=4 ,参数 a = [0110] ,b=[1100]。那么 4 种运算a&b、a|b、a^b 和〜b 分别得到以下结果:

关于布尔代数和布尔环的更多内容
布尔运算 & 对 | 的分配律,写为 a & (b | c) = (a & b) | (a & c)。
布尔运算 | 对 & 的分配律,写为 a | (b & c) = (a | b) & (a | c)。
对于任何值 a 来说,a ^ a = 0。
(a ^ b) ^ a = b。

2.1.7 C语言中的位级运算

C的表达式 二进制表达式 二进制结果 十六进制结果
~0x41 ~[0100 0001] [1011 1110] 0xBE
~0x00 ~[0000 0000] [1111 1111] 0xFF
0x69&0x55 [0110 1001]&[0101 0101] [0100 0001] 0x41
0x69|0x55 [0110 1001]|[0101 0101] [0111 1101] 0x7D

位级运算的一个常见用法就是实现掩码运算,这里掩码是一个为模式,表示从一个字中选出的位的集合。
如:x = 0x89ABCDEF,x&0xFF = 0x000000EF
表达式 ~0 将生成一个全 1 的掩码,不管机器的字大小是多少。

2.1.8 C语言中的逻辑运算

|| OR 或
&& AND 与
! NOT 非

表达式 结果
!0x41 0x00
!0x00 0x01
!!0x41 0x01
0x69&&0x55 0x01
0x69||0x55 0x01

如果对第一个参数求值就能确定表达式的结果,那么逻辑运算符就不会对第二个参数求值。

2.1.9 C语言中的位移运算

左移:x<<k,x向左移动k位,丢弃最高的k位,并在右端补k个0。移位量应该是一个0~w-1之间的值。
逻辑右移:x>>k,在左端补k个0。
算术右移:x>>k,在左端补k个最高有效位的值。

2.2 整数表示

数学术语
下标w表示数据表示中的位数

符号 类型 含义
B2T_w 函数 二进制转补码
B2U_w 函数 二进制转无符号数
U2B_w 函数 无符号数转二进制
U2T_w 函数 无符号转补码
T2B_w 函数 补码转二进制
T2U_w 函数 补码转无符号数
TMin_w 常数 最小补码值
TMax_w 常数 最大补码值
UMax_w 常数 最大无符号数
+_w^t 操作 补码加法
+_w^u 操作 无符号数加法
*_w^t 操作 补码乘法
*_w^u 操作 无符号数乘法
-_w^t 操作 补码取反
-_w^u 操作 无符号数取反

2.2.1 整型数据类型

32位程序上C语言整型数据类型的典型取值范围

C数据类型 最小值 最大值
[signed]char -128 127
unsigned char 0 255
short -32 768 32 767
unsigned short 0 65 535
int -2 147 483 648 2 147 483 647
unsigned 0 4 294 967 295
long -2 147 483 648 2 147 483 647
unsigned long 0 4 294 967 295
int32_t -2 147 483 648 2 147 483 647
uint32_t 0 4 294 967 295
int64_t -9 223 372 036 854 775 808 9 223 372 036 854 775 807
uint_64_t 0 18 446 744 073 709 551 615

64位程序上C语言整型数据类型的典型取值范围

C数据类型 最小值 最大值
[signed]char -128 127
unsigned char 0 255
short -32 768 32 767
unsigned short 0 65 535
int -2 147 483 648 2 147 483 647
unsigned 0 4 294 967 295
long -9 223 372 036 854 775 808 9 223 372 036 854 775 807
unsigned long 0 18 446 744 073 709 551 615
int32_t -2 147 483 648 2 147 483 647
uint32_t 0 4 294 967 295
int64_t -9 223 372 036 854 775 808 9 223 372 036 854 775 807
uint_64_t 0 18 446 744 073 709 551 615

C语言的整型数据类型的保证的取值范围

C数据类型 最小值 最大值
[signed]char -127 127
unsigned char 0 255
short -32 767 32 767
unsigned short 0 65 535
int -32 767 32 767
unsigned 0 65 535
long -2 147 483 647 2 147 483 647
unsigned long 0 4 294 967 295
int32_t -2 147 483 648 2 147 483 647
uint32_t 0 4 294 967 295
int64_t -9 223 372 036 854 775 808 9 223 372 036 854 775 807
uint_64_t 0 18 446 744 073 709 551 615

2.2.2 无符号数的编码

无符号数编码的定义
对向量\vec{x}=[x_{w-1},x_{w-2},...,x_0]:
B2U_w(\vec{x}) \doteq \sum_{i=0}^{w-1}x_i2^i

B2U_4([0001]) = 0 · 2^3 + 0 · 2^2 + 0 · 2^1 + 1 · 2^0 = 0 + 0 + 0 + 1 = 1
B2U_4([0101]) = 0 · 2^3 + 1 · 2^2 + 0 · 2^1 + 1 · 2^0 = 0 + 4 + 0 + 1 = 5
B2U_4([1011]) = 1 · 2^3 + 0 · 2^2 + 1 · 2^1 + 1 · 2^0 = 8 + 0 + 2 + 1 = 11
B2U_4([1111]) = 1 · 2^3 + 1 · 2^2 + 1 · 2^1 + 1 · 2^0 = 8 + 4 + 2 + 1 = 15

最大值是用位向量[11...1]表示
UMax_w \doteq \sum_{i=0}^{w-1}2^i = 2^w - 1
以4位情况为例,
UMax_4 = B2U_4([1111]) = 2^4 - 1 = 15

2.2.3 补码编码

补码编码的定义
对向量\vec{x}=[x_{w-1},x_{w-2},...,x_0]:
B2T_w(\vec{x}) \doteq -x_{w-1}2^{w-1}+ \sum_{i=0}^{w-2}x_i2^i

B2T_4([0001]) = - 0 · 2^3 + 0 · 2^2 + 0 · 2^1 + 1 · 2^0 = 0 + 0 + 0 + 1 = 1
B2T_4([0101]) = - 0 · 2^3 + 1 · 2^2 + 0 · 2^1 + 1 · 2^0 = 0 + 4 + 0 + 1 = 5
B2T_4([1011]) = - 1 · 2^3 + 0 · 2^2 + 1 · 2^1 + 1 · 2^0 = - 8 + 0 + 2 + 1 = - 5
B2T_4([1111]) = - 1 · 2^3 + 1 · 2^2 + 1 · 2^1 + 1 · 2^0 = - 8 + 4 + 2 + 1 = - 1

最小值是用位向量[10...0]表示
(也就是设置这个位为负权,但是清除其他所有的位)
TMin_w \doteq -2^{w-1}
以4位情况为例,
TMin_4 = B2T_4([1000]) = -2^3 = -8

最大值是用位向量[01...1]表示
(清除具有负权的位,而设置其他所有的位)
TMax_w \doteq \sum_{i=0}^{w-2}2^i = 2^{w-1} - 1
以4位情况为例,
TMax_4 = B2T_4([0111]) = 2^2 + 2^1 + 2^0 = 4 + 2 + 1 = 7

注意:
1、补码的范围是不对称的:|TMin| = |TMax| + 1 ,也就是说,TMin没有与之对应的正数。
2、最大的无符号数值刚好比补码的最大值的两倍大一点:UMax_w = 2TMax_w + 1
3、-1 和 UMax 有同样的位表示——一个全 1 的串,数值 0 在两种表示方式中都是全 0 的串。

2.2.4 有符号数和无符号数之间的转换

补码转换为无符号数

对满足 TMin_w \leq x \leq TMax_wx 有:
T2U_w (x)=\begin{cases} x + 2^w ,\quad x < 0 \\ x,\qquad\quad\ x \geq 0 \end{cases}
比如,T2U_w(-12345)=-12345+2^{16}=53191,同时T2U_w(-1)=-1+2^w=UMax_w

推导:补码转换为无符号数
B2U_w(T2B_w(x))=T2U_w(x)=x+x_{w-1}2^w
根据上个公式的两种情况,在x的补码表示中,位x_{w-1}决定了x是否为负。

无符号数转换为补码
对满足 0 \leq x \leq UMax_w\ u\有:
U2T_w (u)=\begin{cases} u ,\qquad\quad\ u \leq TMax_w \\ u-2^w ,\quad u > TMax_w \end{cases}

推导:无符号数转换为补码
U2T_w(u)=-u_{w-1}2^w+u
在u的无符号表示中,对上个公式的两种情况来说,位u_{w-1}决定了 u 是否大于 TMax_w=2^{w-1}-1

2.2.5 C语言中的有符号数与无符号数

C语言支持所有整型数据类型的有符号和无符号运算。尽管C语言标准没有指定有符号数要采用某种表示,但是几乎所有机器都使用补码。通常,大多数数字都默认为是有符号的。要创建一个无符号常量,必须加上后缀字符 'U' 或者 'u' 。
C语言无符号数和有符号数之间的转换,一般原则是底层的位表示保持不变。
当执行一个运算时,如果它的一个运算数是有符号的而另一个是无符号的,那么C语言会隐式地将有符号参数强制类型转换为无符号数,并假设这两个数都是非负的,来执行这个运算。
C语言中TMin的写法
定义在头文件 limits.h 中

#define INT_MAX 2147483647
#define INT_MIN (-INT_MAX - 1)

2.2.6 扩展一个数字的位表示

要将一个无符号数转换为一个更大的数据类型,在开头添加0,这种运算被称为零扩展(zero extension)
要将一个补码数字转换为一个更大的数据类型,可以执行一个符号扩展(sign extension),在表示中添加最高有效位的值。

2.2.7 截断数字

无符号数的截断结果是:
B2U_k[x_{k-1},x_{k-2},...,x_0]=B2U_w([x_{w-1},x_{w-2},...,x_0])\ mod\ 2^k
补码数字的截断结果是:
B2T_k[x_{k-1},x_{k-2},...,x_0]=U2T_k(B2U_w([x_{w-1},x_{w-2},...,x_0]))\ mod\ 2^k

2.2.8 关于有符号数与无符号数的建议

有符号数到无符号数的隐式转换,是会导致错误或者漏洞的方式,避免这类错误的一种方法就是绝不使用无符号数。

2.3 整数运算

2.3.1 无符号加法

将操作 +_w^u 描述为无符号数加法
对满足 0 \leq x,y \leq 2^w\ x\\ y\有:
x+_w^uy=\begin{cases} x+y ,\qquad\quad\ x+y<2^w \qquad\qquad\ 正常 \\ x+y-2^w ,\quad 2^w \leq x+y \leq 2^{w+1} \quad 溢出 \end{cases}

检测无符号数加法中的溢出
对在范围 0 \leq x,y \leq UMax_w 中的\ x\\ y\,令s \doteq x+_w^uy。则对计算s,当且仅当s < x(或者等价地s < y)时,发生了溢出。

阿贝尔群
每个元素有一个加法逆元。考虑 w 位的无符号数的集合,执行加法运算+_w^u。对于每个值 x,必然有某个值-_w^ux满足-_w^ux+_w^ux=0
无符号数求反
对满足 0 \leq x < 2^w 的任意 x,其w位的无符号逆元-_w^ux由下式给出:
-_w^ux =\begin{cases} x ,\qquad\quad\ x=0 \\ 2^w - x ,\quad x>0 \end{cases}

2.3.2 补码加法

对满足 -2^{w-1} \leq x,y \leq 2^{w-1}-1 之内的整数 xy,有:
x+_w^ty =\begin{cases} x + y - 2^w ,\quad 2^{w-1} \leq x + y \qquad\qquad\ \ \ 正溢出 \\ x + y ,\qquad\quad -2^{w-1} \leq x + y \leq 2^{w-1} \quad 正常 \\ x + y + 2^w ,\quad x + y < -2^{w-1} \qquad\qquad 负溢出 \end{cases}

两个数的 w 位补码之和有完全相同的位级表示。我们可以按如下步骤表示运算+_w^t:将其参数转换为无符号数,执行无符号数加法,再将结果转换为补码:
x+_w^ty \doteq U2T_w(T2U_w(x)+_w^uT2U_w(y))

检测补码加法中的溢出
对满足 TMin_w \leq x,y \leq TMax_wxy,令 s \doteq x +_w^t y。当且仅当 x>0,y>0 ,但 s \leq 0 时,计算 s 发生了正溢出。当且仅当 x<0,y<0,但 s \geq 0 时,计算 s 发生了负溢出。

2.3.3 补码的非

可以看到范围在 TMin_w \leq x \leq TMax_w 中的每个数字 x 都有 +_w^t 下的加法逆元,我们将 -_w^tx 表示如下:
对满足 TMin_w \leq x \leq TMax_wx,其补码的非 -_w^t 由下式给出
-_w^tx =\begin{cases} TMin_w ,\quad x=TMin_w \\ -x ,\qquad\quad x>TMin_w \end{cases}
也就是说,对 w 位的补码加法来说,TMin_w 是自己的加法的逆,而对其他任何数值 x 都有 -x 作为其加法的逆。

补码非的位级表示
计算一个位级表示的值的补码非有几种聪明的方法。
1、执行位级补码非的第一种方法是对每一位求补,再对结果加1。在C语言中,我们可以说,对于任意整数值 x ,计算表达式 -x 和 ~x+1 得到的结果完全一样。
2、计算一个数 x 的补码非的第二种方法是建立在将位向量分为两部分的基础之上的。假设 k 是最右边的 1 的位置,因而 x 的位级表示形如 [x_{w-1},x_{w-2},...,x_{k+1},1,0,...,0]。(只要 x \ne 0 就能够找到这样的 k。)这个值的非写成二进制格式就是 [~x_{w-1},~x_{w-2},...,~x_{k+1},1,0,...,0]。也就是,我们对位 k 左边的所有位取反。

2.3.4 无符号乘法

对满足 0 \leq x,y \leq UMax_wxy 有:
x *_w^u y = (x \cdot y) \ mod\ 2^w

2.3.5 补码乘法

对满足 TMin_w \leq x,y \leq TMax_wxy 有:
x *_w^t y = U2T_w((x \cdot y) \ mod\ 2^w)
对于无符号和补码乘法来说,乘法运算的位级表示都是一样的。

2.3.6 乘以常数

乘以 2 的幂
x 为位模式 [x_{w-1},x_{w-2},...,x_0] 表示的无符号整数。那么,对于任何 k \geq 0,我们都认为 [x_{w-1},x_{w-2},...,x_0,0,...,0] 给出了 x2^kw + k 位的无符号表示,这里右边增加了 k 个 0 。

与 2 的幂相乘的无符号乘法
C 变量 x 和 k 有无符号数值 xk,且 0 \leq k < w,则 C 表达式 x<<k 产生数值 x *_w^u 2^k

与 2 的幂相乘的补码乘法
C 变量 x 和 k 有补码值 x 和 无符号数值k,且 0 \leq k < w,则 C 表达式 x<<k 产生数值 x *_w^t 2^k

由于整数乘法比移位和加法的代价要大得多,许多 C 语言编译器试图以移位、加法和减法的组合来消除很多整数乘以常数的情况。
对于某个常数 K 的表达式 x * K 生成代码。编译器会将 K 的二进制表示表达为一组 0 和 1 交替的序列:
[(0...0)(1...1)(0...0)...(1...1)]
考虑一组从位位置 n 到位位置 m 的连续的 1(n\geqm)。我们可以用下面两种不同形式中的一种来计算这些位对乘积的影响:
形式A:(x<<n)+(x<<(n-1)+...+(x<<m)
形式B:(x<<(n+1))-(x<<m)
把每个这样连续的 1 的结果加起来,不用做任何乘法,我们就能计算出 x * K 。

2.3.7 除以 2 的幂

整数除法要比整数乘法更慢。除以 2 的幂也可以用移位运算来实现。无符号和补码数分别使用逻辑移位和算术移位来达到目的。
整数除法总是舍入到零。它将\lfloor向下\rfloor舍入一个正值,而\lceil向上\rceil舍入一个负值。
除以 2 的幂的无符号除法
C 变量 x 和 k 有无符号数值 xk,且 0 \leq k < w,则 C 表达式 x >> k 产生数值 \lfloor x/2^k \rfloor

除以 2 的幂的补码除法,向下舍入
C 变量 x 和 k 有补码值 x 和 无符号数值k,且 0 \leq k < w,则当执行算术位移时, C 表达式 x >> k 产生数值 \lfloor x/2^k \rfloor
对于负数来说,向下舍入结果会有偏差。

除以 2 的幂的补码除法,向上舍入
C 变量 x 和 k 有补码值 x 和 无符号数值k,且 0 \leq k < w,则当执行算术位移时, C 表达式 (x+(1<<k)-1) >> k 产生数值 \lfloor x/2^k \rfloor

2.3.8 关于整数运算的最后思考

  • 计算机执行的“整数”运算实际上是一种模运算形式。
  • 表示数字的有限字长限制了可能的值的取值范围,结果运算可能溢出。
  • 补码表示提供了一种既能表示负数也能表示正数的灵活方法,同时使用了与执行无符号算术相同的位级实现。
  • 无论运算数是以无符号形式还是以补码形式表示的,都有完全一样或者类似的位级行为。

2.4 浮点数

浮点表示对形如 V = x \times 2^y 的有理数进行编码。它对执行涉及非常大的数字(|V|>>0)、非常接近于0(|V|<<1)的数字,以及更普遍地作为实数运算的近似值的计算,是很有用的。

2.4.1 二进制小数

形如 b_mb_{m-1}...b_1b_0.b_{-1}b_{-2}...b_{-n+1}b_{-n} 的表示法,其中每个二进制数字,或者称为位,b_i 的取值范围是 0 和 1 ,这种表示方法表示的数 b 定义如下:
b = \sum_{i=-n}^{m}s^i \times b_i
点左边的位的权是 2 的正幂,点右边的位的权是 2 的负幂。
例如,101.11_2 表示数字 1 \times 2^2 + 0 \times 2^1 + 1 \times 2^0 + 1 \times 2^{-1} + 1 \times 2^{-2} = 4 + 0 + 1 + \frac{1}{2} + \frac{1}{4} = 5 \frac{3}{4}
形如 0.11...1_2 的数表示的是刚好小于 1 的数。例如,0.111111_2 表示 \frac{63}{64},我们将用简单的表达法 1.0-\varepsilon 来表示这样的数值。
定点表示法不能很有效的表示非常大的数字。

2.4.2 IEEE 浮点表示

IEEE浮点标准用 V = (-1)^s \times M \times 2^E 的形式来表示一个数:

  • 符号(sign)s 决定这数是负数(s=1)还是正数(s=0),而对于数值 0 的符号位解释作为特殊情况处理。
  • 尾数(significand)M 是一个二进制小数,它的范围是 1 \sim 2-\varepsilon,或者是 0 \sim 1-\varepsilon
  • 阶码(exponent)E 的作用是对浮点数加权,这个权重是 2E 次幂(可能是负数)。

将浮点数的位表示划分为三个字段,分别对这些值进行编码:

  • 一个单独的符号位 s 直接编码符号 s
  • k 位的阶码字段 exp = e_{k-1}...e_1e_0 编码阶码 E
  • n 位小数字段 frac = f_{n-1}...f_1f_0 编码尾数 M,但是编码出来的值也依赖于阶码字段的值是否等于 0 。

在单精度浮点格式(C 语言中的 float)中,s、exp 和 frac 字段分别为 1 位、k=8 位和 n=23 位,得到一个 32 位的表示。
在双精度浮点格式(C 语言中的 double)中,s、exp 和 frac 字段分别为 1 位、k=11 位和 n=52 位,得到一个 64 位的表示。


给定位表示,根据 exp 的值,被编码的值可以分成三种不同的情况(最后一种情况有两个变种)。图 2-33 说明了对单精度格式的情况。

情况1:规格化的值
当 exp 阶码域的位模式既不全为 0,也不全为 1 时。
阶码字段被解释为以偏置(biased)形式表示的有符号整数。阶码的值是 ,其中 e 是无符号数,Bias 是一个等于 (单精度是127,双精度是1023)的偏置值。
小数字段 frac 被解释为描述小数值 ,其中 ,其二进制表示为 ,也就是二进制小数点在最高有效位的左边。
尾数定义为 。这种方式叫做隐含的以 1 开头的(implied leading 1)表示,因为可以把 看成一个二进制表达式为 的数字。

情况2:非规格化的值
当 exp 阶码域为全 0 时,所表示的数是非规格化形式。
阶码值是 E = 1 - Bias
尾数的值是 M = f ,也就是小数字段的值,不包含隐含的开头的1。
非规格化数有两个用途:
1.提供了一种表示数值 0 的方法。
2.表示那些非常接近于 0.0 的数。

情况3:特殊值
当 exp 阶码域全为 1时,表示特殊值。
当小数域全为 0 时,得到的值表示无穷。当 s=0 时是 +\infty,或者当 s=1 时是 -\infty
当小数域为非零时,结果值被称为 “NaN”,即“不是一个数(Not a Number)”的缩写。

2.4.3 数字示例

图 2-35 展示了假定的 8 位浮点格式的示例,其中有 k=4 的阶码位和 n=3 的小数位。偏置量是 2^{4-1}-1=7


可以观察到最大非规格化数 和最小规格化数 之间的平滑转变。这种平滑性归功于我们对非规格化数的 E 的定义。通过将 E 定义为 1 - Bias ,而不是 -Bias ,我们可以补偿非规格化数的尾数没有隐含的开头的 1。
假如我们将图 2-35 中的值的位表达式解释为无符号整数,它们就是按升序排列的,就像它们表示的浮点数一样。这不是偶然的——IEEE格式如此设计就是为了浮点数能够使用整数排序函数来进行排序。

图 2-36 展示了一些重要的单精度和双精度浮点数的表示和数字值。依据图 2-35 中展示的 8 位格式,我们能够看出有 k 位阶码和 n 位小数的浮点表示的一般属性。


  • 值 +0.0 总有一个全为 0 的位表示。
  • 最小的正非规格化值的位表示,是由最低有效位为 1 而其他所有位为 0 构成的。它具有小数(和尾数)值 M = f = 2^{-n} 和阶码值 E = -2^{k-1} + 2。因此它的数字值是 V = 2^{-n-2^{k-1}+2}
  • 最大的非规格化值的位模式是由全为 0 的阶码字段和全为 1 的小数字段组成的。它有小数(和尾数)值 M = f = 1 - 2^{-n}(我们写成 1 - \varepsilon)和阶码值 E = -2^{k-1}+2。因此,数值 V = (1 - 2^{-n}) \times 2^{-2^{k-1}+2},这仅比最小的规格化值小一点。
  • 最小的正规格化值的位模式的阶码字段的最低有效位为 1 ,其他位全为 0 。它的尾数值 M=1,而阶码值 E=-2^{k-1}+2。因此,数值 V = 2^{-2^{k-1}+2}
  • 值 1.0 的位表示的阶码字段除了最高有效位等于 0 以外,其他位都等于 1。它的尾数值是 M=1,而它的阶码值是 E=0
  • 最大的规格化值的位表示的符号位为 0,阶码的最低有效位等于 0,其他位等于 1。它的小数值 f=1-2^{-n},尾数 M=2-2^{-n}(我们写作 2-\varepsilon)。它的阶码值 E=2^{k-1}-1,得到数值 V=(2-2^{-n}) \times 2^{2^{k-1}-1} = (1-2^{-n-1} \times 2^{2^{k-1}})

整数值转换成浮点形式,相关的区域对应于整数的低位,刚好在等于 1 的最高有效位之前停止(这个位就是隐含的开头的位 1),和浮点表示的小数部分的高位是相匹配的。

2.4.4 舍入

因为表示方法限制了浮点数的范围和精度,所以浮点运算只能近似地表示实数运算。因此,对于值x,能够找到“最接近的”匹配值x',它可以用期望的浮点形式表示出来。这就是舍入(rounding)运算的任务。

舍入方式有四种:

方式 1.40 1.60 1.50 2.50 -1.50
向偶数舍入 1 2 2 2 -2
向零舍入 1 1 1 2 -1
向下舍入 1 1 1 2 -2
向上舍入 2 2 2 3 -1

向偶数舍入是舍入到一个最接近的值。

2.4.5 浮点运算

浮点加法不具有结合性,例如,使用单精度浮点,表达式 (3.14+1e10)-1e10 求值得到 0.0 —— 因为舍入,值 3.14 会丢失。表达式 3.14+(1e10-1e10) 得出值 3.14。
浮点加法满足了单调性属性:如果 a \geq b,那么对于任何 a、b 以及 x 的值,除了 NaN,都有 x+a \geq x+b。无符号或补码加法不具有这个实数(和整数)加法的属性。
浮点乘法不具有结合性。例如,单精度浮点情况下,表达式 (1e201e20)1e-20 求值为+\infty,而 1e20(1e201e-20) 将得出 1e20。
浮点乘法在加法上不具备分配性。例如,单精度浮点情况下,表达式 1e20(1e20-1e20)求值为 0.0,而 1e201e20-1e20*1e20 会得出 NaN。
对于任何a、b和c,并且a、b和c都不等于 NaN,浮点乘法满足下列单调性:
a \geq b \ 且\ c \geq 0 \Rightarrow a *\ ^fc \geq b *\ ^fc
a \geq b \ 且\ c \leq 0 \Rightarrow a *\ ^fc \leq b *\ ^fc
只要 a \neq NaN,就有 a * \ ^fc \geq 0

2.4.6 C 语言中的浮点数

所有的 C 语言版本提供了两种不同的浮点数据类型:float 和 double。
当程序文件中出现下列句子时,GUN 编译器 GCC 会定义程序常数 INFINITY(表示+ \infty)和 NAN(表示 NaN):

#define _GUN_SOURCE 1
#include <math.h>

当在 int、float 和 double 格式之间进行强制类型转换时,程序改变数值和位模式的原则如下(假设 int 是 32 位的):

  • 从 int 转换成 float,数字不会溢出,但是可能被舍入。
  • 从 int 或 float 转换成 double,因为 double 有更大的范围(也就是可表示值的范围),也有更高的精度(也就是有效位数),所以能够保留精确的数值。
  • 从 double 转换成 float,因为范围要小一些,所以值可能溢出成 +\infty-\infty。另外,由于精确度较小,它还可能被舍入。
  • 从 float 或者 double 转换成 int,值将会向零舍入。例如,1.999 将被转换成 1,而 -1.999 将被转换成 -1。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容