引子
众所周知,当我们调换字汉中某些字的序顺时,并不会影响们我对于这句话的理解。这当然是因为我们人脑具有一定的自动检错和纠错能力,这是通过我们过往的经验和上下文的理解实现的,有点类似于深度学习和机器学习的过程。所以最开头那句话输入给chatGPT,它也会理解这句话是什么意思。
但是这种检错和纠错能力的实现需要处理机器拥有一定的“智能”,显然并不是所有的机器都拥有这样的智慧。比如说我们有一个光盘,其中存放了一张照片,我们不小心在光盘上留下了一条划痕,我们需要在有划痕的情况下,将这幅照片完整的展现。听起来好像很不可思议,因为有划痕就意味着光盘上一些数据的丢失或损坏,这就需要我们根据现有的光盘上未损坏的信息,来推测光盘上哪里的数据被损坏了,并试图还原原始数据的内容。
1950年时一位名叫理查德·W·海明的人提出了海明码,这是一种最早的错误检测和纠正技术,它通过在原始数据中添加冗余位(额外的比特)来实现错误检测和纠正。所以如果光盘上数据的编码采用海明码的话,运气好可以实现原始数据的还原。
我们应该了解,计算机中的任何数据本质上都是以二进制数的形式来存储和传送的,包括文件、视频、图片、音乐等,无一例外。
比如我们现在要从A主机传送一个序列到B主机,要传送的序列为1011 0100 1010。我们上个专题中介绍了奇偶校验码和CRC循环冗余校验,所以我们可以选择在传送的序列末尾增加一位奇偶校验位或增加若干位CRC循环冗余校验位。但是这两种校验方法只能让对方知道,数据在传送过程中是否出错,并不能根据校验位知道数据哪里出错。
这时就需要海明码来实现纠错这一功能了。
海明码的构建方法
1. 确定信息位、校验位和总位数
还是以1011 0100 1010这个序列为例,首先我们要确定我们传送的数据位数,在这里是12位,所以k = 12。
然后我们要确定校验位的位数。校验位的位数主要由数据的位数和我们想要拥有的检错和纠正能力决定,较多的校验位可以检测和纠正更多的错误,但也会增加总位数和冗余信息的量。比如我们想要实现一个比特的纠错能力,校验位的位数r应该满足r >= log2(k + r + 1),当k = 12时,此时r应该取5。至于其他的情况,我们可以稍后再讨论。
如此,我们知道了数据位数和校验位的位数,可以得到总位数n = k + r,即17,所以我们一次需要传送17位数据。
2. 校验位的位置选择和计算
当构造海明码时,校验位的位置选择是按照特定的规则进行的,通常是在2的幂次方位置上,比如说1、2、4、8、16,这些都属于2的幂次方位置,在这些位置并不会存放数据,而只会存放校验码。
我们以我们要传递的这个序列为例,来进行演示。如下图,我们传输的数据总位数为17位,红色的地方即需要存放验证码的地方,可以看到都是2的幂次方位置:
接下来我们需要将红色的部分也就是校验码部分填充数据。数据的填充有一定的计算规则,第一步是找到每一个校验位负责的位数。
我们现在有5个校验位,将他们对应的位置下表用二进制写出来,即00001, 00010, 00100, 01000, 10000。可以发现他们都是在特定位为1,其他位为0。那么某校验位负责的位数就是该特定位也为1的位。
比如00001负责的位数分别是1,3,5,7,9,11,13,15,17,这些数的二进制第一位都为1。00010负责的位数分别是2,3,6,7,10,11,14,15,这些数的二进制第二位都为1。照此,可以得到所有校验位负责的位数,10000校验位负责的位数就是16,17
第二步是对校验位负责的位数进行一次偶校验,也就是说,每一个校验位负责的位数中1的个数必须是偶数。
如此,我们就知道了校验码部分的数据,将其填充入上图中,得到:
这样,我们要传输的数据就准备好了。
海明码的检错和纠错功能
接下来我们来检验一下海明码的检错和纠错功能,比如说A主机将上述17位数据发送给B主机,正常应该发送0110 0111 0100 1010 0,但是B主机接收到的是0111 0111 0100 1010。此时B主机需要判断数据在传输过程中有没有发生差错,所以需要对数据进行检验。
他会对所有二进制中某个特定位为1的对应位的值进行异或运算,如下:
X1 ⊕ X3 ⊕ X5 ⊕ X7 ⊕ X9 ⊕ X11 ⊕ X13 ⊕ X15 ⊕ X17 = 0 ⊕ 1 ⊕ 0 ⊕ 1 ⊕ 0 ⊕ 0 ⊕ 1 ⊕ 1 ⊕ 0 = 0
X2 ⊕ X3 ⊕ X6 ⊕ X7 ⊕ X10 ⊕ X11 ⊕ X14 ⊕ X15 = 1 ⊕ 1 ⊕ 1 ⊕ 1 ⊕ 1 ⊕ 0 ⊕ 0 ⊕ 1 = 0
X4 ⊕ X5 ⊕ X6 ⊕ X7 ⊕ X12 ⊕ X13 ⊕ X14 ⊕ X15 = 0 ⊕ 0 ⊕ 1 ⊕ 1 ⊕ 0 ⊕ 1 ⊕ 0 ⊕ 1 = 1
X8 ⊕ X9 ⊕ X10 ⊕ X11 ⊕ X12 ⊕ X13 ⊕ X14 ⊕ X15 = 1 ⊕ 0 ⊕ 1 ⊕ 0 ⊕ 0 ⊕ 1 ⊕ 0 ⊕ 1 = 0
X16 ⊕ X17 = 0 ⊕ 0 = 0
如果最后得到的结果都为0的话,说明传输的数据是没问题的,但是现在不全为0,所以传输的数据一定出了问题。我们可以尝试使用海明码对应的纠错算法来进行数据的修复。如果只有一位发生错误的话,那么错误的这一位的位置一定是00100,也即将上述的异或结果倒过来。
我们可以再尝试改变其他位,比如将15位变为1,那么通过计算可以得到,异或的结果分别为,1,1,1,1,0,那么错误的位的位置一定是01111,正好是15。
但是如果有更多位出错,比如说有两位出错,海明码就没有办法进行错误的纠正了。比如说第一位和第二位出错,那么我们可以得到异或的结果为1,1,0,0,0,依据我们上面的结论,此时应该是00011位的数据出错了,但结果显然不是。此时我们只能判断这串数据是错误的,而没有办法判断到底是哪里出错了。
当有3位或更多位错误时,有可能海明码也判断不了数据是否出错了,这个读者可以自行验证。
所以说,此时海明码可以检测2位错误或者纠正1位错误。
- 更多的校验位
为了使得海明码可以检测或纠正更多位的错误,就需要更多位的校验位。这是非常好理解的,校验位的位数越多,包含的校验信息就越多,自然检测和纠正的位数也越多。在具体介绍这个位数之前,我们首先需要介绍一下码距的概念。
码距
码距是指两个编码之间不同位的数量,或者说是两个编码之间的差异程度。更具体的说,对于海明码,码距指的是两个码字之间不同的位数。
例如,考虑一个海明码的例子,假设有两个码字:1011和1101。我们可以逐位比较这两个码字,找出它们之间的不同位数。在这个例子中,有两个位是不同的:第2位和第3位。因此,这两个码字之间的码距为2。
对于一个12位数据来说,计算他的码距是非常复杂的,所以我们可以用传递两位信息为例来表明计算码距的过程。
比如我们要传递三位信息,那么信息位位数k = 2,校验位位数r >= log2(k + r + 1),所以r取3,下图是带海明码的两位消息的传输格式,蓝色为信息位,红色为校验位:
此时信息位有4种可能,分别是00、01、10和11,校验位是随着信息位的变化而变化的。我们可以列出所有可能的情况:
可以发现,这些情况中,两两逐位的进行比较,发现最小的码距是3,所以这个编码系统的码距就是3
码距和检错纠错位数的关系
事实上,我们有以下结论
- 如果要检测e个错误,最小码距应该满足:D >= e + 1
- 如果要纠正t个错误,最小码距应该满足:D >= 2t + 1
所以如果我们要检测两位错误或纠正1位错误的话,码距就应该大于等于3,也正好符合我们之前的结果。