本篇文章讲述在学习CSAPP位运算LAB时的一些心得。

  1. 移位运算的小技巧

C/C++对于移位运算具有不同的策略,对于无符号数,左右移位为逻辑移位,也就是直接移位;对于有符号数,采用算术移位的方式,即左移仍为直接移位,右移时新产生的位用符号位补足。这种设计的目的是保证右移永远代表除以二,在不考虑溢出的情况下,左移永远代表乘以二;这里涉及到的一个规律是,二进制负数的左侧实际上有无数个1;二进制正数的左侧实际上有无数个0;

此外,当移位的长度大于等于数据的位数时,如果使用一个变量来代表移动的位数,则编译器会放弃这一移位操作;如果使用一个立即数作为移动的位数,则编译器会移动到底。

利用算术移位的特性,可以非常简单的实现一些功能,例如,检测一个数字是否可以被n位二进制表示成补码(这不仅仅要求长度要小于n,更要求表示之后的数字大小不能发生变化,如果原本是一个正数,表示之后的符号位必须仍然保持是0):

例一:检测一个整型数是否能够被n位二进制数表示

  1. int fitsBits(int x, int n) {  
  2.     int r, c;  
  3.     c = 33 + ~n; //c = 33 + ~n = 33 + (-n - 1) = 32 - n  
  4.     r = !(((x << c) >> c) ^ x);  
  5.     return r;  
  6. }  

    通过左移相应的位数再移回来看是否发生改变,就能很容易的知道这个数字能否被n位二进制补码表示。这里涉及到的一个规律是,二进制负数的左侧实际上有无数个1。简单考虑一个四位数字0011,显然其不能被2位数字表示但能被3位数字表示,当左移两位再移回来时,由于第二位的1左移后变成了符号位,结果应该是1111,而左移一位在移回来时仍然是0011。

    这种方案也可以应用在右移当中用于查看是否发生了舍弃末尾。

    例二:位运算实现除以2的n次幂:

  7. int divpwr2(int x, int n) {  
  8.     return ((x >> 31) & !!(x ^ ((x >> n) << n))) + (x >> n);  
  9. }  

    对于正数,位运算实现除以二的n次幂非常简单,只需要单纯的移位即可;但是对于负数情况则略有不同,例如对于-7 = 1001b,其右移一位的结果是-4 = 1100b;然而实际上结果应该是-3;这是由于移位时末尾被舍去了导致的。也就是说,通过位运算实现的除法永远是向下取整,然而除法的规则应该是对于正数向下取整而对于负数则要向上取整,因此这里采用(x ^ ((x >> n) << n))的方式检测是否有某些数字在移位的过程中被忽略,也就是是否不能整除,如果不能整除则进行加一操作;这里还使用了两次!!操作,是利用了逻辑非的缩位特性实现的。

    此外,当我们无法得到0xFFFFFFFF时,也可以利用移位运算生成一个全1数字,((1 << 31) >> 31);

    2.    !运算的缩位特性

    !x = 1当且仅当x=0;否则x = 1;因此可以使用!!x直接实现缩位或的效果。

  10. int bang(int x) {  
  11.     return (~((x >> 31) | (((~x) + 1) >> 31))) & 1;  
  12. }  

    bang函数通过不含!的运算实现了!操作,从中可以更好的体现出它的缩位特性。

    3.    分组运算的技巧(二分——组合)

    对于每个数据类型进行的操作是和数据的长度相关的。然而,当我们了解了很多关于数据类型的知识后,我们可以更好的利用运算的特性来加速,例如下面这一问题——不使用循环和条件语句统计一个二进制数中1的个数:

    最直观的想法其实就是,左移一位后检查末尾是否为1,如果是的话总数就加一,重复31次;然而由于题目中限制不能用循环,如果只是简单的拆成很多条语句显然是很滑稽的。这时候可以考虑分组运算,例如下面的算法以四个为一组,分别统计出来个数,再进行加和。

  13. int bitCount(int x) {  
  14.     int a = 0x11 | (0x11 << 8);  
  15.     int b = a | (a << 16);  
  16.     int sum = x & b;  
  17.     sum = sum + ((x >> 1) & b);  
  18.     sum = sum + ((x >> 2) & b);  
  19.     sum = sum + ((x >> 3) & b);  
  20.     sum = sum + (sum >> 16);  
  21.     a = 0xF | (0xF << 8);  
  22.     sum = (sum & a) + ((sum >> 4) & a);  
  23.     return ((sum + (sum >> 8)) & 0x3F);  
  24. }  

    在加和时实际上也采用了分组的技巧,这种类似于分治的思想成功地把一个O(n)的问题简化为了O(logn)的问题。

    此外还有一例,用单纯的逻辑运算和+实现logn:

    实际上我们是要找到最高位的1所在的位置即可,所以这是一个搜索问题,对于位运算,除了最简单的线性搜索之外,比较容易实现的方法就是二分搜索。把二分搜索的方法应用到位运算中,并且注意这里边始终是优先取高位(因为qr是使用高位得到的),经过5次一定能找到结果。

  25. int ilog2(int x) {  
  26.     int exp = 0;  
  27.     int cx = x;  
  28.     int rx = x >> 16;  
  29.     int qr = (!!rx);  
  30.     qr = (qr << 31) >> 31;  
  31.     rx = (qr & rx) | ((~qr) & cx);  
  32.     exp = exp + ((qr & 16) | (~qr & 0));  
  33.  
  34.     cx = rx;  
  35.     rx = rx >> 8;  
  36.     qr = (!!rx);  
  37.     qr = (qr << 31) >> 31;  
  38.     rx = (qr & rx) | (~qr & cx);  
  39.     exp = exp + ((qr & 8) | (~qr & 0));  
  40.  
  41.     cx = rx;  
  42.     rx = rx >> 4;  
  43.     qr = (!!rx);  
  44.     qr = (qr << 31) >> 31;  
  45.     rx = (qr & rx) | (~qr & cx);  
  46.     exp = exp + ((qr & 4) | (~qr & 0));  
  47.  
  48.     cx = rx;  
  49.     rx = rx >> 2;  
  50.     qr = (!!rx);  
  51.     qr = (qr << 31) >> 31;  
  52.     rx = (qr & rx) | (~qr & cx);  
  53.     exp = exp + ((qr & 2) | (~qr & 0));  
  54.  
  55.     cx = rx;  
  56.     rx = rx >> 1;  
  57.     qr = (!!rx);  
  58.     qr = (qr << 31) >> 31;  
  59.     rx = (qr & rx >> 1) | (~qr & cx);  
  60.     exp = exp + ((qr & 1) | (~qr & 0));  
  61.     return exp;  
  62. }  

