★ 引子
最近两个星期一直在折腾,主机从 Windows 换到了 Linux,工作环境从实体机转移到虚拟机中。当然目的只有一个,那就是充分发挥 Linux 和虚拟机的优势来提高自己的工作效率。俗话说得好:磨刀不误砍柴工,花费一些时间来折腾升级还是有必要的,有空的话可以聊聊相关经验,如果你想急于知道的话,推荐编程随想的博客(博客在墙外,请自行搜索梯子)。
★ 计算原理
好了,废话不多说。上一篇文章讲了绝对值加法的实现,这次来讲讲绝对值减法该如何做。绝对值减法的做法仍然是笔算算法,从低位开始减,不够的向高位借位,直到所有的数位都处理完毕。为了方便日后的有符号数加减的实现,这里规定,算法计算 z = x - y,并且 x 的绝对值大于或等于 y,否则算法返回负数错误。
★ 实现
因为原理比较简单,所以我就先把代码贴出来,然后在介绍他的工作方式。
int bn_sub_abs(bignum *z, const bignum *x, const bignum *y) { int ret; bn_digit *px, *py, *pz; size_t i, min, max, olduse, t1, t2, c; max = x->used; min = y->used; if(bn_cmp_abs(x, y) < 0) return BN_NEGATIVE_VALUE_ERROR; olduse = z->used; z->used = max; BN_CHECK(bn_grow(z, z->used)); c = 0; px = x->dp; py = y->dp; pz = z->dp; for(i = 0; i < min; i++) { t1 = *px++; t2 = *py++; *pz++ = t1 - t2 - c; if(t1 != t2) c = (t1 < t2); } for(; i < max; i++) { t1 = *px++; *pz++ = t1 - c; if(c != 0 && t1 != 0) c = 0; } for(i = max; i < olduse; i++) *pz++ = 0; z->sign = 1; bn_clamp(z); clean: return ret; }
绝对值减法中,对输入进行排序并不重要,因为前面已经规定 |x| >= |y|,所以直接把 x->used 给 max, y->used 给 min;t1 和 t2 是临时变量,c 是借位。
在进行计算之前,先检查 x 和 y 的绝对值大小,如果不满足上面约定的条件,返回负数错误。
如果 x 和 y 的绝对值大小检查没问题,那么计算就可以正常进行,首先把借位的值设为 0,然后设定指针别名来提高内存访问效率。
第一个循环:对位相减。分别把 x 和 y 的每一个数位赋值给临时变量 t1 和 t2,计算 t1 - t2 - c 的值,然后存放到 z 的对应数位当中,如果 c = 0,表示低位没有向高位借位。相减完毕后,判断本次相减是否需要向高位借位,如果原来 x 中的某一数位的值小于 y 中对应数位的值,则比较的结果为 1,c = 1。注意所有的计算都是 mod 2^n。
第二个循环:退位和赋值。如果 max > min,表明 x 的数位要比 y 多,所以还需进行退位计算。如果 c = 0,则不会有退位了,直接把 x 的剩余数位赋值给 z 的对应数位即可。如果 c = 1,则还有来自低位的借位,在完成一次退位计算后,判断下一位是否需要退位,由于 c 的值只可能是 0 或 1,如果本次退位计算前,该数位的值大于 0,则以后的数位都不需要进行退位,故将 c 的值置为 0,否则保持退位值 1。完成退位计算后,将 x 剩余的数位给 z ,完成减法计算。
第三个循环:高位清零。如果减法计算完毕后,高位还有不为 0 的数位,必须清空,否则结果会出错。
所有循环结束后,把符号为设为 1,因为绝对值减法的最终结果仍然是个非负整数;最后压缩多余位完成计算。
★ 总结
减法操作相对于加法来讲要简单些,主要是不需要考虑单双精度的问题,只要你知道笔算算法以及理解计算机下二进制的补码运算,就不难实现。下一篇文章将根据前面建立的比较算法,绝对值加减算法构造有符号数的加减计算算法。
【回到本系列目录】
版权声明
原创博文,转载必须包含本声明,保持本文完整,并以超链接形式注明作者Starrybird和本文原始地址:http://www.cnblogs.com/starrybird/p/4399652.html