Polar SCL的C语言实现

Part 1.SCL背景简介

极化码作为我国5G通信的主导方案,是目前唯一能够被严格证明可以达到香农极限的方法。我们已经在前面的博客中讲解了极化码家族得到最广泛应用的译码方式——SC,虽然SC算法简单、运行效率高,但在稍微极端的通讯环境下,我们还是需要使用算法更复杂、纠错性能更强大的SCL。如果是SC是使用迅速判别并确定译码结果的贪心算法,那么SCL则是将多种译码的可能结果进行更深的比较,“放长线钓大鱼”,从而得到更加靠谱的译码结果。

至于SC与SCL的真实名称,咱直接上图:

顾名思义,SCL需要将不同的译码结果放入列表进行比较,至于怎么比较、怎样确定要选择的路径,我们现在开始介绍。

Part 2.SCL译码原理

在编码调制、加噪声后(与SC的编码、加噪声方法相同),我们以码长64的4-SCL为例,即4条路经的SCL,进行译码。

第一步,从右至左进行f运算,译码算法采用的是SC一维数组的方式,由于SCL有多条路径,我们就需要给数组增加一个维度用于标记路径。

第二步,在出现信息位之前,仍然没开始分裂,每条路径的译码结果、b值均相同。因此,仍使用一维SC的译码方式。

第三步,遇到首次分裂的两个信息位,开始分裂。

   

第四步,判别并选择其中两条最佳路径,即筛选出总路径的1/2,然后将最佳路径分别复制到废除的路径。

判别方法:我们在此称译码运算至最左端的比特为LLR,LLR是浮点数,在SC当中遵循:固定位,译为0;信息位,LLR>0译成0,LLR<0译成1。而在SCL中,在固定位,译码为0;在信息位,分裂成0与1作为可能的译码结果。我们用0和1分别往下译码,得到下一个译码的LLR。此时,我们引用一个定义——PM值作为惩罚机制的累加值,注意,PM初始值为0。如果下一位是固定比特位,若LLR>0,PM值不变;若LLR<0,PM=PM+LLR,因此,这相当于对在固定位译码出错的一种惩罚机制,PM值越小,惩罚程度越大。在信息位时,我们将PM值较大的两条路径保留并且复制,然后继续分裂,从而持续的保留其中最好的两条路径,最终,我们挑最佳的一条路径作为最终译码结果。

例如,上图从左到右4条路经,假如计算得到的LLR值最大的两条路径是第1条与第2条,那么保留1、2条,并且将第1条路径复制给第3条,第2条复制给第四条。

于是,原有的4条不同路经缩减成2种不同路径,我们在这两种不同路径的基础上在信息位继续分裂,以此类推。

如果看到这里,还是感到很抽象,不必纠结,请直接看下面的硬核操作。

Part 3.代码实战

程序要求:写码长为64的4-SCL译码,输入模拟信噪比(建议范围:1.5~3.5dB),程序按照比特的排列规则,随机生成一次码长为64的比特序列,模拟其经过编码、调制、受白噪声干扰,被接收后,将比特序列进行SC译码纠错,还原信息序列,返回给用户端。

输出显示:1.原码序列;2.译码序列; 3.比较原码与译码序列是否完全相同,完全相同,输出right;不完全相同,输出wrong。

注意:上文提到,对于使用数组存储数据的极化码程序,SCL需要在SC的基础上增加一个维度,但在这里,我们表面上数据处理的对象仍是一维数组,但思想仍是二维的方法,具体方法将会在下面讲解。

第一步:定义。我们使用Encode结构体处理编码,使用Decode结构体处理译码。

#include<stdio.h>
#include<math.h>
#include<stdlib.h>
#include<time.h>//种子随机数头文件 
const int N=64; 
const int n=7;
const int L=4;
float t,PM[L]={0,0,0,0};//4条路经对应的PM值
int v[L]={0,1,2,3};//用于排序函数sort
int CBR[N]={            
        0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,1,
	0,0,0,0,0,0,0,1,
	0,0,1,1,1,1,1,1,
	0,0,0,0,0,0,1,1,
	0,1,1,1,1,1,1,1,
	0,1,1,1,1,1,1,1,
	1,1,1,1,1,1,1,1};//固定比特集
