硬件中的数
一、整数
1.1 二进制数与十进制数间的转换
首先强调,这里的二进制数是补码形式的(这里的补码就是负数补码,正数原码),我后面进行数学推导的时候,用的是n位的二进制数,从高到低,分别是 a n − 1 , a n − 2 , ⋅ ⋅ ⋅ , a 0 a_{n-1},a_{n-2},\cdot\cdot\cdot,a_0 an−1,an−2,⋅⋅⋅,a0 。
对于二进制转十进制,有公式
d
e
c
i
m
a
l
=
−
a
n
−
1
∗
2
n
−
1
+
a
n
−
2
∗
2
n
−
2
+
⋅
⋅
⋅
+
a
0
∗
2
0
decimal = -a_{n-1}*2^{n-1} + a_{n-2}*2^{n-2}+\cdot\cdot\cdot+a_0*2^0
decimal=−an−1∗2n−1+an−2∗2n−2+⋅⋅⋅+a0∗20
这是因为补码的本质是将一个负数用一个正数表示,补码的构造是结合溢出,也就是取余操作,比如对于四位二进制,-4对应的是
12,也就是
−
4
+
2
4
=
12
-4+2^4 = 12
−4+24=12,可以看出,如果想把补码(负数对应的正数)还原成原来的负数,需要减去
2
n
2^n
2n ,所以对于一个二进制数表示的负数,如果按照无符号的算法,有usigned_decimal,减去
2
n
2^n
2n,就得到了signed_decimal
u
n
s
i
g
n
e
d
_
d
e
c
i
m
a
l
=
a
n
−
1
∗
2
n
−
1
+
a
n
−
2
∗
2
n
−
2
+
⋅
⋅
⋅
+
a
0
∗
2
0
unsigned \_decimal = a_{n-1}*2^{n-1} + a_{n-2}*2^{n-2}+\cdot\cdot\cdot+a_0*2^0
unsigned_decimal=an−1∗2n−1+an−2∗2n−2+⋅⋅⋅+a0∗20
s
i
g
n
e
d
_
d
e
c
i
m
a
l
=
u
n
s
i
g
n
e
d
_
d
e
c
i
m
a
l
−
2
n
=
−
a
n
−
1
∗
2
n
−
1
+
a
n
−
2
∗
2
n
−
2
+
⋅
⋅
⋅
+
a
0
∗
2
0
signed \_decimal =unsigned \_decimal-2^n= -a_{n-1}*2^{n-1} + a_{n-2}*2^{n-2}+\cdot\cdot\cdot+a_0*2^0
signed_decimal=unsigned_decimal−2n=−an−1∗2n−1+an−2∗2n−2+⋅⋅⋅+a0∗20
然后发现,对于二进制正数,上面的计算公式也恰巧符合( a n − 1 a_{n-1} an−1 恰好为0,正负不影响),所以这个公式就很简便了。
对于十进制转二进制,对于正数,就可以直接转化,对于负数
x
0
x_0
x0 ,需要先写出其绝对值的二进制表示
x
1
x_1
x1 ,在逐位取反得到
x
2
x_2
x2 ,然后加一,就可以得到对应的二进制数
x
3
x_3
x3 ,现在我们来证明这个操作的合法性。有公式
x
1
=
−
x
0
x_1 = -x_0
x1=−x0
x
1
+
x
2
=
111
⋅
⋅
⋅
1
=
2
n
−
1
x_1 + x_2 = 111\cdot\cdot\cdot1 = 2^n - 1
x1+x2=111⋅⋅⋅1=2n−1
x
2
=
2
n
−
1
−
x
1
x_2 = 2^n-1-x_1
x2=2n−1−x1
此时如果我们再给
x
2
x_2
x2 加 1 ,就得到了式子
x
3
=
2
n
−
x
1
=
2
n
+
x
0
x_3 = 2^n - x_1 = 2^n + x_0
x3=2n−x1=2n+x0
依照溢出原理,一个负数加上
2
n
2^n
2n,就可以得到对应的正数。所以
x
3
x_3
x3 就是
x
1
x_1
x1 对应的那个正数。
1.2 通用加法器的实现
对于正数,就是以原码的形式储存,对于负数,是按照补码的形式存储的,这是补码的第一次应用,这样存储的好处是可以将原来以无符号加法为实现目标的加法器,也可以用于**“负数 + 正数”、“正数 + 负数”、“负数 + 负数” **的通用加法器,不需要更改一点硬件设计,这是因为有如下公式
计
算
机
中
负
数
+
正
数
=
实
际
负
数
+
2
n
+
正
数
=
实
际
负
数
+
正
数
(
利
用
溢
出
原
理
消
去
2
n
)
计算机中负数 + 正数 = 实际负数 + 2^n + 正数 = 实际负数 + 正数(利用溢出原理消去2^n)
计算机中负数+正数=实际负数+2n+正数=实际负数+正数(利用溢出原理消去2n)
计
算
机
中
负
数
1
+
计
算
机
中
负
数
2
=
实
际
负
数
1
+
2
n
+
实
际
负
数
2
+
2
n
=
实
际
负
数
1
+
实
际
负
数
2
计算机中负数1 + 计算机中负数2 = 实际负数1 + 2^n + 实际负数2 + 2^n = 实际负数1 + 实际负数2
计算机中负数1+计算机中负数2=实际负数1+2n+实际负数2+2n=实际负数1+实际负数2
1.3 减法器的实现
这是补码的第二次应用,这次跟通用加法器的实现不同,是需要更改一些硬件基础的,对于减数,需要在前面增加一个n位的异或门,一个n位输入接减数,另一个接减法信号(sub == 1时,实现减法器功能,sub == 0 时,实现加法器功能)。此外,还需要把加法器的进位输入端连接到减法信号,整体的效果当要进行减法的时候,是对于减数进行取反操作,并给结果加1,可以知道,这样构造出的减法器应用了补码原理,而且是通用减法器。
硬件描述如下:
module Adder_subtractor(input [15:0] a, input [15:0] b, input sub, output [15:0] ans);
wire carry;
wire [15:0] tmp = b ^ {16{sub}};//如果sub == 1,就对b取反,如果 sub == 0,就不发生变化
add16 sub(a, tmp, sub, ans, carry);//大部分硬件还是与加法器上搭建的
endmodule
module add16(input [15:0] a, input [15:0] b, input cin, output [15:0] sum, output cout);
assign {cout, sum} = a + b;
endmodule
1.4 位数拓展
比如要把一个 4 位的二进制数拓展成 8 位,还不能改变他所代表的值。可以这么做,将 a n − 1 a_{n-1} an−1 复制到待扩展位中,就可以满足条件。我们采用迭代证明,对于n位数,如果扩展到n+1可以,那么扩展到n+2,n+3,n+4……都可以。
对于正数,前面添一个0,显然对值本身不会造成任何影响,对于负数,前面添一个1,我们将其转换为十进制
e
x
t
e
n
d
=
−
a
n
∗
2
n
+
a
n
−
1
∗
2
n
−
1
+
⋅
⋅
⋅
+
a
0
∗
2
0
extend =-a_{n}*2^{n} + a_{n-1}*2^{n-1}+\cdot\cdot\cdot+a_0*2^0
extend=−an∗2n+an−1∗2n−1+⋅⋅⋅+a0∗20
我们又有:
a
n
=
1
,
a
n
−
1
=
1
a_n= 1, a_{n-1} = 1
an=1,an−1=1
所以有
e
x
t
e
n
d
=
−
a
n
∗
2
n
+
a
n
−
1
∗
2
n
−
1
+
⋅
⋅
⋅
+
a
0
∗
2
0
=
−
a
n
−
1
∗
2
n
−
1
+
a
n
−
2
∗
2
n
−
2
+
⋅
⋅
⋅
+
a
0
∗
2
0
=
n
o
n
_
e
x
t
e
n
d
extend =-a_{n}*2^{n} + a_{n-1}*2^{n-1}+\cdot\cdot\cdot+a_0*2^0 =-a_{n-1}*2^{n-1} + a_{n-2}*2^{n-2}+\cdot\cdot\cdot+a_0*2^0 = non\_extend
extend=−an∗2n+an−1∗2n−1+⋅⋅⋅+a0∗20=−an−1∗2n−1+an−2∗2n−2+⋅⋅⋅+a0∗20=non_extend
1.5 溢出判断
溢出有两种形式,“正数+正数”和“负数+负数”(减法自动转化)。最简单的方法是判断最高位的进位carry_out和次高位的进位carry_out是否相同,如果相同,就说明发出了溢出。
对于**“正数+正数”**,显然最高位肯定carry_out是0(两个0和一个carry_in相加没办法进位)如果被次高位进位了,那么它就变成了一个负数了,所以要求次高位不能进位。
对于**“负数+负数”**,最高位的carry_out一定是1,如果是次高位没有1进上来,那么就会在最高位出现0,就溢出了,如果是进上来了,那么可以证明结果尽管发出了物理上的溢出,但是在数值上没有问题,因为有公式
计
算
机
中
负
数
1
+
计
算
机
中
负
数
2
=
实
际
负
数
1
+
2
n
+
实
际
负
数
2
+
2
n
计算机中负数1 + 计算机中负数2 = 实际负数1 + 2^n + 实际负数2 + 2^n
计算机中负数1+计算机中负数2=实际负数1+2n+实际负数2+2n
对于溢出,其实就是减掉一个
2
n
2^n
2n,显然是不会对真值造成影响的。
1.6 超前进位加法器
C1 = A0 & B0 | C0 & (A0 ^ B0);
C2 = A1 & B1 | C1 & (A1 ^ B1);
...
Cn = An-1 & Bn-1 | Cn-1 & (An-1 ^ Bn-1);
//所以如果我们这样转换变量
Gi = Ai & Bi; Pi = Ai ^ Bi;
//就会有如下式子
C1 = G0 | P0 & c0;
C2 = G1 | G0 & P1 | P0 & P1 & C0;
C3 = G2 | G1 & P2 | G0 & P1 & P2 | P0 & P1 & P2 & C0;
C4 = G3 | G2 & P3 | G1 & P2 & P3 | G0 & P1 & P2 & P3 | P0 & P1 & P2 & P3 & C0;
可以看到, C i C_i Ci 不再和 C i − 1 C_{i-1} Ci−1 有直接关系了,这样就为并行计算提供了方法。但是相应的,电路设计变得复杂。
二、浮点数
2.1 最基础的浮点数表示
我们用一个十进制数288来举例,288的二进制表示是11100100,转换为科学计数法就是 1.11001 ∗ 2 111 1.11001 * 2^{111} 1.11001∗2111 。我们用符号位存储数的正负,用阶码存储基数的幂次,用尾数存储那个小数。
符号(1位) | 阶码(8位) | 尾数(23位) |
---|---|---|
0 | 0000_0111 | 111_0010_0000_0000_0000_0000 |
2.2 隐含前导1
我们注意到,只要需要存储的数字不是0,尾数的第一位就一定是1,所以就没有必要每次花一个数据位去保存了,所以我们就得到了采用隐含前导1的第二种表示方法。
符号(1位) | 阶码(8位) | 尾数(23位) |
---|---|---|
0 | 0000_0111 | 110_0100_0000_0000_0000_0000 |
2.3 偏码与补码
那么如果遇到阶码是负数的情况怎么办,当然我们可以用补码来表示,但是补码不太符合浮点数的计算要求,为什么这么说呢?首先我要了解一下浮点数的加法过程。
面对浮点数加法,我们与整数加法不同的是,我们要先比较阶码的大小,然后利用阶码的差,将尾数对齐,那么显然,补码的设计是不适合进行比较的,比如-1永远都对应最大的无符号数,比所有的正数都大。我们需要的是一个好比较的数制。
这是偏码的长处,偏码就是把原始阶码加上了一个常数偏置。这个常数一般是 2 n − 1 − 1 2^{n-1} - 1 2n−1−1 ,其中 n 是阶码的位数,这样一来,阶码的大小比较就很容易了,而且计算差值也不会受到影响。所以采用了偏码的288,储存形式如下:
符号(1位) | 阶码(8位) | 尾数(23位) |
---|---|---|
0 | 1000_0110 | 110_0100_0000_0000_0000_0000 |
那么为什么不所有的数都采用偏码表示,舍弃补码的用法,这可能是因为补码能更好的适应加减法吧。
2.4 浮点数的表示范围
需要明确的一点,尽管浮点数的表示范围大了,但是其实他能表示的数还是 2 32 2^{32} 232 个,这跟一个int表示的数是一样的,准确的来说,是浮点数的上限高了,但是范围内只能表示特定的一些数。因为只有 2 32 2^{32} 232 中状态,所以只能表示 2 32 2^{32} 232 种数,这是很直白的道理。
所以与浮点数相伴随的,是舍入现象和溢出现象,因为没办法精确表示每一个数,所以舍入是必然的。溢出也比整型要复杂,分为负上溢出(负数太小了),负下溢出(负数太接近0了),正下溢出(正数太接近0了),正上溢出(正数太大了)。
2.5 特殊情况的表示
正如2.4节形容的,有些数是没有办法按常理表示的,所以我们分出了几个状态来表示这些特殊值
数字 | 符号 | 阶码 | 小数 |
---|---|---|---|
0 | x | 0000_0000 | 000_0000_0000_0000_0000_0000 |
$\infty $ | 0 | 1111_1111 | 000_0000_0000_0000_0000_0000 |
− ∞ -\infty −∞ | 1 | 1111_1111 | 000_0000_0000_0000_0000_0000 |
NaN | x | 1111_1111 | 非零 |
2.6 IEEE 754 标准
采用隐含前导1,偏码,特殊情况的数制就是这个标准。
三、位,字节,字
3.1 位(bit)
位是存储的最小单位,就是0或者1的感觉。
3.2 字节(byte)
字节是计算机数据处理的基本单位,一般用大写的B来表示
3.3 字(word)
Buses are typically designed to transfer fixed-sized chunks of bytes known as words. The number of bytes in a word(the word size) is a fundamental system parameter that varies across systems. Most machines today have sizes of either 4 bytes(32 bits) or 8 bytes(64 bits)
也就是说,字跟数据处理没有关系,它是形容总线传输能力的参数,因为总线与CPU联系紧密,所以字也可被视作CPU一次处理数据的最大量,总的来说,是硬件参数。平常说的64位电脑,就是说最大可以处理64bit的数据,所以 1 w o r d = 64 b i t = 8 b y t e 1 word = 64 bit = 8 byte 1word=64bit=8byte 。