计算机使用的是2进制(0和1)存储和传输数据。但是这样做会有1定的几率(非常非常小)出现错误,导致0变成了1或者1变成了0。那么,计算机是怎么知道自己出现了错误呢?
三重码
三重码,顾名思义,就是把重要的事情说三遍。如果有数据损坏,那么可以通过其他的数据来修复损坏的数据。如果只有1个以下的地方出错,就可以修复:
原始数据 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 |
---|---|---|---|---|---|---|---|---|
发送数据 | 000 | 000 | 000 | 000 | 111 | 111 | 111 | 111 |
接收数据 | 000 | 001 | 01 0 | 1 00 | 111 | 110 | 10 1 | 0 11 |
还原数据 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 |
那么有2个以上地方出错,那么就不能修复了(虽然概率非常低):
原始数据 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 |
---|---|---|---|---|---|---|---|---|
发送数据 | 000 | 000 | 000 | 000 | 111 | 111 | 111 | 111 |
接收数据 | 011 | 1 01 | 11 0 | 111 | 100 | 0 10 | 00 1 | 000 |
还原数据 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
但是,出错的概率是很低的,使用三重码会传输三次原始数据,导致效率下降。既然如此,纠错的频率可以降低1些。
奇偶校验码
奇偶校验码的工作原理是统计1段2进制数中数字“1”出现的次数。如果这1段中出现偶数个“1”,那么校验码是“0”;如果出现奇数个“1”,那么校验码是“1”。下面是1个8位的简单的例子:
原始数据 | 接收数据 | 校验码 |
---|---|---|
0100 1010 | 0100 1010 | 1 |
0100 1010 | 0100 100 0 | 1 |
在原始数据“0100 1010”中,数字“1”出现了3次(奇数次),所以校验码是“1”。但是在第2个数据中,接收的数据“0100 1000”中数字“1”只出现了2次(偶数次),校验码本该是“0”,但是接收的校验码是“1”,所以判断这个数据有误。
但是奇偶校验码只能判断1个以内的错误。因为奇偶校验码只能告诉数字“1”出现的次数的奇偶性,所以出现2个错误,奇偶校验码就没用了。
根据奇偶校验码的特性,可以想到用行列定位错误的位置。下面是1个16位的例子:
实际数据 | 校验码 | ||||
---|---|---|---|---|---|
0 | 1 | 0 | 0 | 1 | |
1 | 0 | 1 | 0 | 0 | |
0 | 1 | 0 | 0 | 1 | |
1 | 0 | 0 | 0 | 1 | |
校验码 | 0 | 0 | 1 | 0 |
错误数据 | 校验码 | ||||
---|---|---|---|---|---|
0 | 1 | 0 | 0 | 1 | |
1 | 0 | 0 | 0 | 0 | |
0 | 1 | 0 | 0 | 1 | |
1 | 0 | 0 | 0 | 1 | |
校验码 | 0 | 0 | 1 | 0 |
由此,可以很快确定出错的位置。但是出错2个连续的地方就没办法定位了:
错误数据 | 校验码 | ||||
---|---|---|---|---|---|
0 | 1 | 0 | 0 | 1 | |
1 | 0 | 0 | 1 | 0 | |
0 | 1 | 0 | 0 | 1 | |
1 | 0 | 0 | 0 | 1 | |
校验码 | 0 | 0 | 1 | 0 |
因为每1行的校验码都是正确的,所以无法定位。而且这样的效率还是太低了,16位数据需要8个校验码。于是查德·卫斯里·汉明发明了——汉明码。
汉明码
有1道著名的智力谜题:16杯水中有1杯有毒,如何用最少的化验次数判断哪杯有毒?
解题思路:随机选取8杯化验,如果有毒,那么有毒的杯子就在这8杯里;如果没毒,那么有毒的杯子就在另外8杯里面。再选取4杯化验……如此化验4次,直到确定1杯有毒的。这样概括解题思路:错误如果不在这1半,那么1定在那1半。我们继续使用16位的例子:
原始数据 | 校验码 |
---|---|
0100 1010 0100 1000 | 0 |
0100 1010 0100 1000 | 1 |
0100 1010 0100 1000 | 1 |
01 00 10 10 01 00 10 00 | 0 |
由此计算出“0100 1010 0100 1000”的4位校验码是“0110”。
如果接收数据是“0100 1000 0100 1000”,那么这样的校验码是“0000”,与接收到的校验码“0110”不符,所以接收数据有误。
第1个和第4个的校验码无误,所以错误不在这些地方:
01
00
10
00
0100
1000
第2个和第3个的校验码有误,所以错误同时在第2个和第3个校验的地方:
0100 1000
0100 1000
根据上面的分析结果,错误只能在:
0100 100
0 0100 1000
所以正确的数据应该是:
0100 101
0 0100 1000
但是……这个操作有个问题:发生在第1位的错误是检测不出来的。也就是说4位校验码可以校验15位数。而且又迎来了1个问题:数据没错,校验码有误,那么怎么办呢?汉明的方法是将校验码和原始数据混合在1起:在11位中混入4位校验。
位置 | 位置的2进制值 | 值 | 备注 |
---|---|---|---|
1 | 0001 | 留空 | |
2 | 0010 | 留空 | |
3 | 0011 | 0 | |
4 | 0100 | 留空 | |
5 | 0101 | 1 | |
6 | 0110 | 0 | |
7 | 0111 | 0 | |
8 | 1000 | 留空 | |
9 | 1001 | 1 | |
10 | 1010 | 0 | |
11 | 1011 | 1 | |
12 | 1100 | 0 | |
13 | 1101 | 0 | |
14 | 1110 | 1 | |
15 | 1111 | 0 |
会发现:留空的位置的2进制值中只有1位是“1”。
计算校验码时,先计算位置的2进制值第1位是“1”的奇偶校验码,比如位置“1
001”、“1
010”,填入第8位;再计算位置的2进制值第2位是“1”的奇偶校验码,比如位置“01
01”、“11
10”,填入第4位;然后计算位置的2进制值第3位是“1”的奇偶校验码,比如位置“011
0”、“101
0”,填入第2位;最后计算位置的2进制值第4位是“1”的奇偶校验码,比如位置“1001
”、“1011
”,填入第1位。
那么,我们会得到:
位置 | 位置的2进制值 | 值 | 备注 |
---|---|---|---|
1 | 0001 | 1 | 留空 |
2 | 0010 | 0 | 留空 |
3 | 0011 | 0 | |
4 | 0100 | 0 | 留空 |
5 | 0101 | 1 | |
6 | 0110 | 0 | |
7 | 0111 | 0 | |
8 | 1000 | 1 | 留空 |
9 | 1001 | 1 | |
10 | 1010 | 0 | |
11 | 1011 | 1 | |
12 | 1100 | 0 | |
13 | 1101 | 0 | |
14 | 1110 | 1 | |
15 | 1111 | 0 |
如果第5位出现错误,那么第1位和第4位校验码会出错。由排列方式可知:如果数据出错,那么1定会导致2个或者以上校验码出错,因为数据位置的2进制值不仅仅有1个“1”。如果只有1个校验码出错,那么只能说明是校验码本身的问题。如上的方式可以纠正1位错误。那么出现2位错误呢?我们可以设置第0位数来表示后面15位数的奇偶校验码:
0100 0100 1101 0010
当后面的数据出现1位错误时,第0位能察觉出错误;当后面的数据出现2位错误时,第0位察觉不出错误,但是校验码可以察觉错误;这样,汉明码就具备了纠正1位错误,察觉2位错误的能力。当出现2位错误时,虽然不能纠正,但是可以让对方再发送一遍消息来纠正错误。
那么……出现三个错误呢?汉明码就察觉不了了,就需要类似里德-所罗门纠错码的其他纠错码了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。