typedef struct Encode{ 
	int code[N*2];//编码 
}Encode;
typedef struct Decode{
	float a[N*2];//浮点比特 
	int b[N];//b值 
	int LLR[N];//译码值 
}Decode;

第二步,函数声明。

float add_gassrand(float EbNo);//加噪声函数
float gaussrand();//生成噪声函数
float f(float Lin1,float Lin2);
float g(float Lin1,float Lin2,int b);
void first_4(int i);
void first_4_plus(int i);
void function_back(int i,int v,int l);
void first_split(int i);
void second_split(int i);
void sort(int tail);

第三步,实例化结构体(类似C++实例化对象)。虽然只有四条路径,但我们实例化5个Decode结构体,是因为第五个实例要用作复制、交换、赋值的“中介”,作用类似于将a与b的数值交换,使用c存储。

Encode encode;
Encode *pE=&encode;
Decode decode1,decode2,decode3,decode4,decode5;
Decode *pD[5];
int main()
{
	pD[0]=&decode1;
	pD[1]=&decode2;
	pD[2]=&decode3;
	pD[3]=&decode4;
	pD[4]=&decode5;

第四步,编码、调制、加噪声。

main()函数内部:

srand((unsigned)time(NULL));//设定种子随机数,使随机数随时间改变 
	int Vi,e,qq,ss,sum=0,s=0;
	float EbNo;
	printf("EbNo(dB):");
        scanf("%f",&EbNo);//输入信噪比
        printf("\n");
	for(Vi=0;Vi<N;Vi++)
	{if(CBR[Vi])pE->code[Vi]=pE->code[Vi+N]=rand()%2; 
	//CBR数组中非0的元素是信息比特位,在对应行产生0或1的随机数 
	else pE->code[Vi]=pE->code[Vi+N]=0;
	//固定比特位仍然为0 
	}
//编码部分 
	int h=N,y1,o;
for(y1=0;y1<n-1;y1++)
{
	for(o=0;o<N;o=o+(2*N)/h)
	{for(e=o;e<o+N/h;e++)
	{pE->code[e+N]=pE->code[e+N]^pE->code[e+N+N/h]?1:0;}
	}
	h/=2;
}
//调制 
for(y1=0;y1<N;y1++)
{pD[0]->a[y1+N]=pE->code[y1+N]?-1.0:1.0;}
//加噪声 
add_gassrand(EbNo);

main()函数外部——加噪声函数

float gaussrand(){
	static float V1,V2,S;
	static int phase=0;
	float X;
	if (!phase){
		do{
		   float U1=(float)rand()/RAND_MAX;
		   float U2=(float)rand()/RAND_MAX;
	           V1=2*U1-1;
		   V2=2*U2-1;
		   S=V1*V1+V2*V2;
		}while(S>=1||!S);
		X=V1*sqrt(-2*log(S)/S);
	}
	else X=V2*sqrt(-2*log(S)/S);
	phase=1-phase;
	return X;
}
float add_gassrand(float EbNo){
	int i;
	float Sigma2;//噪声方差
	float Sigma;//噪声标准差
	float Rate=(N/2)/(float)N;//数据的传输速率
	Sigma2=(float)1/(2*Rate*pow(10,(EbNo / 10.0)));//白噪声的方差
	Sigma=sqrtf(Sigma2);//白噪声的标准差
	for(i=0;i<N;i++)
	{	
		pD[0]->a[i+N] = 2 * (pD[0]->a[i+N] + gaussrand() * Sigma) / Sigma2;
                //先只对第一条路径的译码赋值,待第一次分裂前,会将其译码值、b值与浮点LLR值赋值给2、3、4路径
	}
	return 0;
}

第五步,译码。

1).f运算,在遇到第一个信息位分裂之前,仍使用SC的译码方法,只不过在这段译码中,比特位均为固定位,因此,b值、LLR译码值均为0,此部分译码函数名为first_4(),我们直接将这部分译码函数简化成以下片段:

void first_4(int i)
{pD[0]->b[i]=pD[0]->LLR[i]=0;
pD[0]->b[i+1]=pD[0]->LLR[i+1]=0;
pD[0]->b[i+2]=pD[0]->LLR[i+2]=0;
pD[0]->b[i+3]=pD[0]->LLR[i+3]=0;
}

而在主函数当中,它是以这样的形式被调用的:

/译码部分 
int j,i,u=N,p=N;
for(j=n;j>0;j--)
{u/=2;
for(i=0;i<u;i++)
{pD[0]->a[i+u]=f(pD[0]->a[i+p],pD[0]->a[i+u+p]);}
p/=2;
}
int password_1[6]={0,1,0,2,0,1}; 
int clue=0,clue0=0,clue1=0,clue2=0;
for(Vi=0;Vi<6;Vi++)
{
switch(password_1[Vi])
{
	case 0:first_4(clue);clue+=4;break;
	case 1:function_back(clue0,2,0);clue0+=8;break;
	case 2:function_back(clue1,3,0);clue1+=16;break;
}
}

至于function_back函数的功能,我们前面也讲过,不过这次,function_back函数多了一个形参,我们待会解释,先上代码:

inline void function_back(int i,int v,int l)
{int u=(int)pow(2.0,v);
if(v==3){
for(int x=i;x<i+4;x++)
{pD[l]->b[x]=pD[l]->b[x]^pD[l]->b[x+4]?1:0;}
}
else if(v==4){
for(int x=i+8;x<i+12;x++)  
pD[l]->b[x]=pD[l]->b[x]^pD[l]->b[x+4];
for(int x=i;x<i+8;x++)
{pD[l]->b[x]=pD[l]->b[x]^pD[l]->b[x+8]?1:0;}
}
else if(v==5){
for(int x=i+24;x<i+28;x++)  
pD[l]->b[x]=pD[l]->b[x]^pD[l]->b[x+4];
for(int x=i+16;x<i+24;x++)
{pD[l]->b[x]=pD[l]->b[x]^pD[l]->b[x+8]?1:0;}
for(int x=i;x<i+16;x++)
{pD[l]->b[x]=pD[l]->b[x]^pD[l]->b[x+16]?1:0;}
}
for(int x=i;x<i+u;x++)
{pD[l]->a[x+2*u]=g(pD[l]->a[x+2*u],pD[l]->a[x+3*u],pD[l]->b[x]);
}
int p1=u,p2=0;
for(int j=v;j>0;j--)
{for(int x=i;x<i+p1/2;x++)
{pD[l]->a[x+u+p1/2]=f(pD[l]->a[x+2*p1+p2],pD[l]->a[x+2*p1+p2+p1/2]);}
p2+=p1/2;
p1/=2;
}
} 

为确保代码完整性,fg运算函数咱也照常展示:

float f(float Lin1,float Lin2)
{float Lout,s,min;
int sign;
s=Lin1*Lin2;
if(s>0)sign=1;
else sign=-1;
min=fabs(Lin1)<=fabs(Lin2)?fabs(Lin1):fabs(Lin2);
Lout=sign*min;
return Lout;
}
float g(float Lin1,float Lin2,int b)
{float Lout;
Lout=pow((float)-1.0,b)*Lin1+Lin2;
return Lout;
}

请注意,这几个函数声明与具体内容都是在主函数之外的。

2).在第一次分裂之前,将路径1的译码值LLR、b值与浮点比特值a赋值给2、3、4路径,第5条路经不需要。

//深度拷贝
for(int y=0;y<L;y++)
for(int x=0;x<N*2;x++)
pD[y]->a[x]=pD[0]->a[x];
for(int y=0;y<L;y++)
for(int x=0;x<N/4;x++)
pD[y]->b[x]=pD[0]->b[x];
for(int y=0;y<L;y++)
for(int x=0;x<clue;x++)
pD[y]->LLR[x]=pD[0]->LLR[x];

3).第一次分裂函数如下,具体分裂点为函数中的“i+3",即第16位比特:

void first_split(int i)
{float temp[L];
for(int l=0;l<L;l++){
if(!CBR[i])pD[l]->b[i]=0;
else pD[l]->b[i]=pD[l]->a[i+1]>0.0?0:1;
pD[l]->LLR[i]=pD[l]->b[i];
pD[l]->a[i+1]=g(pD[l]->a[i+2],pD[l]->a[i+3],pD[l]->b[i]);
if(!CBR[i+1])pD[l]->b[i+1]=0;
else pD[l]->b[i+1]=pD[l]->a[i+1]>0.0?0:1;
pD[l]->LLR[i+1]=pD[l]->b[i+1];
pD[l]->b[i]=pD[l]->b[i]^pD[l]->b[i+1]?1:0; 
temp[l]=pD[l]->a[i+2]=g(pD[l]->a[i+4],pD[l]->a[i+6],pD[l]->b[i]);
pD[l]->a[i+3]=g(pD[l]->a[i+5],pD[l]->a[i+7],pD[l]->b[i+1]);
pD[l]->a[i+2]=f(pD[l]->a[i+2],pD[l]->a[i+3]);
if(!CBR[i+2])pD[l]->b[i+2]=0;
else pD[l]->b[i+2]=pD[l]->a[i+2]>0.0?0:1;
pD[l]->LLR[i+2]=pD[l]->b[i+2];
pD[l]->a[i+3]=g(temp[l],pD[l]->a[i+3],pD[l]->b[i+2]);
//split
if(l<L/2){pD[l]->b[i+3]=0;
if(pD[l]->a[i+3]<0.0)PM[l]+=pD[l]->a[i+3];
}
else{pD[l]->b[i+3]=1;
if(pD[l]->a[i+3]>0.0)PM[l]-=pD[l]->a[i+3];
}
pD[l]->LLR[i+3]=pD[l]->b[i+3];
pD[l]->b[i+2]=pD[l]->b[i+2]^pD[l]->b[i+3]?1:0;
pD[l]->b[i]=pD[l]->b[i]^pD[l]->b[i+2]?1:0;
pD[l]->b[i+1]=pD[l]->b[i+1]^pD[l]->b[i+3]?1:0;
}
}

类似的,第二次分裂函数也恰巧在第”i+3“位比特分裂,即第24位比特:

void second_split(int i)
{float temp[L];
for(int l=0;l<L;l++){
if(pD[l]->a[i+1]<0.0)PM[l]+=pD[l]->a[i+1];
pD[l]->LLR[i]=pD[l]->b[i]=0;
pD[l]->a[i+1]=g(pD[l]->a[i+2],pD[l]->a[i+3],pD[l]->b[i]);
if(pD[l]->a[i+1]<0.0)PM[l]+=pD[l]->a[i+1];
pD[l]->LLR[i+1]=pD[l]->b[i+1]=0;
pD[l]->b[i]=pD[l]->b[i]^pD[l]->b[i+1]?1:0; 
temp[l]=pD[l]->a[i+2]=g(pD[l]->a[i+4],pD[l]->a[i+6],pD[l]->b[i]);
pD[l]->a[i+3]=g(pD[l]->a[i+5],pD[l]->a[i+7],pD[l]->b[i+1]);
pD[l]->a[i+2]=f(pD[l]->a[i+2],pD[l]->a[i+3]);
if(pD[l]->a[i+2]<0.0)PM[l]+=pD[l]->a[i+2];
pD[l]->LLR[i+2]=pD[l]->b[i+2]=0;
pD[l]->a[i+3]=g(temp[l],pD[l]->a[i+3],pD[l]->b[i+2]);
}
//split
for(int l=0;l<L;l++){
if(!(l%2)){pD[l]->LLR[i+3]=pD[l]->b[i+3]=0;
if(pD[l]->a[i+3]<0.0)PM[l]+=pD[l]->a[i+3];
}
else{pD[l]->LLR[i+3]=pD[l]->b[i+3]=1;
if(pD[l]->a[i+3]>0.0)PM[l]-=pD[l]->a[i+3];}
pD[l]->b[i+2]=pD[l]->b[i+2]^pD[l]->b[i+3]?1:0;
pD[l]->b[i]=pD[l]->b[i]^pD[l]->b[i+2]?1:0;
pD[l]->b[i+1]=pD[l]->b[i+1]^pD[l]->b[i+3]?1:0;
}
}

在main函数中,它们是这样被调用的:

//第一次分裂 
first_split(clue);clue+=4;
//由于function_back函数内部并无循环计算四条路经的功能,因此,我们特别给其增加多一个参数,循环调用,以覆盖全部路径的计算
for(int x=0;x<L;x++)function_back(clue2,4,x);clue2+=32;
first_4_plus(clue);clue+=4;
for(int x=0;x<L;x++)function_back(clue0,2,x);clue0+=8;
//第二次分裂
second_split(clue);clue+=4;