关于C/C++中的位运算技巧的更多相关文章

  1. ACM中的位运算技巧

    听说位运算挺好玩的,那这节总结一下ACM中可能用到的位运算技巧. XOR运算极为重要!!(过[LC136](只出现一次的数字 - 力扣(LeetCode)):数组中每个数字都出现两次,只有一个出现一次 ...

  2. ACM位运算技巧

    ACM位运算技巧 位运算应用口位运算应用口诀位运算应用口诀 清零取反要用与,某位置一可用或 若要取反和交换,轻轻松松用异或 移位运算 要点 1 它们都是双目运算符,两个运算分量都是整形,结果也是整形. ...

  3. PHP中的位运算与位移运算(其它语言通用)

    /* PHP中的位运算与位移运算 ======================= 二进制Binary:0,1 逢二进1,易于电子信号的传输 原码.反码.补码 二进制最高位是符号位:0为正数,1为负数( ...

  4. C语言中的位运算和逻辑运算

    这篇文章来自:http://blog.csdn.net/qp120291570/article/details/8708286 位运算 C语言中的位运算包括与(&),或(|),亦或(^),非( ...

  5. Google Earth Engine 中的位运算

    Google Earth Engine中的位运算 按位运算是编程中一个难点,同时也是在我们后续处理影像数据,尤其要使用影像自带的波段比如QA波段经常会用到的一个东西.通过按位运算我们可以筛选出我们想要 ...

  6. C语言中的位运算的技巧

    一.位运算实例 1.用一个表达式,判断一个数X是否是2的N次方(2,4,8,16.....),不可用循环语句. X:2,4,8,16转化成二进制是10,100,1000,10000.如果减1则变成01 ...

  7. C++中的位运算总结

    1)位运算 位运算是指对转换成二进制的数字进行每一位上的0.1的运算,运算涉及到五种运算:与(&),或(|),异或(^),左移(<<),右移(>>). 如下表所示:   ...

  8. 你必须知道的基本位运算技巧(状压DP、搜索优化都会用到)

    一. 位操作基础 基本的位操作符有与.或.异或.取反.左移.右移这6种,它们的运算规则如下所示: 符号 描述 运算规则 & 与 两个位都为1时,结果才为1 | 或 两个位都为0时,结果才为0 ...

  9. js中的位运算

    按位运算符是把操作数看作一系列单独的位,而不是一个数字值.所以在这之前,不得不提到什么是"位": 数值或字符在内存内都是被存储为0和 1的序列,每个0和1被称之为1个位,比如说10 ...

随机推荐

  1. delphi execCommand

    WebBrowser1.Document as IHTMLDocument2 关键点 function execCommand(const cmdID: WideString; showUI: Wor ...

  2. iOS开发——网络编程Swift篇&(五)同步Post方式

    同步Post方式 // MARK: - 同步Post方式 func synchronousPost() { //创建NSURL对象 var url:NSURL! = NSURL(string: &qu ...

  3. 第2章 数字之魅——斐波那契(Fibonacci)数列

    斐波那契(Fibonacci)数列 问题描述 递归算法: package chapter2shuzizhimei.fibonacci; /** * Fibonacci数列递归求解 * @author ...

  4. 网络IPC:套接字之套接字描述符

    套接字是通信端点的抽象.与应用程序要使用文件描述符访问文件一样,访问套接字也需要套接字描述符.套接字描述符在UNIX系统是用文件描述符实现的.事实上,许多处理文件描述符的函数(如read和write) ...

  5. debian防火墙firestarter

    Firestarter是一个非常好用的防火墙图形化配置工具,作者和开发者是芬兰人. 首先肯定的说Firestarter防火墙是一款非常优秀的基于GUI图形用户界面下的,完全免费的自由软件,它为中小型L ...

  6. dl-ssl.google.com

    转载:http://jingyan.baidu.com/article/64d05a02752300de55f73b99.html 搭建Android就会用到Android SDK,而安装SDK有个恶 ...

  7. Debian安装Apache2+MySQL5+PHP5(zz)

    转载:http://hi.baidu.com/lostdays/item/1d5e7e4833b4d20fc116134b 终于在Debian用apt-get安装好LAMP了,之前在CentOS使用编 ...

  8. github使用成长记

    学校里一直都有自己写一些网页,一方面为了学习熟练技能,另一方面也是兴趣所在.但是独乐乐不如众乐乐,一直向往有那么一个平台能把自己做得东西分享给广大网友,并且想借着分享的契机和各位程序猿交流学习心得(这 ...

  9. web2.0、互联网+、IT时代与DT时代、工业4.0 引发的思考

    最近忙着找实习,来学校一个星期还没到,就感觉已经经历了几个春秋. 第一个实习面试是个杭州互联网小公司,面WEB前端开发实习,怪我一个暑假两个月一点书都没碰,偏偏赶上G20到9/9才开学,没啥准备就一头 ...

  10. 分享一个java线程专栏

    专栏 : java线程基础 转载自 http://blog.csdn.net/column/details/yinwenjiethread.html 专栏内容: 1.线程基础:线程(1)--操作系统和 ...