当前位置:网站首页 > 技术博客 > 正文

移位运算求值



       上一篇博文讲到了大整数位的相关操作,这次的内容依然和位的操作相关:移位操作。需要说明的是,这里所讲的大整数移位操作都是算术移位(左移的话精度会增加,而不是把移出的位丢掉)。

         ★ 两种移位的区别

         在移位操作当中存在两种不同的操作方式:逻辑移位和算术移位。在逻辑移位当中,移出的位丢失,移入的位取 0。在算术移位当中,移出的位丢失,左移入的位取 0,右移入的位取符号位,即最高位代表的数据符号保持不变。举个例子:有如下两个类型的变量(32位系统下)被定义:

         unsigned int u = 0x;   (8 后面有 7 个 0)

         int s = 0x;

         用二进制表示都是:1000 0000 0000 0000 0000 0000 0000 0000

         u 跟 s 的区别在于 s 的最高位是符号位(0 代表正数,1 代表负数),所以如果用以下语句打印这两个变量,结果会是:, -。

       printf(" u = 薥 u);
   printf(" s = %d ", s);

         第一个结果好说,无符号数就是 2^31,但是为什么第二个是 - 2^31 呢?按理说应该是 - 0 才对呀。原因是这些数都是用补码表示的。

         先来看看 - 2^31 - 1 (-)这个数用补码怎么表示。

         原码:1111 1111 1111 1111 1111 1111 1111 1111,注意最高位是符号位。要求补码,先计算反码,反码的计算是符号位不变,其余位取反。

         反码:1000 0000 0000 0000 0000 0000 0000 0000,得到反码之后,将反码加 1 就得到补码了。

         补码:1000 0000 0000 0000 0000 0000 0000 0001。所以 - 2^31 - 1 用补码表示就是 0x。

         要表示 - 2^31,只需要将 - 2^31 - 1 的补码减 1 即可,所以是 0x,这就得到 32 位有符号整形下可表示的最小负整数。

         下面看看进行移位后的结果是怎样的。

         对于左移操作,不管是逻辑左移还是算术左移,左边移出的位都丢失,右边移入的位都取 0,所以如果执行如下两条语句:u <<= 1; s <<= 1; 其结果都是 0。

         对于右移操作,逻辑右移左边补 0,算术右移左边补符号位。所以如果执行如下两条语句:u >>= 1; s >>= 1; 其结果:, -。结果的补码用二进制表示就是:

          u:0100 0000 0000 0000 0000 0000 0000 0000

          s:1100 0000 0000 0000 0000 0000 0000 0000

          上面的右移 1 位的操作,相当于在计算: / 2 = ; - / 2 = -

          对于移位操作,左移 n 位相当于乘以 2^n,右移 n 位相当于除以 2^n。

          前面废话那么多就是想说明逻辑移位和算术移位是有区别嘀。对于大整数,算术移位操作不需要用什么补码,因为 dp 指向的内存保存的是大整数的绝对值,符号是用 sign 来跟踪的。另外和C里面不同的是,如果左移后位数不够,大整数的精度会增加,而不像C那样丢弃移出来的位。

         ★ 左移和右移 b 个数位

          简单来说就是乘以或除以 2 ^ (biL *b),移位操作是以整个数位为单元进行的。

          左移 b 个数位:

          左移 b 个数位后,数位增加 b 位,所以 used 值要增加 b。使用 bn_grow 函数增加 bignum 的精度。然后用 top 指针指向 bignum 左移后的最高位,bottom 指针指向 bignum 当前的最高位,然后循环 used - b 次把每一个数位往左移动 b 个数位。操作结束后,让 top 指针指向最低位,循环 b 次把最低的 b 个数位置 0,完成移位操作。

            右移 b 个数位:

          和左移不同的是,右移精度不会增加,但如果 used 的值小于等于 b,则 bignum 的最高位都会从右边被移出去,结果为 0。如果 used > b,让 bottom指向 bignum 的最低数位,top 指针指向第 b + 1 位,然后循环 used - b 次将每个数位往右挪动 b 个数位,最后将左边剩余的 b 位取 0,完成右移操作。

         ★ 左移和右移 1 个比特位

         很好理解,就是乘以 2 或者除以 2。下面的算法完成后会把 x 的值赋值给 y。

         左移 1 位:

         首先算法默认将 y 的精度增加到 x->used + 1 个数位,之所以要加 1,因为有可能刚好把最高位移到下一个数位中去。olduse 记录当前 y 的数位,然后将 y 的数位增加到 x 的数位,如果最终还有进位,y->used 还要加一。shift 变量表明每个数位要往右移动的比特位数,例如在 32 位系统下,shift = 31,64 位系统下 shift = 63。变量 r 存储上一个数位最左边的比特位,变量 rr 存储当前数位最左边的比特位。在循环当中,先将当前数位右移 shift 位来获得最左边的比特位,然后再将这个数位左移 1 位并且与右边数位的最左边比特位做 OR 操作,这样当前数位中的每一比特位就往左边移动了 1 位,并且右边数位的最左边比特位也移动到当前数位的最右位置。循环结束后,如果 r 不为 0,表明原来 bignum 最左边数位的最左边比特位为 1,在左移中被移到了新的数位中,所以 used 加一,新的数位值为 1。最后将 y 的高位设置为 0,把 x 的符号给 y 完成所有操作。

            右移 1 位:

          右移 1 位精度不会增加,不过仍然要调用 bn_grow 函数增加精度,因为一开始 y 的精度可能不够。右移 1 位的操作跟左移 1 位的操作比较类似,只是方向相反。在第一个循环当中,先获取当前数位的最右比特位存放到变量 rr 中,然后当前数位右移 1 位,接着将左边数位的最右比特位左移 shift 位后与当前数位做 OR 操作,最后将 rr 的值存放到变量 r 中,这样当前位的所有比特都往右移动了 1 位,并且左边数位的最右边比特也移动到当前数位的最左边。完成循环后将高位设置为 0,然后设置符号位,最后压缩多余位完成操作。

         ★ 左移和右移 n 个比特位

          如果说前面的移位操作都带有特殊性,那么下面这两个操作就是将其一般化而已。左移或右移 n 位相当于乘以或除以 2^n。

          左移 n 位:

         首先算法检查并增加 y 的精度,接着如果左移的位数 count 大于或等于一个数位的比特数,调用 bn_lshd 函数将 x 左移 count / biL 个数位,然后检查是否有剩余的比特位:d = count & (biL - 1),如果 d 不为 0,表明还有剩余位,按照左移 1 位的原理提取剩余位向左移动完成左移 n 位的操作。

           右移 n 位:

        和左移 n 位一样,先做整个数位的右移,然后再按照右移 1 位的原理往右移动剩余的比特位。要注意的是,右移之后,需要压缩多余位来更新 used 的值。

         ★ 时间复杂度分析

         本文所讲到的移位操作,都是在一重循环内完成的,算法的复杂度与 bignum 的精度和大小有关,时间复杂度大致为 O(n)。

         ★ 总结

         移位操作对于某些特殊的计算是十分有效的,比如乘以或除以 2,因此碰到这类计算,最好使用移位操作而不是乘法或除法。讲完了大整数的位操作,接下来就要开始讲讲四则运算了。下一篇文章将介绍绝对值加法。

   【回到本系列目录】

版权声明
原创博文,转载必须包含本声明,保持本文完整,并以超链接形式注明作者Starrybird和本文原始地址:http://www.cnblogs.com/starrybird/p/4357222.html

版权声明


相关文章:

  • python处理语音信号2025-07-15 08:01:04
  • 网页性能测试2025-07-15 08:01:04
  • Debian8下载2025-07-15 08:01:04
  • ifconfig-lo2025-07-15 08:01:04
  • 敏捷宣言包括2025-07-15 08:01:04
  • mipi接口三种模式的区别2025-07-15 08:01:04
  • fstream ifstream2025-07-15 08:01:04
  • 自动化测试视频教程2025-07-15 08:01:04
  • orm框架的基本原理2025-07-15 08:01:04
  • linux中ps是什么意思2025-07-15 08:01:04