校验和算法原理

前言

读者需要先理解反码数的加法,它和正常数(即补码)的加法不一样。它会对错误式子的结果加1,这是一种修正操作。

算法基本思想

在这里插入图片描述
上面的过程可以简单理解为:
sum = ~X + ~Y + ~0000
check = ~X + ~Y + ~sum
check = ?
当然,最终check会是1111的。下面我们来推导这个结论:

  • 首先注意到,这些式子里的数都是反码数,所以加法也得是反码的加法
  • sum = ~X + ~Y + ~0000代入到check = ~X + ~Y + ~sum
  • 那么得到check = ~X + ~Y + ~(~X + ~Y + ~0000),再得到check = ~X + ~Y + X + Y + 0000)
  • 因为~X + X = 1111(这里也是反码数相加,但由于没有产生溢出,所以和正常的二进制加法一样),但其实X和Y的数量是不确定的,所以check = 1111 + ... + 1111 + 0000
  • 那么为什么1111 + ... + 1111 + 0000 = 1111呢?这里先给出原因,因为n个作为反码数的1111相加后,还是为1111。所以最后剩个1111 + 0000按照反码的加法,最后还是等于1111

但为什么n个作为反码数的1111相加后,还是为1111呢?

  • 比如两个1111相加,得到11110,再将高4bit叠加到低4bit上去,则还是为1111
  • 再比如三个1111相加,得到101101,再将高4bit叠加到低4bit上去,则还是为1111
  • 从反码的表来说,n个(-0)相加,确实应该为-0,不过这样不算推导。
    在这里插入图片描述

上面的示例为:每个数先取反、再求和、再高位叠加到低位,但其实叠加操作是必然跟在求和操作后面的,因为这就是反码数加法的规则。

之前我们求的是~X + ~Y + ~0000,其实也可以转换为~(X + Y + 0000)~(X + Y)(注意这里的加法也是反码数加法)(可以用上面发送端的数据来验证一下这个运算规律),这里就是先求和(后跟叠加操作),再取反。也就是下一章的算法。

算法基本实现

unsigned short checksum(unsigned short *buf,int nword)
{
    
    
    unsigned long sum;
  
    for(sum=0;nword>0;nword--)
        sum += *buf++;
    sum = (sum>>16) + (sum&0xffff);
    sum += (sum>>16);
  
    return ~sum;
}

从参数可以看出,我们把要校验的数据分割成各个16bit的数据,具体的说,我们把这些个数据都看成了反码数。

我们知道short为2字节,long为4字节,那么sum += *buf++的过程中,完全有可能出现1 xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx这样的不止32bit的数据,但这里最高位的1肯定会截断。这样,到了sum = (sum>>16) + (sum&0xffff)这一步,第33bit的1就根本没有参与运算了,但这样不会产生坏影响。
因为这样的1代表是short的模,执行sum>>16后,这个1就是1 00000000 00000000,把这样的1叠加到低16bit上去,相当于让低16bit变成一个对于2^16的同余数,那就相当于没加。

再解释下sum = (sum>>16) + (sum&0xffff),这就是我们所说的 反码数加法的叠加操作。

而第二句sum += (sum>>16),其实应该写成和上一句sum = (sum>>16) + (sum&0xffff)一样的,而之所以需要两句相同的sum = (sum>>16) + (sum&0xffff),是因为有可能第一句执行后,又发生了高于16bit的溢出(循环求和的时候,就会发生高于16bit的溢出),第二句就是来把这个溢出又叠加到低16bit上去。
我们以4bit数值为例子,假设循环求和后,得到的二进制刚好为1111 1111

  • 先执行第一句sum = (sum>>16) + (sum&0xffff),得到11110。发现确实,执行后还是有溢出存在。
  • 再执行第一句sum = (sum>>16) + (sum&0xffff),才得到1111。此时,溢出没有了,得到正确结果。
  • 开始时为1111 1111的负零,现在为1111的负零。证毕。

而之所以能写成sum += (sum>>16),是因为函数的返回值为short,最终都会被截断的。

这两句其实应该写成循环:

while(sum>>16)
    sum = (sum>>16) + (sum&0xffff);

但由于这个循环实际上也只会执行两次,所以上面的算法也是可以的。

猜你喜欢

转载自blog.csdn.net/anlian523/article/details/121481590