4).最后是first_4_plus函数,这个名字一听就知道是first_4函数的加强版,它的功能相对复杂:在固定位,它计算每条路径的PM值大小;在信息位,它调用sort函数对各路径PM值进行排序,根据sort函数筛选出两条最佳路径,然后继续分裂。因此,它涵盖的功能非常强大,它也可以直接替代first_4函数,但遵循最简化原则,我们不必这么做。

先介绍sort排序函数,它会将最佳路径复制给第5条路径,然后,将次佳路径复制给路径2和4,接着,再将路径5复制给1和3,请务必留意,数组的第一位元素是从0数起的。至于复制的范围,我们这次讲的是保守范围,比起lazy copy,会浪费大量的时间进行循环复制:浮点比特a是该比特值到码长的1.5倍;LLR值作为一种结果而非中间计算,它只需呈现已有的译码结果;而b值的复制采用全码长覆盖,因为假使你计算了第300位的b值,而全面的b值已覆盖到第512位,你仍有较大概率会使用其他(更好的)路径剩余的b值去进行g运算。

void sort(int tail)
{int i,j,c;
for(i=0;i<L;i++)v[i]=i;
for(i=0;i<L-1;i++)
   for(j=0;j<L-1-i;j++){
   	if(PM[v[j]]<PM[v[j+1]]){
   		c=v[j];
   		v[j]=v[j+1];
   		v[j+1]=c;
	   }
   }
tail+=1;
for(i=tail;i<N+N/2;i++)pD[4]->a[i]=pD[v[0]]->a[i];
for(i=0;i<N;i++){
	pD[4]->b[i]=pD[v[0]]->b[i];
	pD[4]->LLR[i]=pD[v[0]]->LLR[i];
}
for(i=tail;i<N+N/2;i++)pD[1]->a[i]=pD[3]->a[i]=pD[v[1]]->a[i];
for(i=0;i<N;i++){
	pD[1]->b[i]=pD[3]->b[i]=pD[v[1]]->b[i];
	pD[1]->LLR[i]=pD[3]->LLR[i]=pD[v[1]]->LLR[i];
}
PM[1]=PM[3]=PM[v[1]];
for(i=tail;i<=N+N/2;i++)pD[0]->a[i]=pD[2]->a[i]=pD[4]->a[i];
for(i=0;i<N;i++){
	pD[0]->b[i]=pD[2]->b[i]=pD[4]->b[i];
	pD[0]->LLR[i]=pD[2]->LLR[i]=pD[4]->LLR[i];
}
PM[0]=PM[2]=PM[v[0]];
}

接着,first_4_plus函数也就很好理解了:

