接上一篇。为了配合学生借助 taskbus进行纯控制台STDIO模块的开发,我们收集了手头的通用纠错算法,便于学生进行开发学习。今天介绍李德-所罗门(RS)编码。
RS码是一种多进制的循环纠错编码。循环纠错编码的思路是对待校验的信息做除法(模N除),求余数。如果余数为0(或者某个特定的值),则说明信息是正确的。如果不是0,可以根据余数判断是哪些位置出现了错误。
这种模N的除法,不是通常意义的算术除法,而是基于符号多项式的代数除法。具体到RS码,是在Galois Field(伽罗华域,GF)中进行的。这个域是一个闭合的符号系统,加减乘除运算得到的结果依旧是域的元素。这和算术运算有本质的区别。
1. 伽罗华域
1.1 构造
按照GF(
2
m
2^m
2m)构造元素,则符号共有m个状态,超过m个状态,就绕回去了。比如,GF(2)上的4次多项式
g
(
x
)
=
x
4
+
x
3
+
1
g(x)=x^4+x^3+1
g(x)=x4+x3+1
构造出
G
F
(
2
4
)
GF(2^4)
GF(24)上的域元素为:
using namespace RC_DICTCODEC;
const int mv = 4;
//x^0 is at g[0]
char g[mv+1] = {1,0,0,1,1};
Galois_domain GFm(g,mv);
puts(GFm.printGF().c_str());
return 0;
Output:
元素 | 表达式 | 高幂在前二进制表示 |
---|---|---|
a 0 a^0 a0 | 1 1 1 | 0001 0001 0001 |
a 1 a^1 a1 | x x x | 0010 0010 0010 |
a 2 a^2 a2 | x 2 x^2 x2 | 0100 0100 0100 |
a 3 a^3 a3 | x 3 x^3 x3 | 1000 1000 1000 |
a 4 a^4 a4 | x 3 + 1 x^3+1 x3+1 | 1001 1001 1001 |
a 5 a^5 a5 | x 3 + x + 1 x^3+x+1 x3+x+1 | 1011 1011 1011 |
a 6 a^6 a6 | x 3 + x 2 + x + 1 x^3+x^2+x+1 x3+x2+x+1 | 1111 1111 1111 |
a 7 a^7 a7 | x 2 + x + 1 x^2+x+1 x2+x+1 | 0111 0111 0111 |
a 8 a^8 a8 | x 3 + x 2 + x x^3+x^2+x x3+x2+x | 1110 1110 1110 |
a 9 a^9 a9 | x 2 + 1 x^2+1 x2+1 | 0101 0101 0101 |
a 10 a^{10} a10 | x 3 + x x^3+x x3+x | 1010 1010 1010 |
a 11 a^{11} a11 | x 3 + x 2 + 1 x^3+x^2+1 x3+x2+1 | 1101 1101 1101 |
a 12 a^{12} a12 | x + 1 x+1 x+1 | 0011 0011 0011 |
a 13 a^{13} a13 | x 2 + x x^2+x x2+x | 0110 0110 0110 |
a 14 a^{14} a14 | x 3 + x 2 x^3+x^2 x3+x2 | 1100 1100 1100 |
0 | 0 | 0 |
构造的方法很直观:
a
i
+
1
=
m
o
d
(
x
a
i
,
x
4
+
x
3
+
1
)
a^{i+1}=mod(xa^i, x^4+x^3+1)
ai+1=mod(xai,x4+x3+1)
1.2. GF运算
域上的加减乘除,均是查表完成。
printf ("a + a2 = a%d\n",GFm.I(GFm.mod_m_add(GFm(1),GFm(2))));
printf ("a * a2 = a%d\n",GFm.I(GFm.mod_m_cross(GFm(1),GFm(2))));
printf ("a / a2 = a%d\n",GFm.I(GFm.mod_m_div(GFm(1),GFm(2))));
printf ("a ^ 37= a%d\n",GFm.I(GFm.mod_m_pow(GFm(1),37)));
printf ("a2 + a5 = a%d\n",GFm.I(GFm.mod_m_add(GFm(2),GFm(5))));
printf ("a2 * a5 = a%d\n",GFm.I(GFm.mod_m_cross(GFm(2),GFm(5))));
printf ("a2 / a5 = a%d\n",GFm.I(GFm.mod_m_div(GFm(2),GFm(5))));
printf ("a2 ^ 120= a%d\n",GFm.I(GFm.mod_m_pow(GFm(2),120)));
输出:
a + a2 = a13
a * a2 = a3
a / a2 = a14
a ^ 37= a7
a2 + a5 = a6
a2 * a5 = a7
a2 / a5 = a12
a2 ^ 120= a0
有了这个运算规则,则可以把很长的一段数据切割为以符号a的幂为表达的多项式。
2. 编码
2.1 把二进制流切割为符号a
二进制流是0,1表示的无限长数据。以每m个比特为单位,可以切割为符号。高位在前还是低位在前,全看心情,只要保证后面还原回去时一致即可。
比如,接着上面的例子,m=4,则每4比特一个单位。
0110 1101 0001 0000 1001 1100…
以第0位置为a^0,则上述数据的符号表示为:
a 13 a^{13} a13, a 5 a^{5} a5, a 3 a^{3} a3,0, a 4 a^{4} a4, a 12 a^{12} a12,…
2.2 RS编码的生成多项式
RS编码生成多项式是使用连乘的方法定义的:
G
(
x
)
=
∏
i
=
0
N
−
1
(
x
−
a
S
+
i
)
G(x)=\prod_{i=0}^{N-1}(x-a^{S+i})
G(x)=i=0∏N−1(x−aS+i)
其中,N是连乘次数,越高则纠错能力越强。S是起始幂,常见的是0,当然按照心情可以随便设置,设置太大了,就会环绕回去,没有意义。
接着上面的例子,我们产生多项式S=1,N=2:
using namespace RC_DICTCODEC;
const int mv = 4;
char g[mv+1] = {1,0,0,1,1};
Galois_domain A(g,mv);
//G是GF(2^m)上的生成式
std::vector<unsigned short> Gx = A.geneGx(A(1),2);
printf ("Gx=");
for (size_t i=0;i<Gx.size();++i)
printf("%1X",Gx[i]);
printf ("\n");
也就是 G ( x ) = ( x − a ) ( x − a 2 ) = x 2 − ( a + a 2 ) x + a a 2 G(x)=(x-a)(x-a^2)=x^2-(a+a^2)x+aa^2 G(x)=(x−a)(x−a2)=x2−(a+a2)x+aa2
输出:
Gx=1 6 8
查表,得到
G
(
x
)
=
x
2
+
a
13
x
+
a
3
G(x)=x^2+a^{13}x+a^3
G(x)=x2+a13x+a3
2.3 除法编码
RS码的码长是固定的,为 2 m − 1 2^m-1 2m−1个符号。上面的例子就是15个符号。对应Gx,15个符号除出来的余数,有2个符号。因此,留给信息的符号为15-2=13个。
只要把连续的二进制数据切割为分组长度为13的组,并分别进行编码。就能形成编码序列。
rc_codec_rs codec(n,k,Gx.data(),Gx.size(),A);
unsigned short code[n] = rnd_syms(15,13);
codec.encode(code,code);
3. 纠错译码
RS码一般采用迭代译码。参考Simon Rock liff 在1991年6月26日的开源代码版本。需要注意的是,该算法中数组0下标表示的是多项式的最低位。 我们已经在函数开始和结束做了转换,以弥和此差异。
同时, 由于原始算法根从 a 1 a^1 a1开始,如果起始根幂不是1, 在译码完毕后,还要做旋转。
//Add err
code[5] ^= 0x02;
unsigned short decoded[15] = {0};
codec.decode_rs(code,decoded);
unsigned short check[n] = {0};
codec.chcode(decoded,check);
4. C语言接口重封装
由于C++语言对其他语言包括python,C#不友好,我们参考这篇文章的方法,提供一个非常通用的C语言接口。
/**
* @brief oc_rs_new_codec 分配一个RS纠错码编译码器
* @param n 整个编码后的块长度(2^m符号),单位是1个伽罗华域符号
* @param g_m 张成伽罗瓦域的域多项式的级数(m)
* @param g 张成伽罗瓦域的域多项式的01表示
* @param start_order 生成多项式连乘形式首个根的幂阶数
* @param t 设计纠错个数
* @param g_bigendian g的大小端
* @return RS码编码译码器
*/
void * oc_rs_new_codec(size_t n, size_t g_m, const char g[/*g_m+1*/], size_t start_order, size_t t, int g_bigendian);
/**
* @brief oc_rs_delete_codec 释放RS纠错码编译码器 codec
* @param codec RS码编码译码器
*/
void oc_rs_delete_codec(void * codec);
/**
* @brief oc_rs_get_k 获取RS编码的每轮信息符号数
* @param codec RS码编码译码器
* @return 信息符号个数
*/
int oc_rs_get_k(void * codec);
/**
* @brief oc_rs_set_status 设置RS码的余数初态(默认是全0)
* @param codec RS码编码译码器
* @param status 余数初态
*/
void oc_rs_set_status(void * codec,unsigned short status[/*n-k*/]);
/**
* @brief oc_rs_get_Gx 获取RS编码的生成多项式
* @param codec RS码编码译码器
* @param Gx 二进制表述(不是域上标幂数)
* @param maxLen Gx的最大容积。
* @return Gx的真正容积
*/
int oc_rs_get_Gx(void * codec, unsigned short Gx[], int maxLen);
//对信息data(二进制表述,不是域幂上标数)进行纠错编码,存储到code中。
/**
* @brief oc_rs_encode 进行纠错编码
* @param codec RS码编码译码器
* @param data 信息data
* @param code 编码code
* @return 0表示成功
*/
int oc_rs_encode(void * codec,const unsigned short data[/*k*/], unsigned short code[/*n*/]) ;
//对数据code(二进制表述,不是域幂上标数)进行纠错译码,存储到data中。
/**
* @brief oc_rs_decode 进行RS纠错译码
* @param codec RS码编码译码器
* @param code 数据code
* @param result 纠错后的数据
* @param checkedCode 除法校验后的结果
* @return 0表示成功
*/
int oc_rs_decode(void * codec,const unsigned short code[/*n*/], unsigned short result[/*n*/], unsigned short * checkedCode/*n*/) ;
5. 测试GUI
如果有Qt,则可以编译并使用ocgui测试:
6. 代码链接
参考
https://gitcode.net/coloreaglestdio/taskbus_course/-/tree/master/src/a0channel_algs/ocalg
https://gitcode.com/colorEagleStdio/taskbus_course/tree/master/src/a0channel_algs/ocalg