概述
计算机中不管是任何形式的数据最终都表示为二进制数,也即只有0和1组成的数,又称为机器数。
为了便于表示和计算各类型的数值,又约定了有如有符号数、无符号数、各种码制等。
一般而言,机器数分为有符号数和无符号数。
有符号数,最高位表示的是正/负,其他位用于表示数值,无符号数仅表示正数。它们又有两种约定,一种是表示纯小数,另一种是表示纯整数,前者约定小数点位于最高位之前,后者约定小数点位于最高位之后,但计算机中并不真正的保存小数点,都是人为约定的。
而对于含小数的整数,则使用定点数和浮点数来表示。
本文的介绍将假定你具备二进制与十进制的转换及其运算规则,如果对进制转换不熟悉可以去看该文:数各进制间的转换。
机器数的表示
对于无符号数而言,运算相对简单,但有符号数则有点不同,为了便于运算实现,有符号数又可以采用原码、反码、补码和移码等不同码制的编码方式。有符号数最高位仅表示正负,一般地 0表示正,1表示负。 下面假定机器字长表示为n,均为8位。
原码
原码表示法,即数值被转换为二进制值的原始值。
若X是纯整数,则其定义:
X
原
=
{
X
0
≤
X
≤
2
n
−
1
−
1
2
n
−
1
+
∣
X
∣
−
(
2
n
−
1
−
1
)
≤
X
≤
0
X_原 = \begin {cases} X &\ 0 \leq X \leq 2^{n-1}-1\\ 2^{n-1}+|X| &\ -(2^{n-1}-1)\leq X \leq 0 \end{cases}
X原={X2n−1+∣X∣ 0≤X≤2n−1−1 −(2n−1−1)≤X≤0
若X是纯小数,则其定义:
X
原
=
{
X
0
≤
X
<
1
2
0
+
∣
X
∣
−
1
<
X
≤
0
X_原 = \begin {cases} X &\ 0 \leq X < 1\\ 2^0+|X| &\ -1 < X \leq 0 \end{cases}
X原={X20+∣X∣ 0≤X<1 −1<X≤0
下面是一些示例(其中小数点就是约定小数点的位置):
+
1
原
=
0
0000001
−
1
原
=
1
0000000
+
12
7
原
=
0
1111111
−
12
7
原
=
1
1111111
+
2
8
原
=
0
0011100
−
2
8
原
=
1
0011100
+
0.
5
原
=
0
.
1000000
−
0.
5
原
=
1
.
1000000
+1_原 = 0 \quad 0000001 \quad\quad\quad\quad -1_原 = 1 \quad 0000000 \\ +127_原 = 0 \quad 1111111 \quad\quad\quad\quad -127_原 = 1 \quad 1111111 \\ +28_原 = 0 \quad 0011100 \quad\quad\quad\quad -28_原 = 1 \quad 0011100 \\ +0.5_原 = 0 \quad.1000000 \quad\quad\quad\quad -0.5_原 = 1 \quad .1000000
+1原=00000001−1原=10000000+127原=01111111−127原=11111111+28原=00011100−28原=10011100+0.5原=0.1000000−0.5原=1.1000000
反码
反码十分简单,它既是在原码不改变符号位的基础上,对原码的正数不做任何改变,而对负数的每一位取反。
下面是一些示例:
+
1
反
=
0
0000001
−
1
反
=
1
1111110
+
12
7
反
=
0
1111111
−
12
7
反
=
1
0000000
+
2
8
反
=
0
0011100
−
2
8
反
=
1
1100011
+
0.
5
反
=
0
.
1000000
−
0.
5
反
=
1
.
0111111
+1_反 = 0 \quad 0000001 \quad\quad\quad\quad -1_反 = 1 \quad 1111110 \\ +127_反 = 0 \quad 1111111 \quad\quad\quad\quad -127_反 = 1 \quad 0000000 \\ +28_反 = 0 \quad 0011100 \quad\quad\quad\quad -28_反 = 1 \quad 1100011 \\ +0.5_反 = 0 \quad.1000000 \quad\quad\quad\quad -0.5_反 = 1 \quad .0111111
+1反=00000001−1反=11111110+127反=01111111−127反=10000000+28反=00011100−28反=11100011+0.5反=0.1000000−0.5反=1.0111111
一个特殊的情况是,0的反码有两种形式
0
反
=
0000000
=
11111111
0_反=0000000 = 11111111
0反=0000000=11111111。
补码
补码的正数和反码原码一致,但不同的是,它对负数的表示是在反码的基础上加1。
下面是一些示例:
+
1
补
=
0
0000001
−
1
补
=
1
1111111
+
12
7
补
=
0
1111111
−
12
7
补
=
1
0000001
+
2
8
补
=
0
0011100
−
2
8
补
=
1
1100100
+
0.
5
补
=
0
.
1000000
−
0.
5
补
=
1
.
1000000
+1_补 = 0 \quad 0000001 \quad\quad\quad\quad -1_补 = 1 \quad 1111111 \\ +127_补 = 0 \quad 1111111 \quad\quad\quad\quad -127_补 = 1 \quad 0000001 \\ +28_补 = 0 \quad 0011100 \quad\quad\quad\quad -28_补 = 1 \quad 1100100 \\ +0.5_补 = 0 \quad.1000000 \quad\quad\quad\quad -0.5_补 = 1 \quad .1000000
+1补=00000001−1补=11111111+127补=01111111−127补=10000001+28补=00011100−28补=11100100+0.5补=0.1000000−0.5补=1.1000000
这样一来,补码的0就存在唯一的表示值
0
补
=
00000000
0_补=00000000
0补=00000000。
移码
移码即是在数X的基础上,加一个偏移量来表示,一般是用于浮点数的阶码。它有点特殊,所以看看它的定义,规定偏移量为 2 n − 1 2^{n-1} 2n−1 :
若X是纯整数则其定义:
X
移
=
2
n
−
1
+
X
−
2
n
−
1
≤
X
<
2
n
−
1
X_移 = 2^{n-1} + X \quad\quad\quad-2^{n-1} \leq X < 2^{n-1}
X移=2n−1+X−2n−1≤X<2n−1
若X是纯小数则其定义:
X
补
=
1
+
X
−
1
≤
X
<
1
X_补 = 1 + X \quad\quad\quad\quad -1 \leq X < 1
X补=1+X−1≤X<1
下面是一些示例:
+
1
移
=
1
0000001
−
1
移
=
0
1111111
+
12
7
移
=
1
1111111
−
12
7
移
=
0
0000001
+
2
8
移
=
1
0011100
−
2
8
移
=
0
1100100
+
0
移
=
1
0000000
−
0
移
=
1
0000000
+1_移 = 1 \quad 0000001 \quad\quad\quad\quad -1_移 = 0 \quad 1111111 \\ +127_移 = 1 \quad 1111111 \quad\quad\quad\quad -127_移 = 0 \quad 0000001 \\ +28_移 = 1 \quad 0011100 \quad\quad\quad\quad -28_移 = 0 \quad 1100100 \\ +0_移 = 1 \quad 0000000 \quad\quad\quad\quad -0_移 = 1 \quad 0000000
+1移=10000001−1移=01111111+127移=11111111−127移=00000001+28移=10011100−28移=01100100+0移=10000000−0移=10000000
在偏移量为
2
n
−
1
2^{n-1}
2n−1的情况下,其实移码只需要将补码的符号位取反即可。
定点数和浮点数
数在计算机中有两种表现形式,一种是约定小数点的位置不变,称之为定点数,另一种的位置不固定,又叫做浮点数。
定点数
小数点位置不变的数就是定点数,前面提到的纯小数或纯整数的表现方式都是定点数,它们都是约定小数点在最高有效数值位之前或最低有效数之后。
如机器字长为n,各种码制下带符号数的表示范围如下:
码制 | 定点整数 | 定点小数 |
---|---|---|
原码 | − ( 2 n − 1 − 1 ) -(2^{n-1}-1) −(2n−1−1) ~ + ( 2 n − 1 − 1 ) +(2^{n-1}-1) +(2n−1−1) | − ( 1 − 2 − ( n − 1 ) ) -(1-2^{-(n-1)}) −(1−2−(n−1)) ~ + ( 1 − 2 − ( n − 1 ) ) +(1-2^{-(n-1)}) +(1−2−(n−1)) |
反码 | − ( 2 n − 1 − 1 ) -(2^{n-1}-1) −(2n−1−1) ~ + ( 2 n − 1 − 1 ) +(2^{n-1}-1) +(2n−1−1) | − ( 1 − 2 − ( n − 1 ) ) -(1-2^{-(n-1)}) −(1−2−(n−1)) ~ + ( 1 − 2 − ( n − 1 ) ) +(1-2^{-(n-1)}) +(1−2−(n−1)) |
补码 | − 2 n − 1 -2^{n-1} −2n−1 ~ + ( 2 n − 1 − 1 ) +(2^{n-1}- 1) +(2n−1−1) | − 1 -1 −1 ~ + ( 1 − 2 − ( n − 1 ) ) +(1-2^{-( n - 1 )}) +(1−2−(n−1)) |
移码 | − 2 n − 1 -2^{ n - 1 } −2n−1 ~ + ( 2 n − 1 − 1 ) +(2^{ n - 1 } -1 ) +(2n−1−1) | − 1 -1 −1 ~ + ( 1 − 2 − ( n − 1 ) ) +(1-2^{-( n - 1 )}) +(1−2−(n−1)) |
可以看见,定点数的补码和移码都能表示 2 n 2^n 2n个数,而原码和反码由于0占用了两个编码所以仅能表示 2 n − 1 2^n-1 2n−1个数。
浮点数
小数点位置不固定的数就是浮点数,它的思想是基于在十进制中一个数可以写成很多表现形式的原理。
例如83.125可以写成 1 0 3 × 0.083125 10^3 \times 0.083125 103×0.083125或 1 0 4 × 0.0083125 10^4 \times 0.0083125 104×0.0083125等,同理,一个二进制数也可以写成多种表现形式,如二进制数1011.10101可以写成 2 4 × 0.101110101 2^4 \times 0.101110101 24×0.101110101、 2 5 × 0.0101110101 2^5 \times 0.0101110101 25×0.0101110101、 2 6 × 0.00101110101 2^6 \times 0.00101110101 26×0.00101110101等。很明显一个数的浮点形式的表示并不是唯一的,当小数点位置变化时,阶码也可以相应的改变,因此可以用多个浮点形式来表达同一个数。
由此得出规律,一个二进制数N可以表示为 N = 2 E × F N = 2^E \times F N=2E×F,其中E被称为阶码,F称为尾数。用阶码和尾数来表示的数就被称为浮点数,这种表示法又叫做浮点表示法。
在浮点表示法中,阶码为带符号的整数,尾数为带符号的纯小数。其格式如下:
浮点数的表示范围主要由阶码决定,其精度则由尾数决定,为了充分利用尾数,通常将尾数的绝对值限定在 [ 0.5 , 1 ] [0.5,1] [0.5,1]区间。当尾数用补码表示时有以下两点注意:
-
若尾数 M ≥ 0,则规格化尾数形式为 M = 0.1...... . ( 2 ) M = 0.1......._{(2)} M=0.1.......(2)
即其区间在 [ 0.5 , 1 ] [0.5 , 1 ] [0.5,1]
-
若尾数 M < 0,则规格化尾数形式为 M = 1.0...... . ( 2 ) M = 1.0......._{(2)} M=1.0.......(2)
即其区间在 [ − 1 , − 0.5 ] [-1, -0.5] [−1,−0.5]
上面提到的尾数形式都是二进制的开头。
如果浮点数的阶码(包括1位阶符)用R位移码表示,尾数(包括1位数符)用M位的补码表示,则这种浮点数的表示的最大正数: + ( 1 − 2 − M + 1 ) × 2 2 R − 1 − 1 +(1 - 2^{-M+1}) \times 2^{2^{R-1}-1} +(1−2−M+1)×22R−1−1;最小负数: − 1 × 2 2 R − 1 − 1 -1 \times 2^{2^{R-1}-1} −1×22R−1−1。
IEEE 754 浮点数标准
从上面就可以知道浮点数有很多种实现的方法,但现行浮点数最主要的标准就是IEEE 754。
其表示形式为: ( − 1 ) S × 2 E ( b 0 b 1 b 2 … b p − 1 ) (-1)^S \times 2^E(b_0 b_1 b_2 \dots b_{p-1}) (−1)S×2E(b0b1b2…bp−1),其中:
- ( − 1 ) S (-1)^S (−1)S为该浮点数的数符,当S为0是表示正,反之则为负数;
- E为指数(阶码),它用移码表示;
- 2 E ( b 0 b 1 b 2 … b p − 1 ) 2^E(b_0 b_1 b_2 \dots b_{p-1}) 2E(b0b1b2…bp−1)为尾数,长度为P位,用原码表示;
目前,计算机中主要用三种形式的浮点数:
参数 | 单精度浮点数 | 双精度浮点数 | 扩充精度浮点数 |
---|---|---|---|
浮点数字长 | 32 | 64 | 80 |
尾数长度P | 23 | 52 | 64 |
符号位S | 1 | 1 | 1 |
指数长度E | 8 | 11 | 15 |
指数范围 | -126 ~ 127 | -1022 ~ 1023 | -16382 ~ 16383 |
指数偏移量 | +127 | +1023 | +16383 |
可表示范围 | 1 0 − 38 10^{-38} 10−38 ~ 1 0 38 10^{38} 1038 | 1 0 − 308 10^{-308} 10−308 ~ 1 0 308 10^{308} 10308 | 1 0 − 4932 10^{-4932} 10−4932 ~ 1 0 4932 10^{4932} 104932 |
而编码值也分为三种不同情况:
-
规格化的值
当阶码不是全0或全1时,代表这就是一个规格化的值。如在单精度下:
阶码为
1011 0011
时,偏移量为127(0111 1111)
,则其表示的真值为1011 0011-01111111=0011 0100
,十进制即52.尾数部分约定小数点左边隐含有一位1,即尾数为**1.**开头(但实际上并不存在,只是一种约定),因此单精度浮点数有效位数为24位,不溢出情况下尾数值范围为: 1 ≤ M < 2 1 \leq M < 2 1≤M<2,该约定是一种获得额外精度的技巧。
如: b 0 b 1 … b 22 = 01001001100010001001011 b_0 b_1 \dots b_{22}=0100100 1100 0100 0100 1011 b0b1…b22=01001001100010001001011时,其对应的尾数为: 1 + 2 − 2 + 2 − 5 + 2 − 8 + 2 − 9 + 2 − 13 + 2 − 17 + 2 − 20 + 2 − 22 + 2 − 23 1+2^{-2}+2^{-5}+2^{-8}+2^{-9}+2^{-13}+2^{-17}+2^{-20}+2^{-22}+2^{-23} 1+2−2+2−5+2−8+2−9+2−13+2−17+2−20+2−22+2−23 = 1.28724038600921630859375。
-
非规格化的值
当阶码全0时,代表这是一个非规格化值,在这种情况下,指数的真值是1-偏移量(单精度为-126,双精度为-1022),尾数的值就是二进制的小数,不包含隐含的1。
非规格化的值主要用于表示0,或非常接近0的值。因为规格化包含隐含的1,无法表示0。
实际上0.0的浮点表示是除了符号位以外全0,但-0.0则是符号位为1以外全0,也就是说说浮点数下±0.0在表示上是有不同的。
-
特殊值
当阶码全1时,表示特殊值。
尾数部分全0时就表示无穷大,符号位用于表示正无穷或负无穷,在运算时溢出就会用无穷来表示。
尾数部分不全为0时表示"NaN, Not a Number",即不是一个数。在运算时结果不是实数或者无穷就会用NaN来表示。
示例
下面示范如何用IEEE 754标准将176.0625表示为单精度浮点数:
先将十进制转换为二进制:
( 176.0625 ) 10 = ( 10110000.0001 ) 2 (176.0625)_{10} = (10110000.0001)_2 (176.0625)10=(10110000.0001)2
然后对数进行规格化处理(即尾数小数点左边隐含一位1的表示形式,并满足尾数占23位长度的规定):
( 10110000.0001 ) 2 = ( 1.01100000001000000000000 × 2 7 ) 2 (10110000.0001)_2 = (1.01100000001000000000000 \times 2^7)_2 (10110000.0001)2=(1.01100000001000000000000×27)2
然后求阶码,标准规定阶码以移码方式表示,单精度浮点数规定偏移量127,即:
7(指数)+127(偏移量) = ( 134 ) 10 = ( 10000110 ) 2 (134)_{10} = (10000110)_2 (134)10=(10000110)2
最后,拼接起来即可得到 ( 176.0625 ) 10 (176.0625)_{10} (176.0625)10的单精度浮点数二进制:
符号位 S | 阶码 E | 尾数 P |
---|---|---|
0 | 10000110 | 01100000001000000000000 |
浮点数运算
设有浮点数 X = M × 2 j , Y = N × 2 j X = M \times 2^j , Y = N \times 2^j X=M×2j,Y=N×2j,求X±Y的运算过程要经过如下步骤:
-
对阶
使两个数的阶码相同,令 K = ∣ i − j ∣ K=|i-j| K=∣i−j∣,把阶码小的数的尾数右移K位,使其阶码加上K。
-
求尾数和(差)
尾数相加/相减
-
结果规格化,判断是否溢出
如果运算结果非规格化值,则规格化。当尾数溢出则调整阶码。
-
舍入处理
对结果向右规格化时,尾数的最低位将被丢弃。另外,对阶也可能出现右移导致丢失低位。舍入是为了求出最低的运算误差。
-
溢出判别
以阶码为准,若阶码溢出,则运算结果溢出;若阶码小于最小值,则结果为0;否则结果正确。
而相乘则是乘数的阶码相加,尾数相乘;相除则是被除数减去除数阶码作为商的阶码,商的尾数等于被除数尾数除以除数;
同样的,乘除运算都要进行规格化处理并判断是否溢出。