void first_4_plus(int i)
{float temp[L]={};
if(!CBR[i])
for(int l=0;l<L;l++){pD[l]->b[i]=0;
if(pD[l]->a[i+1]<0.0)PM[l]+=pD[l]->a[i+1];
}
else{sort(i-1);
for(int l=0;l<L;l++)
if(l<L/2){pD[l]->b[i]=0;
if(pD[l]->a[i+1]<0.0)PM[l]+=pD[l]->a[i+1];
}
else{pD[l]->b[i]=1;
if(pD[l]->a[i+1]>0.0)PM[l]-=pD[l]->a[i+1];
}
}
for(int l=0;l<L;l++){
pD[l]->LLR[i]=pD[l]->b[i];
pD[l]->a[i+1]=g(pD[l]->a[i+2],pD[l]->a[i+3],pD[l]->b[i]);
}
if(!CBR[i+1])
for(int l=0;l<L;l++){pD[l]->b[i+1]=0;
if(pD[l]->a[i+1]<0.0)PM[l]+=pD[l]->a[i+1];
}
else{sort(i);
for(int l=0;l<L;l++)
if(l<L/2){pD[l]->b[i+1]=0;
if(pD[l]->a[i+1]<0.0)PM[l]+=pD[l]->a[i+1];
}
else{pD[l]->b[i+1]=1;
if(pD[l]->a[i+1]>0.0)PM[l]-=pD[l]->a[i+1];
}
}
for(int l=0;l<L;l++){
pD[l]->LLR[i+1]=pD[l]->b[i+1];
pD[l]->b[i]=pD[l]->b[i]^pD[l]->b[i+1]?1:0; 
temp[l]=pD[l]->a[i+2]=g(pD[l]->a[i+4],pD[l]->a[i+6],pD[l]->b[i]);
pD[l]->a[i+3]=g(pD[l]->a[i+5],pD[l]->a[i+7],pD[l]->b[i+1]);
pD[l]->a[i+2]=f(pD[l]->a[i+2],pD[l]->a[i+3]);
}
if(!CBR[i+2])
for(int l=0;l<L;l++){pD[l]->b[i+2]=0;
if(pD[l]->a[i+2]<0.0)PM[l]+=pD[l]->a[i+2];
}
else{sort(i+1);
for(int l=0;l<L;l++)
if(l<L/2){pD[l]->b[i+2]=0;
if(pD[l]->a[i+2]<0.0)PM[l]+=pD[l]->a[i+2];
}
else{pD[l]->b[i+2]=1;
if(pD[l]->a[i+2]>0.0)PM[l]-=pD[l]->a[i+2];
}
}
for(int l=0;l<L;l++){
pD[l]->LLR[i+2]=pD[l]->b[i+2];
pD[l]->a[i+3]=g(temp[l],pD[l]->a[i+3],pD[l]->b[i+2]);
}
if(!CBR[i+3])
for(int l=0;l<L;l++){pD[l]->b[i+3]=0;
if(pD[l]->a[i+3]<0.0)PM[l]+=pD[l]->a[i+3];
}
else{sort(i+2);
for(int l=0;l<L;l++)
if(l<L/2){pD[l]->b[i+3]=0;
if(pD[l]->a[i+3]<0.0)PM[l]+=pD[l]->a[i+3];
}
else{pD[l]->b[i+3]=1;
if(pD[l]->a[i+3]>0.0)PM[l]-=pD[l]->a[i+3];
}
}
for(int l=0;l<L;l++){
pD[l]->LLR[i+3]=pD[l]->b[i+3];
pD[l]->b[i+2]=pD[l]->b[i+2]^pD[l]->b[i+3]?1:0;
pD[l]->b[i]=pD[l]->b[i]^pD[l]->b[i+2]?1:0;
pD[l]->b[i+1]=pD[l]->b[i+1]^pD[l]->b[i+3]?1:0;
}
}

在主函数当中,它们的具体调用如下:

int password_2[20]={2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0};
for(Vi=0;Vi<20;Vi++)
{
switch(password_2[Vi])
{
	case 0:first_4_plus(clue);clue+=4;break;
	case 1:for(int x=0;x<L;x++)function_back(clue0,2,x);clue0+=8;break;
	case 2:for(int x=0;x<L;x++)function_back(clue1,3,x);clue1+=16;break;
	case 3:for(int x=0;x<L;x++)function_back(clue2,4,x);clue2+=32;break;
	case 4:for(int x=0;x<L;x++)function_back(0,5,x);break;
}
}
//译码全过程结束

译码真的结束了吗!?不,别忘了我们的排序机制,是“排前不排后”的,而最后一个比特是信息比特,它是经过上一位信息比特的路径筛选后,进行了分裂但没有确定译码值的比特。因此,别忘了加上这一句:

sort(N-1);

最终,最佳路径就是第1条了。我们将最佳路径与原码序列进行对比,输出运行结果,程序结束。

/输出端
printf("原码序列:");
for(i=0;i<N/2;i++)printf("%d ",pE->code[i]);
printf("\n         ");
for(i=N/2;i<N;i++)printf("%d ",pE->code[i]);
printf("\n译码序列:");
for(i=0;i<N/2;i++)printf("%d ",pD[0]->LLR[i]);
printf("\n         ");
for(i=N/2;i<N;i++)printf("%d ",pD[0]->LLR[i]);
//判断正误 
int w=0; 
for(int i=0;i<N;i++)
{if(pE->code[i]^pD[0]->LLR[i]){w++,s++;}
}
if(w)printf("\n结果:wrong\n");
else printf("\n结果:right\n");
return 0;
}

Part 4.程序验收

最后,感谢耐心观看。做一件事情很容易,但把事情做好却很难。同样,想要了解极化码算法不难,但想要真正的去领悟其编程实操,甚至是自己动手去写代码,都需要经历无数个bug的洗礼与一定时间的“煎熬”。能看到这里,相信大家对SCL的译码过程的了解已有一定深度,编者水平有限,欢迎对文章中有错误的地方进行指正,也非常欢迎分享不同的思路。

  • 12
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值