前言
读者需要先理解反码数的加法,它和正常数(即补码)的加法不一样。它会对错误式子的结果加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);
但由于这个循环实际上也只会执行两次,所以上面的算法也是可以的。