海明码编码和校验原理与实现【转载】

海明编码与检验原由
以内存为例, 如果内存所处的电磁环境比较复杂, 或者空间环境下受到带电粒子的打击, 那么可能导致电容的充放电或者触发器的翻转(SRAM)。 这样会导致存储信息的改变。 如果不校验, 存储中存储的程序可能不会发挥它应有的作用, 甚至会发生严重的后果。

编码的最小距离
任意两组合法代码之间二进制位数的最小差异、编码的纠错、改错能力与编码的最小距离有关。 编码的最小距离就是从一种合法代码转化成另外一种合法代码所变化的最小位数。存在如下公式:
L 1 = C + D ( D C ) L-1=C+D(D\geq C) 汉明码是一种具有纠错能力的编码。其中:

L : L: 编码的最小距离
D : D: 检测错误的位数(信息位)
C : C: 纠正错误的位数(校验位或冗余位)

冗余位
冗余位”是一种二进制位,它被用来添加到需要传输的数据信息中,以确保信息在传输过程中不会发生丢失或者改变。

对于“冗余位”究竟需要多少位这个问题,我们有一个公式可以用来计算:
2 r m + r + 1 2^r\geq m+r+1 其中 r r 是冗余位, m m 为数据位。 r r 指的是冗余位究竟需要多少位,而 m m 指的是传输的数据的二进制位数。

假设传输的数据的二进制位数是7位,那么冗余位的个数就可以通过上面的公式来计算: = 2 4 7 + 4 + 1 = 2^4 ≥ 7 + 4 + 1 因此,我们的至少需要4个二进制位作为“冗余位”。

对比以上两种概念,可看出冗余位等价于纠正错误的位数

