有趣的位运算

20 篇文章 0 订阅


位运算

位运算,即是 操作二进制的bit(位),来进行运算。

其分类有:按位与(&),按位或(|),按位异或(^),按位取反(~),按位左移(<<),按位右移(>>)

java中还有 “无符号右移” (>>>);C语言中是没有的

参与位运算的,应该是一个整数,或者字符型(其实质内部也是对应一个整数)


按位与(&)

按位与,是双目运算符。其功能是参与运算的两数各对应的二进位相与。只要对应的二个二进位都为1时,结果位就为1。参与运算的两个数均以补码出现。

。。。 上面说的太长了, 我都记成: 同位为1值为1,反之为0

  • 获取某一段的8位

    比如int类型,在当前系统中占用4个字节,1个字节等于8bit(位),即占用32位

    从右数,想获取第一段的8位: a&0xff; 获取第二段的8位:(a&0xff00)>>8; 第三段则右移16位,第四段则右移24位。 因右移的优先级高,所以左边要用小括号,也可以:a>>8&0xff; a>>16&0xff; a>>24&0xff;

    这个应用,在安卓中的颜色类Color.java源码中能找到,用4个字节分别存储一个颜色值的:ARGB

  • &1,判断奇偶数

    奇数的末位,总是为1。所以用&1来判断奇偶。 a & 1 == 1 ? "奇数\n": "偶数\n"


按位或(|)

按位或是双目运算符。同位有1得1。

  • 将某一段的8位置为1

    右起,第一段:a|0xff; 第二段:a | 0xff00

  • 两个二的n次幂的整数进行 | 操作

    二的n次幂整数:2, 4, 8, 16…,对应的二进制位:10, 100, 1000, 10000…

    进行 | 操作后,结果就相当于: 两个整数相加的和。

    若有一个操作数为负,结果就不一定满足,相加的性质。
    如 -2 | 16 ,结果不为 14


按位异或(^)

按位异或是双目运算符。同位相异得1,相同得0。

  • 应用1:交换两个变量的值。

    int a = -9;
    int b = 10;
    a = a ^ b;
    b = a ^ b;
    a = a ^ b;
    printf("交换位置后,a=%d  b=%d \n", a, b);
    
  • 应用2:由于异或自身等于0,判断两个变量是否相等。

int a = 10; 
int isEqual(int x) {
	return (a ^ x) == 0;
}

按位取反(~)

按位取反,是单目运算符。每位1变0,0变1。 ~a;


按位左移(<<)

左移运算:左移多少位,右边就补上多少个0。也就是左移n位,就乘以2的n次冥。

a << 2


按位右移(>>)

右移,是要分符号位的。

在计算机中,不管正数还是负数,都是以补码形式存储的。正数的原码和补码是相同的,符号位为0。负数的补码,等于原码取反,再加1,符号位为1。

正数的右移:右移多少位,左边补多少个0。跟左移类似,即正数右移n位,就除2的n次冥。

负数的右移:保持符号位1不变;右移n位后,再取反码加1

a >> 2


无符号右移(>>>)

(C语言中没有这,Java中有)

正数的无符号右移:和右移是一样的。

负数的无符号右移:右移n位后,左边补n个0,符号位为0

-40 >>> 3;

40的原码:		0 0000 0000 0000 0000 0000 0000 0010 1000
40的反码:		1 1111 1111 1111 1111 1111 1111 1101 0111
-40的补码:		1 1111 1111 1111 1111 1111 1111 1101 1000
右移三位:   	    1 0001 1111 1111 1111 1111 1111 1111 1011
变符号位为正:     0 0001 1111 1111 1111 1111 1111 1111 1011

Java#HashMap 中的应用

HashMap有段代码

static final int tableSizeFor(int cap) {
    int n = cap - 1;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

最终,计算出对第一个大于或等于 cap 值的 二进制数。
分析:

  1. n=cap-1; 这里为什么要减1, 最后说;
  2. 继续看,如下:
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;

结果,就是对于 n,从第一位非0的二进制位开始,后面都赋上了1。
为此写了段测试代码:

public static void main(String[] args) {
   	int n = (int)Math.pow(2,15);
   	n += 2222;
   	get(n);
   	for (int i = 0; i < 5; i++) {
   		n |= n >>> (1 << i);
   		get(n);
   	}
}

static void get(int x) {
   print("----x="+x+"----");
   String bs = Integer.toBinaryString(x);
   print(bs);
}

static void print(Object o) {
   System.out.println(o.toString());
}

测试发现,
对于 n 的二进制中,从首个非0开始值开始,
循环第1次,即 n|= n>>>1; 可使二进制数中前2位为1;
循环第2次,即 n|= n>>>2; 可使二进制数中前4位为1;
循环第3次,即 n|= n>>>4; 可使二进制数中前8位为1;
循环第4次,即 n|= n>>>8; 可使二进制数中前16位为1;
循环第5次, 即 n|= n>>>16; 可使二进制数中前32位为1;
如,n=2^8;二进制:1后面8个0。 经过三次循环后,前8位为1,后1位为0。第四次循环后,“可使二进制数中前16位为1”,实际 n 只有9位,所以此时 就是9个1,即2^9-1

  1. 最后看 return 语句
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
  • 开始时 int n = cap - 1; 若这时 n 为负数,那么 return 1。
  • 若 n 为正数,经计算后,大于等于 MAXIMUM_CAPACITY,就 return MAXIMUM_CAPACITY。
  • 若 n 为正数,经计算后,小于MAXIMUM_CAPACITY, 就 return n+1;
    开始时int n = cap - 1,由上例知,若初始时 cap=2^8,n=2^8-1,最终 n+1,就是 2^8;
    若初始时 cap 在 [2^8 + 1, 2^9]内,最终 n+1,就是 2^9。

简单说
要如何对一个整数,求大于它的第一个二进制数:
对于 int 型这样的32位二进制类型来说,连续 n |= n >>> (1,2,4,8,16) 就可。
那若是64位,连续 n |= n >>> (1,2,4,8,16,32) 就可。


位移运算和加减法的优先级

在二分查找算法中,发现 int mid = (r - l) >> 1 + l; 有问题,结果和 int mid = (r - l) / 2 + l; 不一致。
发现是 位移运算的优先级 比加减法 低。所以混合运算时,要加上小括号。
int mid = ((r - l) >> 1) + l; 这样才能保证结果正确


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值