C++ 建立在 C 语言基础之上,使用完全相同的数字表示和运算。
GNU 编译器套装(GNU Compiler Collection,GCC)可以基于不同的命令行选项,依照多个不同版本的C 语言规则来编译程序。
C版本 | GCC命令行选项 |
---|---|
GNU 89 | 无,-std=gnu89 |
ANSI, ISO C90 | -ansi, -std=c89 |
ISO C99 | -std=c99 |
ISO C11 | -std=c11 |
2.1 信息存储
1、字节:作为 最小的 可寻址的 内存单位。
2、虚拟内存:机器级程序将内存视为一个非常大的字节数组,称之为虚拟内存。
3、地址:内存的每一个字节都由一个唯一的数字来标识,称之为地址。
4、虚拟地址空间:所有可能的地址集合,称为虚拟地址空间。
- 只是一个展现给机器级程序的概念性映像。
5、程序对象:程序数据、指令和控制信息。
- 机器级程序并不包含关于数据类型等信息。
2.1.1 十六进制表示法
二进制:太冗长。
十进制:位模式转换很麻烦。
十六进制:以16为基数,简写hex,以0x或0X作为数字前缀。(0~9,‘A’ ~ ‘F’,不区分大小写)。
1、二进制与十六进制转换
从低位开始,每四个二进制位换算成一个十六位数,高位不足四位补0。反之亦然。
十六进制 | 1 | 7 | 3 | A | 4 | c |
---|---|---|---|---|---|---|
二进制 | 0001 | 0111 | 0011 | 1010 | 0100 | 1100 |
2、十进制与十六进制转换
(1)十进制为 2 的非负整数 n 次幂
x = 2 n , n = i + 4 j x = 2^n,n = i+4j x=2n,n=i+4j ,其中 0 ≤ i ≤ 3 0≤i≤3 0≤i≤3 ,可将 x x x 写成最高有效位为 2 i 2^i 2i ,后面跟着 j j j 个十六进制 0。
比如 x = 2048 = 2 11 , n = 11 = 3 + 4 ∗ 2 , x = 0 x 8000 x=2048=2^{11},n =11=3+4*2,x=0x8000 x=2048=211,n=11=3+4∗2,x=0x8000
(2)一般十进制转16进制
x = q ∗ 16 + r ; x = q ; x = q*16 +r;{\space} x=q; x=q∗16+r; x=q;
2.1.2 字数据大小
字长: w w w,指明指针数据的标称大小, 决定了虚拟地址空间的最大大小,范围 0 0 0 ~ 2 w − 1 2^w-1 2w−1,程序最多访问 2 w 2^w 2w 个字节。
大多数 64 位机器向后兼容 32 位机器编译的程序:
linux> gcc -m32 prog.c # 可以在32位或64位机器上正确运行
linux> gcc -m64 prog.c # 只能在64位机器上运行。
32位程序,64位程序,区别在于如何编译的,而不是运行的机器类型。
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 |
其中 unsigned 关键字的位置并没有固定的要求。
2.1.3 寻址和字节顺序
两个规则:
- 对象的地址——其所占内存位置中最小的地址
- 如何排序对象的字节
- 小端法:低字节存放在低地址
- 大端法:低字节存放在高地址
网络字节序:大端法存储
sizeof(T) 返回存储一个类型为T的对象所需要的字节数。
2.1.4 表示字符串
C 语言中字符串被编码为一个以 ‘\0’ 为结尾的字符数组。
char c[10] = {123456};
char *c_ptr = "123456";
sizeof(c); // 10
sizeof(c_ptr); // 32位:4
strlen(c); // 7
2.1.5 表示代码
不同的机器类型使用不同且不兼容的指令和编码方式,因此同样的进程,其二进制代码也是不兼容的,很少能够移植。
从机器的角度,程序只是一个字节序列,没有关于源程序的任何信息(帮助调试的辅助表除外)。
2.1.6 布尔代数简介
取反(~) | 与(&) | 或(|) | 异或(^) |
---|
1、位运算
位向量 a = [ a w − 1 , a w − 2 , . . . , a 0 ] a=[a_{w-1},a_{w-2},...,a_0] a=[aw−1,aw−2,...,a0], b = [ b w − 1 , b w − 2 , . . . , b 0 ] b=[b_{w-1},b_{w-2},...,b_0] b=[bw−1,bw−2,...,b0]。
a & b a{\space}\&{\space}b a & b 定义为一个长度为 w w w 的位向量,其中第 i 个元素等于 a i & b i , 0 ≤ i < w a_i{\space}\&{\space}b_i,{\space}0≤i<w ai & bi, 0≤i<w 。
同理,可得 a ∣ b a{\space}|{\space}b a ∣ b, a a{\space} a ^ b {\space}b b 和 ~ a a a
【应用】有时候可以高效的解决一些问题:
- 求一个数的二进制表示法中有多少个 1。
while(x)
{
count1++; x&(x-1);
}
2、运算性质
a & ( b ∣ c ) = ( a & b ) ∣ ( a & c ) a{\space}\&{\space}(b{\space}|{\space}c)=(a{\space}\&{\space}b){\space}|{\space}(a{\space}\&{\space}c) a & (b ∣ c)=(a & b) ∣ (a & c)
a ∣ ( b & c ) = ( a ∣ b ) & ( a ∣ c ) a{\space}|{\space}(b{\space}\&{\space}c)=(a{\space}|{\space}b){\space}\&{\space}(a{\space}|{\space}c) a ∣ (b & c)=(a ∣ b) & (a ∣ c)
a a a^ a = 0 a=0 a=0
( a (a (a ^ b ) b) b) ^ a = a= a= ( a (a (a ^ a ) a) a) ^ b = b b=b b=b
3、逻辑运算
非零参数都表示 TRUE,参数 0 表示 FALSE。
4、移位运算
操作数 x x x ,位表示为 [ x w − 1 , x w − 1 , . . . , x 0 ] [x_{w-1},x_{w-1},...,x_0] [xw−1,xw−1,...,x0]。
【左移】 x < < k x<<k x<<k 位表示为 [ x w − k − 1 , x w − k − 2 , . . . , x 0 , 0 , . . . , 0 ] [x_{w-k-1},x_{w-k-2},...,x_0,0,...,0] [xw−k−1,xw−k−2,...,x0,0,...,0],丢弃原来的 k k k 个最高位,并在右端补 k k k 个 0 0 0 。
【右移】分两种:
- 逻辑右移: x > > k x>>k x>>k 位表示为 [ 0 , . . . , 0 , x w − k , x w − k , . . . , x k ] [0,...,0,x_{w-k},x_{w-k},...,x_{k}] [0,...,0,xw−k,xw−k,...,xk],丢弃原来的 k k k 个最低位,并在左端补 k k k 个 0 0 0 。
- 算术右移: x > > k x>>k x>>k 位表示为 [ 0 , . . . , 0 , x w − k , x w − k , . . . , x k ] [0,...,0,x_{w-k},x_{w-k},...,x_{k}] [0,...,0,xw−k,xw−k,...,xk],丢弃原来的 k k k 个最低位,并在左端补 k k k 个 原来最高有效位的值(1或者0)。
k
k
k 的大小,原则上没有限制,因为会对
k
k
k 取字长
w
w
w 的余数
k
%
w
k{\%}w
k%w 。
不过这种行为对于 C 程序来说可能是没有保证的(不安全因素),因此,**
k
k
k 的取值应该保持小于字长
w
w
w**。
2.2 整数表示
两种表示:(1)只能表示非负数;(2)表示负数、零和正数。
符号 | 值or表达式 | 类型 | 含义 |
---|---|---|---|
B 2 T w B2T_w B2Tw | = − x w − 1 2 w − 1 + ∑ i = 0 w − 2 x i 2 i =-x_{w-1}2^{w-1}+\sum_{i=0}^{w-2}x_i2^i =−xw−12w−1+∑i=0w−2xi2i | 函数 | Binary to Two’s-complement,二进制转补码 |
B 2 U w B2U_w B2Uw | = ∑ i = 0 w − 1 x i 2 i =\sum_{i=0}^{w-1}x_i2^i =∑i=0w−1xi2i | 函数 | Binary to Unsigned,二进制转无符号数 |
U 2 B w U2B_w U2Bw | 与 B 2 U w B2U_w B2Uw 是双射 | 函数 | Unsigned to Binary,无符号数转二进制 |
U 2 T w U2T_w U2Tw | = { u , u ≤ T M a x w u − 2 w , u > T M a x w , 0 ≤ u ≤ U M a x w =\begin{cases}u,&u≤TMax_w\\u-2^w,&u>TMax_w\end{cases},{\space}{\space} 0≤u≤UMax_w ={u,u−2w,u≤TMaxwu>TMaxw, 0≤u≤UMaxw | 函数 | Unsigned to Two’s-complement,无符号数转补码 |
T 2 B w T2B_w T2Bw | 与 B 2 T w B2T_w B2Tw 是双射 | 函数 | Two’s-complement to Binary,补码转二进制 |
T 2 U w T2U_w T2Uw | = { x + 2 w , x < 0 x , x ≥ 0 , T M i n w ≤ x ≤ T M a x w =\begin{cases}x+2^w,&x<0\\x,&x≥0\end{cases},{\space}{\space}TMin_w≤x≤TMax_w ={x+2w,x,x<0x≥0, TMinw≤x≤TMaxw | 函数 | Two’s-complement to Unsigned,补码转无符号数 |
T M i n w TMin_w TMinw | − 2 w − 1 -2^{w-1} −2w−1 | 常数 | 最小补码值 |
T M a x w TMax_w TMaxw | 2 w − 1 − 1 2^{w-1}-1 2w−1−1 | 常数 | 最大补码值 |
U M a x w UMax_w UMaxw | 2 w − 1 2^w-1 2w−1 | 常数 | 最大无符号数 |
w w w 为数据表示的位数。
十进制(decimal) | 16进制(hex) | 二进制(Binary) | |
---|---|---|---|
U M a x UMax UMax | 65535 65535 65535 | F F F F FF{\space}{\space} FF FF FF | 11111111 11111111 11111111 {\space}{\space}11111111 11111111 11111111 |
T M a x TMax TMax | 32767 32767 32767 | 7 F F F 7F{\space}{\space} FF 7F FF | 01111111 11111111 01111111{\space}{\space} 11111111 01111111 11111111 |
T M i n TMin TMin | − 32768 -32768 −32768 | 80 00 80{\space}{\space} 00 80 00 | 10000000 00000000 10000000 {\space}{\space} 00000000 10000000 00000000 |
− 1 -1 −1 | − 1 -1 −1 | F F F F FF{\space}{\space} FF FF FF | 11111111 11111111 11111111 {\space}{\space} 11111111 11111111 11111111 |
0 0 0 | 0 0 0 | 00 00 00{\space}{\space} 00 00 00 | 00000000 00000000 00000000 {\space}{\space} 00000000 00000000 00000000 |
w w w | 8 | 16 | 32 | 64 |
---|---|---|---|---|
UMax | 255 | 65535 | 4,294,967,295 | 18,446,774,073,709,551,615 |
TMax | 127 | 32767 | 2,147,483,647 | 9,223,372,036,854,775,807 |
TMin | -128 | -32768 | -2,147,483,648 | -9,223,372,036,854,775,808 |
2.2.1 无符号数的编码
原理1:无符号数编码的定义
对向量 x ⃗ = [ x w − 1 , x w − 2 , . . . , x 0 ] : B 2 U w ( x ⃗ ) = ∑ i = 0 w − 1 x i 2 i \vec {x}=[x_{w-1},{\space}x_{w-2},{\space}...,x_0]:B2U_w(\vec {x})=\sum_{i=0}^{w-1}x_i2^i x=[xw−1, xw−2, ...,x0]:B2Uw(x)=∑i=0w−1xi2i.
B 2 U 4 ( [ 1011 ] ) = 1 × 2 3 + 0 × 2 2 + 1 × 2 1 + 1 × 2 0 = 11 B2U_4([1011])=1×2^3+0×2^2+1×2^1+1×2^0=11 B2U4([1011])=1×23+0×22+1×21+1×20=11
原理2:无符号数编码的唯一性
函数 B 2 U w B2U_w B2Uw 是一个双射,输入输出一一对应。
应用&意义
- 想要把字仅仅看做是位的集合而没有任何数字意义时,无符号数值是非常有用的,比如内存地址。
- 当实现模运算和多精度运算的数学包时,数字是由字的数组来表示的,无符号值也会非常有用。
2.2.2 补码编码
原理1:补码编码的定义
对向量 x ⃗ = [ x w − 1 , x w − 2 , . . . , x 0 ] : B 2 T w ( x ⃗ ) = − x w − 1 2 w − 1 + ∑ i = 0 w − 2 x i 2 i \vec {x}=[x_{w-1},{\space}x_{w-2},{\space}...,x_0]:B2T_w(\vec {x})=-x_{w-1}2^{w-1}+\sum_{i=0}^{w-2}x_i2^i x=[xw−1, xw−2, ...,x0]:B2Tw(x)=−xw−12w−1+∑i=0w−2xi2i.
B 2 T 4 ( [ 1011 ] ) = − 1 × 2 3 + 0 × 2 2 + 1 × 2 1 + 1 × 2 0 = − 8 + 0 + 2 + 1 = − 5 B2T_4([1011])=-1×2^3+0×2^2+1×2^1+1×2^0=-8+0+2+1=-5 B2T4([1011])=−1×23+0×22+1×21+1×20=−8+0+2+1=−5
原理2:补码编码的唯一性
函数 B 2 T w B2T_w B2Tw 是一个双射,输入输出一一对应。
Compare:Unsigned & Two’s-complement
- 补码的范围是不对称的: ∣ T M i n ∣ = ∣ T M a x ∣ + 1 |TMin|=|TMax|+1 ∣TMin∣=∣TMax∣+1, ∣ T m i n ∣ |Tmin| ∣Tmin∣ 没有与之对应的正数。(-1与0为一对时)
- 最大的无符号数值比补码的最大值大1: U M a x w = 2 T M a x w + 1 UMax_w=2TMax_w+1 UMaxw=2TMaxw+1
Other:
- 几乎所有机器都是要求用补码形式来表示无符号整数。
- C 库文件
<
l
i
m
i
t
.
h
>
<limit.h>
<limit.h> 定义了一组常量,来限定编译器运行的这台机器的不同整型数据类型的取值范围,例如常量
INT_MAX
、INT_MIN
、INT_UMAX
。
如果机器的int
有 w w w 位,这些常量就对应于 $ TMax_w 、 、 、TMin_x$ 和 U M a x x UMax_x UMaxx 的值。 - ISO C99 标准在文件
<
s
t
d
i
n
t
.
h
>
<stdint.h>
<stdint.h> 中引入了确定大小的整数类型类,定义了形如
intN_t
和uintN_t
,对不同的 N 值指定 N 位有符号和无符号正数。
N 的具体值与实现相关。
它们是通过一组宏来进行替换的。 - 有符号数和补码是什么关系?
答:补码是有符号数在机器中的表达方式。 - 有符号数还有反码和原码两种标准表示方法。
反码(Ones’ Complement):除了最高有效位的权是 − ( 2 w − 1 − 1 ) -(2^{w-1}-1) −(2w−1−1) ,其他部分和补码相同。
B 2 O w ( x ⃗ ) = − x w − 1 ( 2 w − 1 − 1 ) + ∑ i = 0 w − 2 x i 2 i B2O_w(\vec {x})=-x_{w-1}(2^{w-1}-1)+\sum_{i=0}^{w-2}x_i2^i B2Ow(x)=−xw−1(2w−1−1)+∑i=0w−2xi2i
原码(Sign-Magnitude):最高有效位是符号位,用来确定剩下的位应该是取正还是负。
B 2 S w ( x ⃗ ) = ( − 1 ) x w − 1 × ( ∑ i = 0 w − 2 x i 2 i ) B2S_w(\vec {x})=(-1)^{x_{w-1}}×(\sum_{i=0}^{w-2}x_i2^i) B2Sw(x)=(−1)xw−1×(∑i=0w−2xi2i)
补码 = 反码 + 1
无论如何,几乎所有的机器都是用补码表示整数,而不是用反码或原码。
2.2.3 有符号数和无符号数之间的转换
C 语言允许在各种不同的数字数据类型之间做强制类型转换。
处理同样字长的有符号数和无符号数之间相互转换的一般规则是:数值可能会改变,但是位模式不变——就是构成的0和1的组合关系不变,变的只是获得最终数值的计算方式。
原理1:补码转换为无符号数
对满足 T M i n w ≤ x ≤ T M a x w TMin_w≤x≤TMax_w TMinw≤x≤TMaxw 的 x x x 有: T 2 U x ( x ) = { x + 2 w , x < 0 x , x ≥ 0 T2U_x(x)=\begin{cases}x+2^w,&x<0\\x,&x≥0\end{cases} T2Ux(x)={x+2w,x,x<0x≥0 .
T 2 U 16 ( − 12345 ) = − 12345 + 2 16 = 53191 T2U_{16}(-12345)=-12345+2^{16}=53191 T2U16(−12345)=−12345+216=53191
原理2:无符号数转换为补码
对满足 0 ≤ u ≤ U M a x w 0≤u≤UMax_w 0≤u≤UMaxw 的 u u u 有: U 2 T w ( u ) = { u , u ≤ T M a x w u − 2 w , u > T M a x w U2T_w(u)=\begin{cases}u,&u≤TMax_w\\u-2^w,&u>TMax_w\end{cases} U2Tw(u)={u,u−2w,u≤TMaxwu>TMaxw .
C 语言中的有符号数和无符号数
- 默认是有符号的。
要创建一个无符号常量,必须加上后缀字符‘U’或者‘u’。 - 当执行一个运算时,如果一个是有符号的,另一个是无符号的,则 C 语言会隐式地将有符号参数强制转换为无符号数,并假设这两个数都是非负的,最后再指向运算。
【注意】这种隐式转换对于标准的算术运算并无多大差异,但对于像‘<’和‘>’这样的关系运算符,就会导致非直观的结果。
例如:-1 < 0U,此时会先将 -1 转换为无符号数,假设 int 为32位补码,则此表达式等价于4294967295U < 0U,结果显然是 false,但原表达式的结果是 true。
C 语言中,数据类型 size_t
一般被定义为 unsigned int
,所以在调用一些库函数时要格外的注意有符号数和补码数的隐式转换!
2.2.4 扩展一个数字的位表示
从一个较小的数据类型转换到一个较大的类型。
原理1:无符号数的零扩展(zero extension)
定义宽度为 w w w 的位向量 u ⃗ = [ u w − 1 , u w − 2 , . . . , u 0 ] \vec u=[u_{w-1},{\space}u_{w-2},{\space}...,u_0] u=[uw−1, uw−2, ...,u0] 和宽度为 w ′ w' w′ 的位向量 u ⃗ ′ = [ \vec u'=[ u′=[ 0 , . . . , 0 , 0,{\space}...,0, 0, ...,0, u w − 1 , u w − 2 , . . . , u 0 ] {\space}u_{w-1},{\space}u_{w-2},{\space}...,u_0] uw−1, uw−2, ...,u0],其中 w ′ > w w'>w w′>w。则 B 2 U w ( u ⃗ ) = B 2 U w ′ ( u ⃗ ′ ) B2U_w(\vec u)=B2U_{w'}(\vec{u}') B2Uw(u)=B2Uw′(u′)。
原理2:补码数的符号扩展(sign extension)
定义宽度为 w w w 的位向量 x ⃗ = [ x w − 1 , x w − 2 , . . . , x 0 ] \vec x=[x_{w-1},{\space}x_{w-2},{\space}...,x_0] x=[xw−1, xw−2, ...,x0] 和宽度为 w ′ w' w′ 的位向量 x ⃗ = [ \vec x=[ x=[ x w − 1 , . . . , x w − 1 , x_{w-1},{\space}...,x_{w-1}, xw−1, ...,xw−1, x w − 1 , x w − 2 , . . . , x 0 ] {\space}x_{w-1},{\space}x_{w-2},{\space}...,x_0] xw−1, xw−2, ...,x0],其中 w ′ > w w'>w w′>w。则 B 2 T w ( x ⃗ ) = B 2 T w ′ ( x ⃗ ′ ) B2T_w(\vec x)=B2T_{w'}(\vec{x}') B2Tw(x)=B2Tw′(x′)。
short sx = -12345;
unsigned short usx = sx;
int x = sx;
unsigned us = usx;
printf("sx = %d:\t",sx);
show_bytes((byte_pointer)&sx, sizeof(short));
printf("usx = %u:\t",usx);
show_bytes((byte_pointer)&usx, sizeof(unsigned short));
printf("x = %d:\t",x);
show_bytes((byte_pointer)&x, sizeof(int));
printf("ux = %u:\t",ux);
show_bytes((byte_pointer)&ux, sizeof(unsigned));
// 在采用补码表示的32位大端法机器上运行时,输出如下:
sx = -12345: cf c7
use = 53191: cf c7
x = -12345: ff ff cf c7 // 符号扩展
ux = 53191: 00 00 cf c7 // 零扩展
C 语言补码和无符号数的转换规则
先扩展位大小,再完成有符号到补码的转换。(这相对顺序会影响程序的行为的!)
2.2.5 截断一个数字的位表示
减少表示一个数字的位数。
截断一个数字可能会改变它的值——这也是溢出的一种形式。
原理1:截断无符号数
令 u ⃗ \vec u u 等于位向量 [ u w − 1 , u w − 2 , . . . , u 0 ] [u_{w-1},{\space}u_{w-2},{\space}...,u_0] [uw−1, uw−2, ...,u0],而 u ⃗ ′ \vec u' u′ 是将其截断为 k k k 位的结果: u ⃗ ′ = [ u k − 1 , u k − 2 , . . . , u 0 ] \vec u'=[u_{k-1},{\space}u_{k-2},{\space}...,u_0] u′=[uk−1, uk−2, ...,u0]。令 u = B 2 U w ( u ⃗ ) , u ⃗ ′ = B 2 U k ( u ⃗ ′ ) u=B2U_w(\vec u),{\space}\vec u'=B2U_k(\vec u') u=B2Uw(u), u′=B2Uk(u′)。则 u ′ = u m o d 2 k u'=u {\space}mod{\space}2^k u′=u mod 2k。
原理2:截断补码数值
令 x ⃗ \vec x x 等于位向量 [ x w − 1 , x w − 2 , . . . , x 0 ] [x_{w-1},{\space}x_{w-2},{\space}...,x_0] [xw−1, xw−2, ...,x0],而 x ⃗ ′ \vec x' x′ 是将其截断为 k k k 位的结果: x ⃗ ′ = [ x k − 1 , x k − 2 , . . . , x 0 ] \vec x'=[x_{k-1},{\space}x_{k-2},{\space}...,x_0] x′=[xk−1, xk−2, ...,x0]。令 x = B 2 U w ( x ⃗ ) , x ⃗ ′ = B 2 U k ( x ⃗ ′ ) x=B2U_w(\vec x),{\space}\vec x'=B2U_k(\vec x') x=B2Uw(x), x′=B2Uk(x′)。则 x ′ = U 2 T w ( x m o d 2 k ) x'=U2T_w(x {\space}mod{\space}2^k) x′=U2Tw(x mod 2k)。(先从二进制数转无符号数,然后求模实现截断,最后无符号数转补码。)
2.3 整数运算
算术运算的溢出:完整的整数结果发生了“字长膨胀”,不能放到原数据类型的字长限制中去。
符合 | 类型 | 含义 |
---|---|---|
+ w t +^t_w +wt | 操作 | 截断为 w w w 位的 补码加法 |
+ w u +^u_w +wu | 操作 | 截断为 w w w 位的 无符号数加法 |
∗ w t *^t_w ∗wt | 操作 | 截断为 w w w 位的 补码乘法 |
∗ w u *^u_w ∗wu | 操作 | 截断为 w w w 位的 无符号数乘法 |
− w t -^t_w −wt | 操作 | 截断为 w w w 位的 补码减法 |
− w u -^u_w −wu | 操作 | 截断为 w w w 位的 无符号数减法 |
2.3.1 无符号加法
原理1:无符号加法
对满足 0 ≤ x , y < 2 w 0≤x,y<2^w 0≤x,y<2w 的 x x x 和 y y y 有: x + w u y = { x + y , x + y < 2 w 正常 x + y − 2 w , 2 w ≤ x + y < 2 w + 1 溢出 x+^u_wy=\begin{cases}x+y,&x+y<2^w&\text{正常}\\x+y-2^w,&2^w≤x+y<2^{w+1}&\text{溢出} \end{cases} x+wuy={x+y,x+y−2w,x+y<2w2w≤x+y<2w+1正常溢出
C 程序,不会将溢出作为错误而发出信号。只能额外去判断👇
原理2:检测无符号数加法中的溢出
对在范围
0
≤
x
,
y
≤
U
M
a
x
w
0≤x,y≤UMax_w
0≤x,y≤UMaxw 中的
x
x
x 和
y
y
y,令
s
=
x
+
w
u
y
s=x+^u_wy
s=x+wuy,则当且仅当 s<x || s<y
时,发生了溢出。
原理3:无符号数求反
对满足 0 ≤ x < 2 w 0≤x<2^w 0≤x<2w 的任意 x x x ,其 w w w 位的无符号逆元 − w u x = { x , x = 0 2 w − x , x > 0 -^u_wx=\begin{cases}x,&x=0\\2^w-x,&x>0\end{cases} −wux={x,2w−x,x=0x>0 .
2.3.2 补码加法
原理1:补码加法
对满足 − 2 w − 1 ≤ x , y ≤ 2 w − 1 − 1 -2^{w-1}≤x,y≤2^{w-1}-1 −2w−1≤x,y≤2w−1−1 的整数 x x x 和 y y y,有: x + w t y = { x + y − 2 w , 2 w − 1 ≤ x + y 正溢出 x + y , − 2 w − 1 ≤ x + y < 2 w − 1 正常 x + y + 2 w , x + y < − 2 w − 1 负溢出 x+^t_wy=\begin{cases}x+y-2^w,&2^{w-1}≤x+y&\text{正溢出}\\x+y,&-2^{w-1}≤x+y<2^{w-1}&\text{正常}\\x+y+2^w,&x+y<-2^{w-1}&\text{负溢出}\end{cases} x+wty=⎩⎪⎨⎪⎧x+y−2w,x+y,x+y+2w,2w−1≤x+y−2w−1≤x+y<2w−1x+y<−2w−1正溢出正常负溢出 .
补码加法与无符号加法有相同的位级表示,补码加法就是将其参数转换为无符号数,执行无符号数加法,再将结果转换为补码:
x + w t y = U 2 T w ( T 2 U w ( x ) + w u T 2 U w ( y ) ) x+^t_wy=U2T_w(T2U_w(x)+^u_wT2U_w(y)) x+wty=U2Tw(T2Uw(x)+wuT2Uw(y))
原理2:检测补码加法中的溢出
对满足 T M i n w ≤ x , y ≤ T M a x w TMin_w≤x,y≤TMax_w TMinw≤x,y≤TMaxw 的 x x x 和 y y y,令 s = x + w t y s=x+^t_wy s=x+wty。
- 当且仅当 x > 0 , y > 0 x>0,y>0 x>0,y>0,但 s ≤ 0 s≤0 s≤0 时,计算 s s s 发生正溢出;
- 当且仅当 x < 0 , y < 0 x<0,y<0 x<0,y<0,但 s ≥ 0 s≥0 s≥0 时,计算 s s s 发生负溢出;
原理3:补码的非
对满足 T M i n w ≤ x ≤ T M a x w TMin_w≤x≤TMax_w TMinw≤x≤TMaxw 中 x x x,其补码的非 − w t x = { T M i n w , x = T M i n w − x , x > T M i n w -^t_wx=\begin{cases}TMin_w,&x=TMin_w\\-x,&x>TMin_w\end{cases} −wtx={TMinw,−x,x=TMinwx>TMinw.
-1和1对应,-2和2对应,0和0对应, T M i n w TMin_w TMinw 和 T M i n w TMin_w TMinw 对应。
补码的非的位级表示
- 对补码的每一位取反(0变成1,1变成0),再对结果加1。
- 将位向量分为两部分:假设
k
k
k 是位模式中最右边1的位置,对位
k
k
k 左边的所有位取反,就得到补码的非。
例如 x x x 位级表示形如 [ x w − 1 , x w − 2 , . . . , 1 , 0 , . . 0 , ] [x_{w-1},{\space}x_{w-2},{\space}...,1,0,..0,] [xw−1, xw−2, ...,1,0,..0,] ,只要 x ≠ 0 x≠0 x=0 就能找到这样的 k k k 。这个 x x x 的非写成二进制格式就是 [ x w − 1 , x w − 2 , . . . , x k + 1 , 1 , 0 , . . . , 0 ] [~x_{w-1},{\space}~x_{w-2},{\space}...,~x_{k+1},1,0,...,0] [ xw−1, xw−2, ..., xk+1,1,0,...,0]。
2.3.3 无符号乘法
原理:无符号乘法
对满足 0 ≤ x , y ≤ U M a x w 0≤x,y≤UMax_w 0≤x,y≤UMaxw 的 x x x 和 y y y 有: x ∗ w u y = ( x ⋅ y ) mod 2 w x*^u_wy=(x·y)\text{mod}{\space}2^w x∗wuy=(x⋅y)mod 2w.
- C 语言中的无符号乘法被定义为产生 w w w 位的值,就是 2 w 2w 2w 位的整数乘积的低 w w w 位表示的值;
- 将一个无符号数截断为 $w $ 位等价于计算该值模 2 w 2^w 2w .
2.3.4 补码乘法
原理1:补码乘法
对满足 T M i n w ≤ x , y ≤ T M a x w TMin_w≤x,y≤TMax_w TMinw≤x,y≤TMaxw 的 x x x 和 y y y 有: x ∗ w t y = U 2 T w ( ( x ⋅ y ) mod 2 w ) x*^t_wy=U2T_w((x·y)\text{mod}{\space}2^w) x∗wty=U2Tw((x⋅y)mod 2w).
原理2:无符号和补码乘法的位级等价性
给定长度为 w w w 的位向量 x ⃗ \vec x x 和 y ⃗ \vec y y :
用补码形式的位向量表示来定义整数 x x x 和 y y y: x = B 2 T W ( x ⃗ ) x=B2T_W(\vec x) x=B2TW(x), y = B 2 T w ( y ⃗ ) y=B2T_w(\vec y) y=B2Tw(y)。
用无符号形式的位向量表示来定义非负整数 x ′ x' x′ 和 y ′ y' y′: x ′ = B 2 U W ( x ⃗ ) x'=B2U_W(\vec x) x′=B2UW(x), y ′ = B 2 U w ( y ⃗ ) y'=B2U_w(\vec y) y′=B2Uw(y)。
则: T 2 B w ( x ∗ w t y ) = U 2 B w ( x ′ ∗ w u y ′ ) T2B_w(x*^t_wy)=U2B_w(x'*^u_wy') T2Bw(x∗wty)=U2Bw(x′∗wuy′) .
溢出:
不能用减法来检验加法是否溢出,因为阿贝尔群。
但能用除法来检验乘法是否溢出。
2.3.5 乘以常数
原理1:与 2 的幂相乘的无符号乘法
C 变量 x 和 k 是无符号数值 x x x 和 k k k,且 0 ≤ k < w 0≤k<w 0≤k<w,则 C 表达式 x < < k x<<k x<<k 产生数值 x ∗ w u 2 k x*^u_w2^k x∗wu2k .
由于固定大小的补码算术运算的位级操作与其无符号运算等价。
原理2:与 2 的幂相乘的补码乘法
C 变量 x 和 k 分别是补码值 x x x 和无符号数值 k k k,且 0 ≤ k < w 0≤k<w 0≤k<w,则 C 表达式 x < < k x<<k x<<k 产生数值 x ∗ w t 2 k x*^t_w2^k x∗wt2k .
溢出:
无论是无符号运算还是补码运算,乘以 2 的幂都可能导致溢出。
编译器优化:与常数相乘
整数乘法指令比加法指令要慢很多。编译器对此进行优化。
许多 C 语言编译器试图以移位、加法和减法的组合来消除很多整数乘以常数的情况。
例如:x *14,利用 14 = 2 3 + 2 2 + 2 1 14=2^3+2^2+2^1 14=23+22+21,编译器会将乘法重写为 ( x < < 3 ) + ( x < < 2 ) + ( x < < 1 ) (x<<3)+(x<<2)+(x<<1) (x<<3)+(x<<2)+(x<<1),将一个乘法替换为3个移位和2个加法。
2.3.6 除以 2 的幂
整数除法要比整数乘法更慢。
向下舍入:
对任何实数 a a a,定义 └ a ┘ └a┘ └a┘ 为唯一的整数 a ′ a' a′,使得 a ′ ≤ a < a ′ + 1 a'≤a<a'+1 a′≤a<a′+1 ;
- 对于 x ≥ 0 x≥0 x≥0 和 y > 0 y>0 y>0,结果会是 └ x / y ┘ └x/y┘ └x/y┘ ,向下舍入一个正值。
向上舍入:
对任何实数 a a a,定义 ┌ a ┐ ┌a┐ ┌a┐ 为唯一的整数 a ′ a' a′,使得 a ′ − 1 < a ≤ a ′ a'-1<a≤a' a′−1<a≤a′ ;
- 对于 x < 0 x<0 x<0 和 y > 0 y>0 y>0,结果会是 ┌ x / y ┐ ┌x/y┐ ┌x/y┐ ,向上舍入一个负值。
原理1:除以 2 的幂的无符号除法
C 变量 x 和 k 是无符号数值 x x x 和 k k k,且 0 ≤ k < w 0≤k<w 0≤k<w,则 C 表达式 x > > k x>>k x>>k 产生数值 └ x / 2 k ┘ └x/2^k┘ └x/2k┘ .
原理2:除以 2 的幂的补码除法,向下舍入
C 变量 x 和 k 分别是补码值 x x x 和无符号数值 k k k,且 0 ≤ k < w 0≤k<w 0≤k<w,则当执行算术移位时, C 表达式 x > > k x>>k x>>k 产生数值 └ x / 2 k ┘ └x/2^k┘ └x/2k┘ .
- 对于 x ≥ 0 x≥0 x≥0 ,变量 x x x 的最高有效位为0,所以效果与逻辑右移是一样的。
- 对于
x
<
0
x<0
x<0 :
- 对于不需要舍入的情况,结果是 └ x / 2 k ┘ └x/2^k┘ └x/2k┘ ;
- 对于需要舍入的情况,
└
x
/
2
k
┘
└x/2^k┘
└x/2k┘ 采取向下舍入,计算结果将比实际结果大1。
例如: └ − 77.123 ┘ = − 77 └-77.123┘=-77 └−77.123┘=−77。
原理2:除以 2 的幂的补码除法,设置偏置后,再向上舍入
C 变量 x 和 k 分别是补码值 x x x 和无符号数值 k k k,且 0 ≤ k < w 0≤k<w 0≤k<w,则当执行算术移位时, C 表达式 ( x + ( 1 < < k ) − 1 ) > > k (x+(1<<k)-1)>>k (x+(1<<k)−1)>>k 产生数值 ┌ x / 2 k ┐ ┌x/2^k┐ ┌x/2k┐ .
k | 偏置量 | -12340+偏置量 | >>k(二进制) | 十进制 | − 12340 / 2 k -12340/2^k −12340/2k | └ − 12340 / 2 k ┘ └-12340/2^k┘ └−12340/2k┘ |
---|---|---|---|---|---|---|
0 | 0 | 1100111111001100 | 1100111111001100 | -12340 | -12340.0 | -12340 |
1 | 1 | 1100111111001101 | 1110011111100110 | -6170 | -6170.0 | -6170 |
4 | 15 | 1100111111011011 | 1111110011111101 | -771 | -771.25 | -771 |
8 | 255 | 1101000011001011 | 1111111111010000 | -48 | -48.203125 | -48 |
对于使用算术右移的补码及其,C 表达式 (x<0 ? x+(1<<k)-1 : x)>>k
将会计算数值
x
/
2
k
x/2^k
x/2k。
注意
这种方法不能推广到除以任意常数。同乘法不同,不能用除以 2 的幂的除法来表示除以任意常数 K K K 的除法。
2.3.7 整数运算思考
- 计算机执行的**“整数”运算实际上是一种 模运算**。
- 溢出:表示数字的有限字长限制了结果的取值范围。
- 补码表示提供了一种既能表示负数也能表示正数的灵活方式,同时使用了与执行无符号算术相同的位级实现。
- 无论运算数是以无符号形式还是以补码形式表示的,都有完全一样或者非常类似的位级行为。
2.4 浮点数
浮点表示对形如 V = x × 2 y V=x×2^y V=x×2y 的有理数进行编码——一般使用 IEEE 标准 754 编码。
由于有限的精度范围,浮点数多数情况下无法被精确表示,必须向上或向下调整,IEEE 标准 754 给出了四种调整方式。
2.4.1 二进制小数
定点表示法
十进制表示: d = d m d m − 1 ⋅ ⋅ ⋅ d 1 d 0 . d − 1 d − 2 ⋅ ⋅ ⋅ d − n = ∑ i = − n m 1 0 i × d i , d i = { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 } d=d_md_{m-1}···d_1d_0.d_{-1}d_{-2}···d_{-n}=\sum^m_{i=-n}10^i×d_i,{\space}d_i = \{0,1,2,3,4,5,6,7,8,9\} d=dmdm−1⋅⋅⋅d1d0.d−1d−2⋅⋅⋅d−n=∑i=−nm10i×di, di={0,1,2,3,4,5,6,7,8,9}
12.3 4 10 = 1 × 1 0 1 + 2 × 1 0 0 + 3 × 1 0 − 1 + 4 × 1 0 − 2 = 12 34 100 12.34_{10}=1×10^1+2×10^0+3×10^{-1}+4×10^{-2}=12\frac{34}{100} 12.3410=1×101+2×100+3×10−1+4×10−2=1210034
二进制表示: b = b m b m − 1 ⋅ ⋅ ⋅ b 1 b 0 . b − 1 b − 2 ⋅ ⋅ ⋅ b − n = ∑ i = − n m 2 i × b i , b i = { 0 , 1 } b=b_mb_{m-1}···b_1b_0.b_{-1}b_{-2}···b_{-n}=\sum^m_{i=-n}2^i×b_i,{\space}b_i = \{0,1\} b=bmbm−1⋅⋅⋅b1b0.b−1b−2⋅⋅⋅b−n=∑i=−nm2i×bi, bi={0,1}
101.1 1 2 = 1 × 2 2 + 0 × 2 1 + 1 × 2 0 + 1 × 2 − 1 + 1 × 2 − 2 = 4 + 0 + 1 + 1 2 + 1 4 = 5 3 4 101.11_2=1×2^2+0×2^1+1×2^0+1×2^{-1}+1×2^{-2}=4+0+1+\frac{1}{2}+\frac{1}{4}=5\frac{3}{4} 101.112=1×22+0×21+1×20+1×2−1+1×2−2=4+0+1+21+41=543
-
二进制小数点向左移动一位相当于这个数被 2 除;
-
二进制小数点向右移动一位相当于这个数乘 2;
形如 0.11 ⋅ ⋅ ⋅ 1 2 0.11···1_2 0.11⋅⋅⋅12 的数表示的是刚好小于 1 的数,可以简单表示为 1.0 − ϵ 1.0-\epsilon 1.0−ϵ ,然而必须考虑有限长度的编码,小数的二进制表示法只能精确表示那些能够被写成 x × 2 y x×2^y x×2y 的数,其他值只能够被近似的表示。所以增加编码长度可以增加表达的精度。
表示 | 值 | 十进制 | 表示 | 值 | 十进制 |
---|---|---|---|---|---|
0. 0 2 0.0_2 0.02 | 0 2 \frac{0}{2} 20 | 0.0 | 0.0011 0 2 0.00110_2 0.001102 | 6 32 \frac{6}{32} 326 | 0.1875 |
0.0 1 2 0.01_2 0.012 | 1 4 \frac{1}{4} 41 | 0.25 | 0.00110 1 2 0.001101_2 0.0011012 | 13 64 \frac{13}{64} 6413 | 0.203125 |
0.01 0 2 0.010_2 0.0102 | 2 8 \frac{2}{8} 82 | 0.25 | 0.001101 0 2 0.0011010_2 0.00110102 | 26 128 \frac{26}{128} 12826 | 0.203125 |
0.001 1 2 0.0011_2 0.00112 | 3 16 \frac{3}{16} 163 | 0.1875 | 0.0011001 1 2 0.00110011_2 0.001100112 | 51 256 \frac{51}{256} 25651 | 0.19921875 |
2.4.2 IEEE 浮点表示
IEEE 浮点标准用 ** V = ( − 1 ) s × M × 2 E V=(-1)^s×M×2^E V=(−1)s×M×2E ** 的形式来表示一个数:
符号 | 名称 | 位表示 | 解释 |
---|---|---|---|
s s s | 符号 | 一个单独的符号位 | 决定这数是负数(
s
=
1
s=1
s=1)还是正数(
s
=
0
s=0
s=0); 而对于数值 0 的符号位解释作为特殊情况处理。 |
M M M | 尾数 | n n n 位小数字段 frac = f n − 1 ⋅ ⋅ ⋅ f 1 f 0 \text{frac}=f_{n-1}···f_1f_0 frac=fn−1⋅⋅⋅f1f0 | M M M 是一个二进制小数,范围是 1~2- ϵ \epsilon ϵ,或者 0~1- ϵ \epsilon ϵ。 |
E E E | 阶码 | k k k 位阶码字段 exp = e k − 1 ⋅ ⋅ ⋅ e 1 e 0 \text{exp}=e_{k-1}···e_1e_0 exp=ek−1⋅⋅⋅e1e0 | E E E 对 M M M 加权,权重是 2 的 E E E 次幂(可能是负数)。 |
单精度 f l o a t float float 浮点格式: s = 1 , k = 8 , n = 23 s=1,{\space}k=8,{\space}n=23 s=1, k=8, n=23 得到一个 32 位的表示;
双精度 d o u b l e double double 浮点格式: s = 1 , k = 11 , n = 52 s=1,{\space}k=11,{\space}n=52 s=1, k=11, n=52 得到一个 64 位的表示;
根据 exp 的值,被编码值分为三种不同的情况:
情况1:规格化的值:当 exp 的位模式不全为 0 或 1 时
最普遍的情况。
阶码字段 exp 解释为以偏置形式表示的有符号整数: E = e − B i a s E=e-Bias E=e−Bias ,其中:
- e e e 的位表示为 e k − 1 ⋅ ⋅ ⋅ e 1 e 0 e_{k-1}···e_1e_0 ek−1⋅⋅⋅e1e0 ;
- B i a s = 2 k − 1 − 1 Bias = 2^{k-1}-1 Bias=2k−1−1 (单精度是 127 127 127,双精度是 1023 1023 1023)
- 范围:单精度是 − 126 -126 −126 ~ + 127 +127 +127,双精度是 − 1022 -1022 −1022 ~ + 1023 +1023 +1023.
小数字段 frac 被解释为描述小数值 f ( 0 ≤ f < 1 ) f(0≤f<1) f(0≤f<1) ,其二进制表示为 0. f n − 1 ⋅ ⋅ ⋅ f 1 f 0 0.f_{n-1}···f_1f_0 0.fn−1⋅⋅⋅f1f0 :
- 由于第一位总是等于1,所以可以隐式的表示它:尾数 M = 1 + f = 1. f n − 1 ⋅ ⋅ ⋅ f 1 f 0 M = 1+f = 1.f_{n-1}···f_1f_0 M=1+f=1.fn−1⋅⋅⋅f1f0 .
情况2:非规格化的值:当 exp 的位模式全为 0 时
阶码 E = 1 − B i a s E=1-Bias E=1−Bias
尾数 M = f M = f M=f ,也就是小数字段 f r a c frac frac 的值,不包含隐含的开头的 1。
非规格化数的两个用途:
- 提供一种表示数值 0 的方法。
因为在规格数下, M ≥ 1 M≥1 M≥1,因此不能表示 0。
+0.0 的浮点表示的位模式为全 0;
-0.0 的浮点表示的位模式除了符号位 s 为 1,其他全为 0 。 - 表示那些非常接近于 0.0 的数,它们提供了一种属性:逐渐溢出,可能的数值分布均匀地接近于 0.0。
情况3:特殊值:当 exp 的位模式全为 1 时
当小数字段全为 0 时,得到的值表示无穷:
- 当 s = 0 s=0 s=0 时,是正无穷;
- 当 s = 1 s=1 s=1 时,是负无穷;
当小数字段不全为 0 时,得到的值称为 N a N NaN NaN,不是一个数(Not a Number),如计算 − 1 , ∞ − ∞ \sqrt{-1},∞-∞ −1,∞−∞。
一般性归纳
描述 | exp | frac | V = M × 2 E V=M×2^E V=M×2E | 单精度k=8,n=23 | 双精度k=11,n=52 |
---|---|---|---|---|---|
0 | 00···00 | 0···00 | 0 | 0 | 0 |
最小非规格化数 | 00···00 | 0···01 |
M
=
f
=
2
−
n
M=f=2^{-n}
M=f=2−n E = − 2 k − 1 + 2 E=-2^{k-1}+2 E=−2k−1+2 V = 2 − n × 2 − 2 k − 1 + 2 V=2^{-n}×2^{-2^{k-1}+2} V=2−n×2−2k−1+2 | 2 − 23 × 2 − 126 2^{-23}×2^{-126} 2−23×2−126 | 2 − 52 × 2 − 1022 2^{-52}×2^{-1022} 2−52×2−1022 |
最大非规格化数 | 00···00 | 1···11 |
M
=
f
=
1
−
2
−
n
=
1
−
ϵ
M=f=1-2^{-n}=1-\epsilon
M=f=1−2−n=1−ϵ E = − 2 k − 1 + 2 E=-2^{k-1}+2 E=−2k−1+2 V = ( 1 − ϵ ) × 2 − 2 k − 1 + 2 V=(1-\epsilon)×2^{-2^{k-1}+2} V=(1−ϵ)×2−2k−1+2 | ( 1 − ϵ ) × 2 − 126 (1-\epsilon)×2^{-126} (1−ϵ)×2−126 | ( 1 − ϵ ) × 2 − 1022 (1-\epsilon)×2^{-1022} (1−ϵ)×2−1022 |
最小规格化数 | 00···01 | 0···00 |
M
=
1
M=1
M=1 E = − 2 k − 1 + 2 E=-2^{k-1}+2 E=−2k−1+2 V = 1 × 2 − 2 k − 1 + 2 V=1×2^{-2^{k-1}+2} V=1×2−2k−1+2 | 1 × 2 − 126 1×2^{-126} 1×2−126 | 1 × 2 − 1022 1×2^{-1022} 1×2−1022 |
1 | 01···11 | 0···00 |
M
=
1
M=1
M=1 E = 0 E=0 E=0 V = 1 × 2 0 = 1 V=1×2^0=1 V=1×20=1 | 1 × 2 0 1×2^0 1×20 | 1 × 2 0 1×2^0 1×20 |
最大规格化数 | 11···10 | 1···11 |
M
=
1
+
f
=
1
+
1
−
2
−
n
M=1+f=1+1-2^{-n}
M=1+f=1+1−2−n = 2 − 2 − n = 2 − ϵ =2-2^{-n}=2-\epsilon =2−2−n=2−ϵ E = 2 k − 1 − 1 E=2^{k-1}-1 E=2k−1−1 V = ( 2 − 2 − n ) × 2 − 2 k − 1 − 1 V=(2-2^{-n})×2^{-2^{k-1}-1} V=(2−2−n)×2−2k−1−1 = ( 2 − ϵ ) × 2 − 2 k − 1 − 1 =(2-\epsilon)×2^{-2^{k-1}-1} =(2−ϵ)×2−2k−1−1 | ( 2 − ϵ ) × 2 127 (2-\epsilon)×2^{127} (2−ϵ)×2127 | ( 2 − ϵ ) × 2 1023 (2-\epsilon)×2^{1023} (2−ϵ)×21023 |
- 值 + 0.0 +0.0 +0.0 总有一个全为 0 的位表示。
- 最小的正非规格化值的位表示:
示例:8 位浮点格式的非赋值(k=3, n=3)
描述 | 位表示 | 指数 | 小数 | 值 | |||||
---|---|---|---|---|---|---|---|---|---|
e e e | E E E | 2 E 2^E 2E | f f f | M M M | 2 E × M 2^E×M 2E×M | V V V | 十进制 | ||
0 | 0 0000 000 | 0 | -6 | 1 64 \frac{1}{64} 641 | 0 8 \frac{0}{8} 80 | 0 8 \frac{0}{8} 80 | 0 512 \frac{0}{512} 5120 | 0 | 0.0 |
最小的非规格化数 | 0 0000 001 | 0 | -6 | 1 64 \frac{1}{64} 641 | 1 8 \frac{1}{8} 81 | 1 8 \frac{1}{8} 81 | 1 512 \frac{1}{512} 5121 | 1 512 \frac{1}{512} 5121 | 0.001953 |
0 0000 010 | 0 | -6 | 1 64 \frac{1}{64} 641 | 2 8 \frac{2}{8} 82 | 2 8 \frac{2}{8} 82 | 2 512 \frac{2}{512} 5122 | 1 256 \frac{1}{256} 2561 | 0.003906 | |
0 0000 011 | 0 | -6 | 1 64 \frac{1}{64} 641 | 3 8 \frac{3}{8} 83 | 3 8 \frac{3}{8} 83 | 3 512 \frac{3}{512} 5123 | 3 512 \frac{3}{512} 5123 | 0.005859 | |
…… | …… | …… | …… | …… | …… | …… | …… | …… | …… |
最大的非规格化数 | 0 0000 111 | 0 | -6 | 1 64 \frac{1}{64} 641 | 7 8 \frac{7}{8} 87 | 7 8 \frac{7}{8} 87 | 7 512 \frac{7}{512} 5127 | 7 512 \frac{7}{512} 5127 | 0.013672 |
最小的规格化数 | 0 0001 000 | 1 | -6 | 1 64 \frac{1}{64} 641 | 0 8 \frac{0}{8} 80 | 8 8 \frac{8}{8} 88 | 8 512 \frac{8}{512} 5128 | 1 64 \frac{1}{64} 641 | 0.015625 |
0 0001 001 | 1 | -6 | 1 64 \frac{1}{64} 641 | 1 8 \frac{1}{8} 81 | 9 8 \frac{9}{8} 89 | 9 512 \frac{9}{512} 5129 | 9 512 \frac{9}{512} 5129 | 0.017578 | |
…… | …… | …… | …… | …… | …… | …… | …… | …… | …… |
0 0110 110 | 6 | -1 | 1 2 \frac{1}{2} 21 | 6 8 \frac{6}{8} 86 | 14 8 \frac{14}{8} 814 | 14 16 \frac{14}{16} 1614 | 7 8 \frac{7}{8} 87 | 0.875 | |
0 0110 111 | 6 | -1 | 1 2 \frac{1}{2} 21 | 7 8 \frac{7}{8} 87 | 15 8 \frac{15}{8} 815 | 15 16 \frac{15}{16} 1615 | 15 16 \frac{15}{16} 1615 | 0.9375 | |
1 | 0 0111 000 | 7 | 0 | 1 | 0 8 \frac{0}{8} 80 | 8 8 \frac{8}{8} 88 | 8 8 \frac{8}{8} 88 | 1 1 1 | 1.0 |
0 0111 001 | 7 | 0 | 1 | 1 8 \frac{1}{8} 81 | 9 8 \frac{9}{8} 89 | 9 8 \frac{9}{8} 89 | 9 8 \frac{9}{8} 89 | 1.125 | |
0 0111 010 | 7 | 0 | 1 | 2 8 \frac{2}{8} 82 | 10 8 \frac{10}{8} 810 | 10 8 \frac{10}{8} 810 | 5 4 \frac{5}{4} 45 | 1.25 | |
…… | …… | …… | …… | …… | …… | …… | …… | …… | …… |
0 1110 110 | 14 | 7 | 128 | 6 8 \frac{6}{8} 86 | 14 8 \frac{14}{8} 814 | 1792 8 \frac{1792}{8} 81792 | 224 | 224.0 | |
最大的规格化数 | 0 1110 111 | 14 | 7 | 128 | 7 8 \frac{7}{8} 87 | 15 8 \frac{15}{8} 815 | 1920 8 \frac{1920}{8} 81920 | 240 | 240.0 |
无穷大 | 0 1111 000 | —— | —— | —— | —— | —— | —— | ∞ ∞ ∞ | —— |
- 最大非规格化数 7 512 \frac{7}{512} 5127 和最小规格化数 8 512 \frac{8}{512} 5128 之间的转变是平滑的,这归功于对非规格化数的 E E E 的定义:通过将 E E E 定义为 1 − B i a s 1-Bias 1−Bias ,而不是 − B i a s -Bias −Bias ,我们可以补偿非规格化数的尾数没有隐含的开头的1。
- 通过增加阶码,可以获得更大的规格化值,通过 1.0 后得到最大的规格化数,其 E = 7 E=7 E=7,得到一个权 2 E = 128 2^E=128 2E=128,小数等于 7 8 \frac{7}{8} 87 得到尾数 M = 15 8 M=\frac{15}{8} M=815 。此时,数值 V = 240 V=240 V=240 ,再增加阶码,就会溢出到 + ∞ +∞ +∞ 。
- 将上表“位表示”一列解释为无符号整数,它们是升序排列的,就像它们表示的浮点数一样。IEEE 格式如此设计就是为了浮点数能够使用整数排序函数来进行排序。
- 然而处理负数时,因为它们第一位是1,且是按照降序出现。……(?)
练习:整数值转换成浮点数
整数:12345
二进制表示:[11000000111001]
将二进制小数点左移13位:
1.100000011100
1
2
×
2
13
1.1000000111001_2×2^{13}
1.10000001110012×213
用 IEEE 单精度形式编码:丢弃开头的1,并且在末尾增加 23-13=10 个 0 来构造小数字段 frac,得到 [10000001110010000000000]
构造阶码字段 exp :13 加偏置量127等于140,二进制表示为 [10001100]
加上符号位 0,最后结果:[0100 0110 0100 0000 1110 0100 0000 0000]
2.4.3 舍入
IEEE 浮点格式定义了四种不同的舍入方式,默认方式是:向偶数舍入,其他三种可用于计算上界和下界。
方式 | 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 |
向偶数舍入:将数字向上或者向下舍入:
- 首先,遵从向最接近的值舍入原则;
- 然后,到上下界距离相等时,也即比保留位低一级的位值为 5,此时遵从使结果的最低有效数字是偶数。(1.5 和 2.5 都舍入成 2)
为什么用向偶数舍入?
- 可以避免统计偏差。
2.4.4 浮点运算
1、浮点数加法
浮点数加法不具有结合性,不同的结合可能会产生不同的值,产生一定的差异!!!
- 因此,编译器倾向于保守,避免任何对功能产生影响的优化。
浮点数加法满足单调性:
- 如果 a ≥ b a≥b a≥b ,那么对于任何 a 、 b a、b a、b 以及 x x x 的值,除了 N a N NaN NaN ,都有 x + a ≥ x + b x+a ≥ x+b x+a≥x+b 。
- 补充:无符号或补码加法不具有单调性属性。
2、浮点数乘法
浮点数乘法是封闭的(虽然可能产生无穷大或 N a N NaN NaN),是可交换的。
浮点数乘法也不具有结合性:因为可能会发生溢出,或者舍入而失去精度。
单精度浮点情况下: ( 1 e 20 ∗ 1 e 20 ) ∗ 1 e − 20 = + ∞ (1e20*1e20)*1e-20 = +∞ (1e20∗1e20)∗1e−20=+∞,然而 1 e 20 ∗ ( 1 e 20 ∗ 1 e − 20 ) = 1 e 20 1e20*(1e20*1e-20)=1e20 1e20∗(1e20∗1e−20)=1e20
浮点数乘法也不具有分配性:
单精度浮点情况下: 1 e 20 ∗ ( 1 e 20 − 1 e 20 ) = 0.0 1e20*(1e20-1e20) = 0.0 1e20∗(1e20−1e20)=0.0,然而 1 e 20 ∗ 1 e 20 − 1 e 20 ∗ 1 e 20 ) = N a N 1e20*1e20-1e20*1e20)=NaN 1e20∗1e20−1e20∗1e20)=NaN
浮点数乘法满足单调性:
- 对于任何 a 、 b a、b a、b 和 c c c ,并且 a 、 b a、b a、b 和 c c c 都不等于 N a N NaN NaN ,则 { a ≥ b 且 c ≥ 0 ⇒ a ∗ f c ≥ b ∗ f c a ≥ b 且 c ≤ 0 ⇒ a ∗ f c ≤ b ∗ f c \begin{cases}a≥b\text{且}c≥0{\space}{\space}⇒{\space}{\space}a*^{\text{f}}c≥b*^{\text{f}}c\\a≥b\text{且}c≤0{\space}{\space}⇒{\space}{\space}a*^{\text{f}}c≤b*^{\text{f}}c\end{cases} {a≥b且c≥0 ⇒ a∗fc≥b∗fca≥b且c≤0 ⇒ a∗fc≤b∗fc
- 只要 a ≠ N a N a≠NaN a=NaN ,就有 a ∗ f a ≥ 0 a*^{\text{f}}a≥0 a∗fa≥0
- 补充:无符号或补码乘法不具有单调性属性。
2.4.5 C 语言中的浮点数
在 int、float 和 double 格式之间进行强制类型转换时,程序改变数值和位模式的原则如下(设 int 是32位):
-
从 int 到 float ,数字不会溢出,但是可能被舍入,因为小数字段 frac 只有23位长。
-
从 int 或 float 转换到 double,因为 double 有更大的范围,也有更高的进度,所以能够保留精确的数值。
-
从 double 转换到 float,因为 float 表示的范围要小一些,值可能溢出成 + ∞ +∞ +∞ 或 − ∞ -∞ −∞ ;且精度也较小,还可能被舍入。
-
从 float 或 double 转换到 int,值将会向零舍入为整数,如1.999 转换成 1。
值可能会溢出:但C 语言没有对这一情况指定固定的结果。与 Intel 兼容的微处理器指定位模式 [10···00] (字长为 w w w 时的 T M i n w TMin_w TMinw) 为整数不确定值。
一个浮点数到整数的转换,如果不能为浮点数找到一个合理的整数近似值,就会产生这样一个值。
例如:表达式(int)+1e10
会得到 -21483648,即从一个正值变成了一个负值。【注意】将大的浮点数转换成整数是一种常见的程序错误来源。
2.5 小结
(待刷第二遍总结)