(注意:汉明码默认一串数据只错一位

奇偶校验位
奇校验: 在奇校验检测方式中,对于需要发送的数据信息比特,检查其中1的个数。如果这串比特中1的个数是奇数,为了保证加上“冗余位“后,””整串数据中1的个数最后为奇数,可想而知,冗余位上应该设置为“0”。如果在没有添加“冗余位”之前,数据比特流中的1的个数为偶数,那么为了最后把1的个数凑成一个奇数,冗余位上应该设置为1。
偶校验: 同理,在偶校验检测方式中,对于需要发送的数据信息比特,仍然检查其中1的个数。如果这串比特中1的个数是奇数,为了保证加上“冗余位“后,””整串数据中1的个数最后为偶数,可想而知,冗余位上应该设置为“1”。如果在没有添加“冗余位”之前,数据比特流中的1的个数为偶数,那么为了最后把1的个数凑成一个偶数,冗余位上应该设置为0。例如(偶校验):

如果整个代码“1”的个数为奇数, 我们就知道有一位数据发生了翻转(因为数据位翻转的位数越多, 可能性越小。)如果我们将数据划分成为两组,每组都加一个校验位,那么可以大大缩小确定发生翻转的位数的范围。这种划分方式是没有重叠的, 每个分组的数据连在一起就是原来的数据。

但海明码并未使用上述分组原理。

海明码分组原理
汉明码采用的是非划分方式。对于下面这个数据, 我们采用汉明码的方式。

将数据分为三组, 每组一个校验位, 每组四位数据。 按照如下方式分组

(注意:上、左、右三个圆分别表示P1、P2、P3组) 把表示位置的这个数,转化成二进制数。也就是,

第1个位置,变成第0001个位置;
第2个位置,变成第0010个位置;
第3个位置,变成第0011个位置;
第4个位置,变成第0100个位置;
第5个位置,变成第0101个位置;
第6个位置,变成第0110个位置;
第7个位置,变成第0111个位置;

那么,规定来了,
凡是位置符合这种形式的,XXX1,归到P1;
凡是位置符合这种形式的,XX1X,归到P2;
凡是位置符合这种形式的,X1XX,归到P3;
凡是位置符合这种形式的,1XXX,归到P4;

则上图中:
位置在1、3、5、7的数据进入组P1;
位置在2、3、6、7的数据进入组P2;
位置在4、5、6、7的数据进入组P3;
没有数据进入组P4;

若某个组位置错了将其置为1,没错就是0,则有:

P3 P2 P1 错位的位置号
0 0 0 无差错
0 0 1 1
1 0 1 5
1 1 0 6
1 1 1 7

可以看出P3、P2、P1组成的二进制值就是发生错误的位置。记住规定,在采用汉明码的一串数据中, 2 i 2^i 的位置上,我们放校验码。


海明码编码和校验实例(以奇校验为例)
编码:
假定数据为:1011001。由于 2 4 7 + 4 + 1 2^4 ≥ 7 + 4 + 1 ,因此需要4位校验位或冗余位。填充冗余位后有:
在这里插入图片描述

  • 对于第一组来说(1,3,5,7,9,11位为一组):1的个数为4个,偶数个,因此①号应该为1。这样1的个数最后才能保证为奇数。
    在这里插入图片描述
  • 同理,对于第二组来说(2,3,6,7,10,11位1组):1的个数为3个,已经是奇数了,因此②号应该是0。在这里插入图片描述
  • 对于第三组来说(4,5,6,7位一组),1的个数为1个,因此,③号应该是0。
    在这里插入图片描述
  • 对于第4组来说(8,9,10,11为一组),1的个数为2个,因此,④应该为1。
    在这里插入图片描述
  • 最后,总的汉明码就构造完毕了,如下所示:
    在这里插入图片描述
    校验:
    上面,我们已经完成了“汉明码”的编码,那么,汉明码又是如何发现错误以及改正错误的呢?
    假设,第“5”号位上的“0”在传输过程中变成了“1”,接收方收到的数据则为:10111010101。
    在这里插入图片描述
    汉明码通过检查每一小组的“奇校验”,来确定是否发生了错误。
  • 首先第一组(1,3,5,7,9,11位):1的个数为6位,不再是奇数个了,因此,我们可以断定,这一组中肯定有某个数据发生了错误,但不能确定是哪一位上发生了错误。为了达到“奇校验”,我们必须补1个1来达到奇数个1。
  • 接下来,我们检查第二组(2,3,6,7,10,11) ,1的个数为3位,仍然满足“奇校验”,因此我们也可以断定这一组中没有任何一位数据发生了改变。所以,我们只需要补0。
  • 我们继续检查第三组(4,5,6,7),1的个数为2个,不在满足“奇校验”,因此,我们可以断定,这一组中也有数据发生改变。为了达到“奇校验”,我们必须补1个1来达到奇数个1。
  • 我们检查第4组(8,9,10,11位),1的个数为3位,满足“奇校验”,因此没有发生改变。所以我们只需要补0。
    如下图所示:
    在这里插入图片描述
    最后得出来的二进制数是:0101,我们会神奇地发现,0101就是10进制5的二进制表现,因此,我们可以准确的知道,5号位上发生了数据的改变,我们只要对5号位进行置反操作即可。最后,接收方就可以修改成为正确的数据啦。

海明码编码和校验实例(以偶校验为例)
编码(通常汉明编码是以偶校验进行编码的):
假定数据为:10011010,由于 2 4 8 + 4 + 1 2^4 ≥ 8 + 4 + 1 ,因此需要4位校验位或冗余位。填充冗余位后有:
_ _1_001_1010
分组情况如下:
组数是这样的:
P1: 1,3,5,7,9… 检验奇数位
P2: 2,3,6,7,10,11… 检验两个,跳过两个,检验两个,跳过两个
P3: 4,5,6,7,12,13,14,15… 检验4个,跳过4个
P4: 8,9,10,11,12,13,14,15… 检验8个,跳过8个
由于进行偶校验,要求每个组中1的个数是偶数,因此有:

  • 第1组检验位为 1,3,5,7,9,11:
    ? _ 1 _ 0 0 1 _ 1 0 1 0. 看看1,3,5,7,9,11位,发现正好是偶数个1,所以校验位1设为0就行了:
    0 _ 1 _ 0 0 1 _ 1 0 1 0
  • 第2组检验位为 2,3,6,7,10,11:
    0 ? 1 _ 0 0 1 _ 1 0 1 0. 发现是奇数个1,于是设置校验位2为1,这样算上校验位的1,恰好是偶数个0 1 1 _ 0 0 1 _ 1 0 1 0
  • 第3组检验位为 4,5,6,7,12:
    0 1 1 ? 0 0 1 _ 1 0 1 0. 发现是奇数个1,于是设置校验位4为 0 1 1 1 0 0 1 _ 1 0 1 0
  • 第4组检验位为 8,9,10,11,12:
    0 1 1 1 0 0 1 ? 1 0 1 0. 发现是偶数个1,于是设置校验位8为 0 1 1 1 0 0 1 0 1 0 1 0
  • 最后配置好的汉明码: 011100101010.

校验:
如我们传送的011100101010,但接收到的是011100101110 (第10位出现错误)。

出错位可以这样确定:
对接受到的汉明码011100101110 进行每一个校验码的验算,即它所在的组(包括它,与之前不同的是,之前校验码是待定的,现在已经有值了)的1的个数应该是偶数个,这就暗示我们将要覆盖这个值(实际上并不是,仅仅是验算)的新校验码应该是0才对,但是由于接收到的有错误,我们在验算中发现有的校验位要填1。

这里,验算后发现第2个和第8个校验位要填1,那么错误的数据位就是2+8=10。


海明码编码和校验实现(C++\C)

#include <stdio.h>
#include <stdlib.h>//调用system函数
#include <malloc.h>
#include <math.h>
#include <dos.h>//注意pow(double,int)而不是pow(int,int)
#define M 100 //最大的数据位数
#define N 50//最大的校验位数

struct Hamming{
	int flag;
	int value;//海明码的数值
}H[M];//存放海明码信息

int P[N];//存放校验位信息
int C[N];//存放检验位信息
int m[M + N];//存放需要纠正的海明码
int DC = 0, PC = 0, EC = 0, HC = 0, ERROR = 0, ERROR1 = 0;//DC是数据的位数,PC是校验位的位数,不能在此更改这些数据的值,否则出错

void showInf(){//显示信息
	printf("-------------------------------------------------------------------------------\n");
	printf("*****************************************海明码********************************\n");
	printf("***************************************输入格式如下:***************************\n");
	printf("*****************************如果要输入1101,需输入 1 1 0 1 -1******************\n");
	printf("**************输入数据 1 1 0 1 -1的过程中,不能回退删除,否则会发生错误*********\n");
	printf("-----------------------------选择操作:----------------------------------------\n");
	printf("1 输入数据后生成海明码 2 输入海明码  3 清屏  4 纠正海明码 5 返回主页 6 结束程序\n\n");
	printf("请选择操作,只能输入一个数: ");
}

int inputData(int choice){//输入数据或海明码,参数是1就表示输入的是数据,参数是2就表示输入的是海明码。返回-1就表示输入数据错误。
	int data, i = 1;
	scanf("%d", &data);
	if (data != 0 && data != 1){
		system("cls");
		printf("\n\n提示信息:输入格式不正确,第一位只能是0或1,请重新选择操作\n\n\n");
		return -1;
	}
	DC = 1;
	PC = 1;
	while (data != -1){
		if (pow(2.0, PC - 1) != i){//存放数据值到海明码结构体中
			H[i].flag = 0;//数据位,位置标记为0
			H[i].value = data;
			m[i] = data;
			DC++;
			scanf("%d", &data);
		}
		else if (pow(2.0, PC - 1) == i&&choice == 1){//设置校验位
			H[i].flag = 1;//校验位,位置标记为1
			PC++;
		}
		if ((pow(2.0, PC - 1) == i) && choice == 2){//把海明码中的校验位放到校验位数组中,以便用这个校验位和利用数据生成的校验位异或来计算哪一位出错
			H[i].flag = 1;
			P[PC++] = data;
			m[i] = data;
			scanf("%d", &data);
		}
		i++;
	}
	//累计次数是多加了一次,减回来。
	DC--;
	PC--;
	if (PC + DC < 3)
		ERROR = 2;
	return 0;
}

void caculateHammingCode(){//根据海明码结构体中数据位的数据值计算海明码,并把正确的海明码放到海明码结构体中。
	int m, j, k, temp = 0;
	for (m = 3; m <= DC + PC; m++){
		temp = 0;
		if (H[m].flag == 0){//数据位
			for (j = PC; j >= 1; j--){
				k = (int)pow(2.0, j - 1);
				if (k < m){
					H[k].value = H[k].value^H[m].value;
					temp = k;
					break;
				}
			}
			for (k = j - 1; k >= 1; k--){
				if (H[k].flag == 1){//校验位
					if (temp + pow(2.0, k - 1) == m){
						H[k].value = H[k].value^H[m].value;
						break;
					}
					if (temp + pow(2.0, k - 1) < m){
						H[k].value = H[k].value^H[m].value;
						temp = temp + pow(2.0, k - 1);
					}
				}
			}
		}
	}
}

void caculateC(int pc){//计算检验位的值,并放到检验位的数组中。接受的参数是校验位的个数
	int i, k;
	for (i = 1; i <= pc; i++){
		k = (int)pow(2.0, i - 1);
		C[i] = P[i] ^ H[k].value;
	}
}

void initial(){//使海明码结构体清零
	int i;
	for (i = 1; i <= DC + PC; i++){
		H[i].value = 0;
	}
}

void showHammingCode(int dc, int pc){//打印海明码结构体中的值。接受的参数是dc为数据位的个数,pc为校验位的个数。
	int i;
	printf("\n\n生成的海明码为:\n");
	for (i = 1; i <= dc + pc; i++){
		printf("第%d位:", i);
		printf("  %d\n", H[i].value);
	}
	printf("\n");
}
void judge(int pc){//判断出哪一位出错,并打印出这一位是数据位还是校验位
	int i, result = 0, flag = 0;
	//system("cls");
	for (i = 1; i <= pc; i++){
		if (C[i] == 1) flag++;

	}
	for (i = 1; i <= pc; i++){
		if (flag == 0)
			result = result + C[i] * pow(2.0, pc - i);
		else result = result + C[i] * pow(2.0, i - 1);
	}
	if (result != 0){
		printf("\n\n\n提示信息:第%d位出错,", result);
		EC = result;
		if (flag >= 2)
			printf(" 这一位是数据位!\n\n");
		else printf(" 这一位是校验位!\n\n");
	}
	else printf("\n\n提示信息:海明码正确!\n\n\n");
}
void mend(int mc){//纠正错误的海明码。并打印出纠正前错误的海明码和纠正后正确的海明码
	int i, k = 0, p = 1;
	if (EC == 0){
		printf("\n\n提示信息:海明码正确或者已经纠正或者海明码为空,请重新选择操作\n\n\n");
		return;
	}
	system("cls");
	printf("\n提示信息:海明码第%d位有错误\n\n", EC);
	printf("\n输入的错误的海明码是:\n\n");
	for (i = 1; i <= mc; i++)
		printf("第%d位%d\n", i, m[i]);
	m[EC] = !m[EC];
	printf("\n\n纠正后的正确的海明码是:\n\n");
	for (i = 1; i <= mc; i++)
		printf("第%d位%d\n", i, m[i]);
}

void run(int command){//运行不同的操作模式,command为1表示输入的数据位,command为2表示输入的是海明码
	int i;
	if (command == 1){//输入的是数据
		printf("\n\n请输入数据,以-1做结束符:");
		i = inputData(1);//返回输入的数据位数
		if (i == -1) return;
		caculateHammingCode();
		showHammingCode(DC, PC);
		initial();
		HC = PC + DC;
		DC = 0;
		PC = 0;
	}
	else {//输入的是海明码
		printf("\n\n请输入海明码,以-1做结束符,只能有一位错误:");
		i = inputData(2);
		if (i == -1) return;
		caculateHammingCode();
		if (ERROR == 1 || ERROR == 2) {
			system("cls");
			printf("\n\n提示信息:输入格式不正确,第一位只能是0或1,并且输入的位数要大于等于3.请重选操作\n\n\n");
			ERROR = 0;
			PC = 0;
			return;
		}
		caculateC(PC);
		judge(PC);
		initial();
		HC = PC + DC;
		PC = 0;
		DC = 0;
	}
}
void main(){
	int function, flag = 1, command, md = 0, back = 0;
	showInf();
	scanf("%d", &function);
	while (flag){
		switch (function){
		case 1: command = 1;
			run(command);//输入数据
			md = 0;
			break;
		case 2: command = 2;//输入海明码
			run(command);
			md = 1;
			break;
		case 3: system("cls");//清屏
			break;
		case 4:system("cls");
			if (md == 1)//纠正海明码
				mend(HC);
			else printf("\n\n提示信息:您还没有输入海明码或者输入的海明码已经被纠正了.\n\n\n");
			md = 0;
			EC = 0;
			break;
		case 5: system("cls");//返回主页
			back = 1;
			break;
		case 6:exit(1);//退出
		default: system("cls");
			printf("\n\n\n提示信息:操作不正确,请重新选择!\n\n");
			break;
		}
		if (back == 1){
			showInf();
			back = 0;
		}
		else {
			printf("\n1 输入数据后生成海明码 2 输入海明码  3 清屏  4 纠正海明码 5 返回主页 6 结束程序\n\n");
			printf("请选择操作,只能输入一个数: ");
		}
		scanf("%d", &function);
	}
}

测试:
以数据10011010进行测试,结果如下:
在这里插入图片描述
海明编码结果为:011100101010
如我们传送的011100101010,但接收到的是011100101110 (第10位出现错误)。校验如下:
在这里插入图片描述
纠正结果如下:
在这里插入图片描述
接收到的是011100101110 (第10位出现错误),但实际传送的011100101010


以上博文整理自:

  1. williamgavin_存储器(四)-- 汉明码
  2. 刘扬俊_汉明码 – 计算机网络 – 全网最通俗的讲解
  3. Vic_Chen_is_here_存储器的校验–汉明码(Hanming Code)
  4. flysky2011_海明码的改进算法,请多多指教!
发布了96 篇原创文章 · 获赞 294 · 访问量 84万+

猜你喜欢

转载自blog.csdn.net/baidu_38172402/article/details/105396763