开始前,会这些就够了
- 会读0与1、奇数偶数、按位异或
- 先看 变量、基本类型与运算符 的二进制位置
把一张四位小纸条发给别人,路上某一格可能被改成相反值。我们多加三格检查信息,既想知道出了问题,也想定位哪里出了问题。但这个定位承诺有前提:每个七位块最多坏一位。
本例使用普通 Hamming(7,4),位置1、2、4放偶校验位,位置3、5、6、7放数据。位置从左到右编号1到7,和整数最低位在右边的书写习惯分开;每个输出数组都明确沿位置顺序显示。
把例子带到自己的电脑上
下载完整练习包
需要 Python 3.10 或更新版本,无第三方依赖;制作时实测 Windows / Python 3.14.0。使用标准库,不需要安装第三方包。下载 ZIP 后选择“全部提取”,进入能看到 README.md 的文件夹。
下载 ZIP · 5 个文件包里有什么
- hamming.py:完整可运行示例
- README.md:运行说明与独立练习
- observed.stdout.txt:本次实际输出
- verification.json:源码和产物验证记录
- SHA256SUMS.txt:内容摘要
在这个文件夹打开 PowerShell,逐行运行
python --version
python -B hamming.py解压到独立练习文件夹,在含 hamming.py 的目录打开终端运行。普通Hamming(7,4),偶校验,位置1/2/4校验、3/5/6/7数据;不含第八位SECDED,不测真实信道与错误概率。
找不到命令或文件时,先按 README 检查环境和所在目录。文中标出的阅读时间只估算文章篇幅,安装环境和动手练习可按自己的节奏进行。
校验归零,原来的数据回来了吗?
把四个数据位放到位置 3、5、6、7;位置 1、2、4 放偶校验位。点击下面的位置翻转接收到的位,再看纠错器会怎么做。
检查 1:位置 1、3、5、7 的异或为 1,不通过。
检查 2:位置 2、3、6、7 的异或为 1,不通过。
检查 4:位置 4、5、6、7 的异或为 0,通过。
校验结果:1 × 1 + 1 × 2 + 0 × 4 = 3。纠错器据此翻转位置 3。
发送:0110011 → 接收:0100011 → 处理后:0110011。
取回数据为 1011;和实验中已知的原数据 1011 相同。处理后的校验结果为 0。
本次至多一位出错,符合单错纠正的保证。
演示普通 Hamming(7,4) 的单错纠正。双错会产生非零校验结果,但把它一律当作单错修正会误纠;要同时纠正单错并区分双错,需要额外机制,例如扩展码的整体奇偶校验。
三张检查表怎样给七个位置不同编号?
第一张表检查1、3、5、7;第二张检查2、3、6、7;第三张检查4、5、6、7。每张表中的1的数量都应是偶数。位置5属于第一与第三张,不属于第二张;位置6属于第二与第三张。七个位置的参与组合各不相同。
把第一、第二、第三张失败分别记为1、2、4,再加起来。若只有位置5翻转,失败组合就得到1+4=5;若只有位置6翻转,得到2+4=6。这个三位检查结果叫 syndrome,可先理解成“哪些检查表亮红灯”的编号。
校验位为什么能各自补好偶数?
先把四个数据位放在3、5、6、7,暂把1、2、4设为0。对于位置1的检查组,若当前1的数量为奇数,就把位置1设为1;否则保留0。位置2和位置4也照此处理。
三个校验位置各只属于自己的那张表,所以依次补齐不会破坏前一张。对数据1011,本例得到沿位置顺序的0110011;四个原始数据仍能直接从3、5、6、7读回。源码会拒绝长度不对、非0/1整数和布尔值。
两位损坏,为什么校验会冤枉第三位?
从合法码字出发,同时翻转位置1和2。第一张表失败,第二张表失败,第三张通过,于是syndrome=3。若接收者坚信最多坏一位,就会翻转位置3。现在1、2、3都与原文不同,却又成了一份通过全部检查的合法码字。
两个不同位置的二进制编号异或不会是0,因此恰好两位翻转时,普通(7,4)的syndrome一定非零:只做检测可以发现异常。但它无法仅凭这个值区分“一位坏在3”和“两位坏在1、2”。一旦自动按单错纠正,双错就可能被误报为已修好;本例枚举里336种双错全部误纠成另一个码字。
合法码字至少隔三格,保证到底到哪儿?
任意两个合法(7,4)码字至少有三位不同,称最小 Hamming 距离为3。一个合法码字周围只翻一位的结果,不会同时距另一合法码字也只差一位,因此可以唯一恢复。
但距原文两位的接收串,可能距另一合法码字只差一位。最近邻解码会更靠近那个错误目标,这不是算法遗漏某个特判,而是现有检查信息不足以同时区分这两种来源。若翻转位置1、2、3,三组检查还会全部通过,syndrome=0也不能无条件宣布没有错误。
对全部码字、单错、双错都实算一次
运行 python -B hamming.py。single_bit_cases_restored为112,即16个合法消息各翻7个位置;double_bit_cases_detectable_but_miscorrected为336,即16×21。输出还给出同一原文翻1、2位时的误纠结果,以及翻1、2、3位时syndrome归零的例子。
参考码字表通过枚举全部128个七位串,并按显式三组位置检查偶数得到,不调用主编码器或syndrome函数。参考解码器计算与每个合法码字逐位不同的数量,选择唯一最近者;这与按syndrome直接定位走不同路径。程序还对全部128种接收串比较两种解码结果,并独立核对码字最小距离是3。
展开收起代码 · 小实验
长行可在代码区域内左右滚动,按 Tab 可把焦点移入代码区。
"""Hamming(7,4): single-bit correction does not distinguish two-bit errors."""
import itertools
import json
def validate(bits, length):
bits = tuple(bits)
if len(bits) != length or any(type(b) is not int or b not in (0, 1) for b in bits):
raise ValueError("exact number of integer zero/one bits required")
return bits
def encode(data):
data = validate(data, 4)
word = [0] * 8 # Position zero is unused.
for position, bit in zip((3, 5, 6, 7), data):
word[position] = bit
for p in (1, 2, 4):
word[p] = sum(word[i] for i in range(1, 8) if i & p) % 2
return tuple(word[1:])
def syndrome(word):
word = validate(word, 7)
return sum(p for p in (1, 2, 4)
if sum(word[i-1] for i in range(1, 8) if i & p) % 2)
def correct_assuming_at_most_one(word):
word = list(validate(word, 7))
suspect = syndrome(word)
if suspect:
word[suspect - 1] ^= 1
return tuple(word)
def flip(word, positions):
result = list(word)
for p in positions:
result[p-1] ^= 1
return tuple(result)
def reference_codebook():
# Independent oracle: enumerate all 128 words and use explicit parity sets.
# This does not call encode(), syndrome(), or the bit-index construction.
checks = ((0, 2, 4, 6), (1, 2, 5, 6), (3, 4, 5, 6))
return [word for word in itertools.product((0, 1), repeat=7)
if all(sum(word[i] for i in row) % 2 == 0 for row in checks)]
def distance(a, b):
return sum(x != y for x, y in zip(a, b))
def nearest(word, codebook):
ranked = sorted((distance(word, code), code) for code in codebook)
if ranked[0][0] == ranked[1][0]:
raise AssertionError("ambiguous nearest word")
return ranked[0][1]
def main():
checks, singles, doubles = 0, 0, 0
def check(ok):
nonlocal checks
if not ok:
raise AssertionError("Hamming check failed")
checks += 1
codebook = reference_codebook()
check(len(codebook) == 16)
check(min(distance(a, b) for a, b in itertools.combinations(codebook, 2)) == 3)
for data in itertools.product((0, 1), repeat=4):
word = encode(data)
check(word in codebook)
check(tuple(word[i-1] for i in (3, 5, 6, 7)) == data)
check(correct_assuming_at_most_one(word) == word)
for p in range(1, 8):
damaged = flip(word, (p,))
corrected = correct_assuming_at_most_one(damaged)
check(syndrome(damaged) == p)
check(corrected == word)
check(corrected == nearest(damaged, codebook))
singles += 1
for p, q in itertools.combinations(range(1, 8), 2):
damaged = flip(word, (p, q))
corrected = correct_assuming_at_most_one(damaged)
check(syndrome(damaged) != 0)
check(corrected != word and corrected in codebook)
check(corrected == nearest(damaged, codebook))
doubles += 1
# Every 7-bit input is at distance <= 1 from exactly one legal codeword.
for word in itertools.product((0, 1), repeat=7):
check(correct_assuming_at_most_one(word) == nearest(word, codebook))
original = encode((1, 0, 1, 1))
double = flip(original, (1, 2))
triple = flip(original, (1, 2, 3))
check(syndrome(double) == 3)
check(syndrome(triple) == 0 and triple != original)
for bad in [(True, 0, 0, 0), (1, 0, 1), (1, 0, 2, 0)]:
try:
encode(bad)
except ValueError:
check(True)
else:
raise AssertionError("invalid data accepted")
print(json.dumps({"checks_passed": checks, "single_bit_cases_restored": singles,
"double_bit_cases_detectable_but_miscorrected": doubles,
"original_positions_1_to_7": original,
"flip_1_2": {"received": double, "syndrome": syndrome(double),
"assumed_single_correction": correct_assuming_at_most_one(double)},
"flip_1_2_3": {"received": triple, "syndrome": syndrome(triple)},
"scope": "plain (7,4), even parity; no eighth parity bit and no SECDED decoder"}, indent=2))
if __name__ == "__main__":
main()
怎样运行:在独立练习文件夹中执行 python -B hamming.py。2026-10-18 已在 Windows、Python 3.14.0 运行此份源码并通过自带检查。普通Hamming(7,4),偶校验,位置1/2/4校验、3/5/6/7数据;不含第八位SECDED,不测真实信道与错误概率。
运行后对照结果
{
"checks_passed": 1527,
"single_bit_cases_restored": 112,
"double_bit_cases_detectable_but_miscorrected": 336,
"original_positions_1_to_7": [
0,
1,
1,
0,
0,
1,
1
],
"flip_1_2": {
"received": [
1,
0,
1,
0,
0,
1,
1
],
"syndrome": 3,
"assumed_single_correction": [
1,
0,
0,
0,
0,
1,
1
]
},
"flip_1_2_3": {
"received": [
1,
0,
0,
0,
0,
1,
1
],
"syndrome": 0
},
"scope": "plain (7,4), even parity; no eighth parity bit and no SECDED decoder"
}多一位整体校验,是另一个协议约定
给整个七位码字再加一个整体偶校验位,可构成距离4的扩展 Hamming(8,4)码。配套解码规则利用整体奇偶性区分单错纠正与双错报错,通常称SECDED。仅多写一个数字8而不修改解码规则并不会自动获得能力。
本文没有实现第八位或SECDED解码器,也没有模拟真实内存条、无线传输或错误概率。每次flip作用于互不相同的位置;同一位翻两次会抵消,不属于这里恰好两位不同的双错。七位固定长度使运算量固定,但“固定”不等于具备密码学恒定时间保证。
记住这三点
- syndrome只描述检查失败组合,单错定位依赖最多一位错误的前提。
- 普通(7,4)可作双错检测,却不能同时把双错与单错自动区分。
- syndrome=0只说明满足校验方程,多位错误仍可能通过。
别把例子用过头
- 把非零syndrome当成确凿的单错位置。
- 把普通(7,4)与加整体奇偶位的SECDED混为一谈。
- 忽略编号方向,拿数组第0项当作位置0参与校验。
自己试一小步
换一个四位消息后,哪些错误性质仍与消息内容无关?
- 自己选择不同于正文的四位消息,并手算七位码字。
- 选择一个单错位置和两个不同的双错位置,先推三张检查表。
- 保留原文、接收串、syndrome与假设单错的输出,比较是否真正还原。
做到这些,就算完成
- 校验位位置1、2、4与数据位3、5、6、7保持不变。
- 逐位比较原文,不能只看修复后syndrome是否为0。
- 把实际翻转的位数与纠错器假设分开,解释不一致时出现了什么。
查原始资料
想确认一个细节,先看这份资料对应的部分,不必从头读完所有文档。
- MIT 6.050J:Errors 课程讲义 (新标签页)↗
支持 Hamming 距离、三组偶校验和普通 Hamming 纠一错/检两错的不同使用前提。
- MIT MAS.160:Error-Correcting Codes (新标签页)↗
支持校验矩阵各列为位置二进制及 syndrome 的来源。
- Error Correction Zoo:[8,4,4] extended Hamming code (新标签页)↗
支持增加整体奇偶校验位后得到距离4的扩展码。
- Hamming 原论文:Bell Labs 官方条目 (新标签页)↗
支持原论文来源及区分错误检测与纠正的研究背景。