Java 位运算
1.表示方法:
在Java语言中,二进制数使用补码表示,最高位为符号位,正数的符号位为0,负数为1。补码的表示需要满足如下要求。
(1)正数的最高位为0,其余各位代表数值本身(二进制数)。
(2)对于负数,通过对该数绝对值的补码按位取反,再对整个数加1。
2.位运算符
位运算表达式由操作数和位运算符组成,实现对整数类型的二进制数进行位运算。位运算符可以分为逻辑运算符(包括~、&、|和^)及移位运算符(包括>>、<< >>>)。
1)左移位运算符(<< p>
2)“有符号”右移位运算符(>>)则将运算符左边的运算对象向右移动运算符右侧指定的位数。“有符号”右移位运算符使用了“符号扩展”:若值为正,则在高位插入0;若值为负,则在高位插入1。
3)Java也添加了一种“无符号”右移位运算符(>>>),它使用了“零扩展”:无论正负,都在高位插入0。这一运算符是C或C++没有的。
4)若对char,byte或者short进行移位处理,那么在移位进行之前,它们会自动转换成一个int。只有右侧的5个低位才会用到。这样可防止我们在一个int数里移动不切实际的位数。若对一个long值进行处理,最后得到的结果也是long。此时只会用到右侧的6个低位,防止移动超过long值里现成的位数。但在进行“无符号”右移位时,也可能遇到一个问题。若对byte或short值进行右移位运算,得到的可能不是正确的结果(Java 1.0和Java 1.1特别突出)。它们会自动转换成int类型,并进行右移位。但“零扩展”不会发生,所以在那些情况下会得到-1的结果。
在进行位运算时,需要注意以下几点。
(1)>>>和>>的区别是:在执行运算时,>>>运算符的操作数高位补0,而>>运算符的操作数高位移入原来高位的值。
(2)右移一位相当于除以2,左移一位(在不溢出的情况下)相当于乘以2;移位运算速度高于乘除运算。
(3)若进行位逻辑运算的两个操作数的数据长度不相同,则返回值应该是数据长度较长的数据类型。
(4)按位异或可以不使用临时变量完成两个值的交换,也可以使某个整型数的特定位的值翻转。
(5)按位与运算可以用来屏蔽特定的位,也可以用来取某个数型数中某些特定的位。
(6)按位或运算可以用来对某个整型数的特定位的值置l。
3.位运算符的优先级
~的优先级最高,其次是<< >>和>>>,再次是&,然后是^,优先级最低的是|。
按位异或运算符^
参与运算的两个值,如果两个相应位相同,则结果为0,否则为1。即:0^0=0, 1^0=1, 0^1=1, 1^1=0
例如:10100001^00010001=10110000
0^0=0,0^1=1 0异或任何数=任何数
1^0=1,1^1=0 1异或任何数-任何数取反
任何数异或自己=把自己置0
(1)按位异或可以用来使某些特定的位翻转,如对数10100001的第2位和第3位翻转,可以将数与00000110进行按位异或运算。10100001^00000110=10100111 //1010 0001 ^ 0x06 = 1010 0001 ^ 6
(2)通过按位异或运算,可以实现两个值的交换,而不必使用临时变量。
例如交换两个整数a,b的值,可通过下列语句实现:
a=10100001,b=00000110
a=a^b; //a=10100111
b=b^a; //b=10100001
a=a^b; //a=00000110
(3)异或运算符的特点是:数a两次异或同一个数b(a=a^b^b)仍然为原值a.
Java 中除了二进制的表示方法:
由于数据在计算机中的表示,最终以二进制的形式存在,所以有时候使用二进制,可以更直观地解决问题。
但,二进制数太长了。比如int 类型占用4个字节,32位。比如100,用int类型的二进制数表达将是:
0000 0000 0000 0000 0110 0100
面对这么长的数进行思考或操作,没有人会喜欢。因此,C,C++,以及java中 没有提供在代码直接写二进制数的方法。
八进制数的表达方法
如何表达一个八进制数呢?如果这个数是 876,我们可以断定它不是八进制数,因为八进制数中不可能出7以上的阿拉伯数字。但如果这个数是123、是567,或12345670,那么它是八进制数还是10进制数,都有可能。
所以规定,一个数如果要指明它采用八进制,必须在它前面加上一个0,如:123是十进制,但0123则表示采用八进制。这就是八进制数的表达方法。 现在,对于同样一个数,比如是100,我们在代码中可以用平常的10进制表达,例如在变量初始化时:
int a = 100;
我们也可以这样写:
int a = 0144; //0144是八进制的100;一个10进制数如何转成8进制。
千万记住,用八进制表达时,你不能少了最前的那个0。否则计算机会通通当成10进制。不过,有一个地方使用八进制数时,却不能使用加0,那就是我们前面学的用于表达字符的“转义符”表达法。
十六进制数的表达方法
如果不使用特殊的书写形式,16进制数也会和10进制相混。随便一个数:9876,就看不出它是16进制或10进制。
16进制数必须以 0x开头。比如 0x1表示一个16进制数。而1则表示一个十进制。另外如:0xff,0xFF,0X102A,等等。其中的x也也不区分大小写。(注意:0x中的0是数字0,而不是字母O)
以下是一些用法示例:
int a = 0x100F;
int b = 0x70 + a;
最后一点很重要,10进制数有正负之分,比如12表示正12,而-12表示负 12,;但8进制和16进制只能用来表达无符号的正整数,如果你在代码中里:-078,或者写:-0xF2,编译器并不把它当成一个负数。
位操作实现加减乘除四则运算;
常见的位操作实现
1. 常用的一个等式:-n = ~(n - 1) = ~n + 1
2. 获取整数的二进制的最右边的1:n & (-n) 或 n & ~(n - 1)。例如 n = 010100, -n = 101100,那么n & (-n) = 000100
3. 去除整数的二进制的最右边的1:n & (n - 1)。例如 n = 010100,n-1 = 010011,n&(n-1) = 010000
加法操作
实现加法操作使用”异或“和”与“来实现。对应位的异或操作可以得到该位的值,对应位的与操作可以产生该位对高位的进位值。
减法操作
减法操作可以用加法操作来实现。例如 a - b = a + (-b) = a + (~b + 1)
乘法操作
二进制的乘法与十进制原理类似:都是将乘数的每一位和被乘数的每一位依次相乘,然后将相乘的结果相加即可。
例如:
可以看出,乘法过程:如果乘数b的第i(i >= 1;i = 1是乘数最右侧的那一位)位为1,那么该位与被乘数a相乘的结果S[i]就是(a << i);然后将这些所有的结果S[i]相加即为最后结果。
除法操作
例如:求101011除以11:
在上图的除法过程中:
(1)第一次除法先找到除数应该左移的位数,使得除数是不大于除数的数,上图中将除数左移了三位(11<< 3 = 11000),变为11000;然后本次除法结果为(1 << 3);被除数变为了原来的被除数101011 减去当前的除数11000:10011,该被除数就是下一次除法的被除数。
(2)第二次除法的被除数为10011,此次的除数为上一次除法右移一位,即(原始除数11左移两位:11<<2 = 1100);本次除法结果为(1<<2);被除数变为10011 - 1100 = 111,这作为下一次除法的被除数。
(3)第三次除法的被除数变为111,除数是上一次除法右移一位,也就是初始除数11左移一位(11<< 1 = 110);本次除法结果为(1<<1);被除数为111 - 110 = 1;
(4)乘法结束。商为(1 << 3 + 1 << 2 + 1 << 1) = 1000 + 100 + 10 = 1110 = 14。
源码
import java.util.HashMap;
import java.util.Map;
/*
* java位运算
* @author: chx
* @Time: 2016年3月7日下午10:17:49
*/
public class BitTest {
public static void main(String[] args) {
int pTestNum = 5;
int nTestNum = -5;
int size = 2;
String operLS = "<<";
String operRS = ">>";
String operNS = ">>>";
int firNum = 8; //1000
int secNum = 9; //1001
String andSize = "&"; //1000 8
String orSize = "|"; //1001 9
String dOrSize = "^"; //0001 1
String negSize = "~"; //11111111111111111111111111110111 -9
System.out.println("左移操作:");
//左移 低位补,正补0 负补1 数值的左移动: result = testNum*2^(size);
testMove(pTestNum, size,operLS);// 0000 0000 0000 0000 0000 0000 0000 0101 然后左移2位后,低位补0:
// 0000 0000 0000 0000 0000 0000 0001 0100 换算成10进制为20
testMove(nTestNum, size,operLS);// 1111 1111 1111 1111 1111 1111 1111 1011 -5
// 1111 1111 1111 1111 1111 1111 1110 1100 -20
System.out.println("右移操作:");
//右移 高位补,正补0 负补1
testMove(pTestNum, size,operRS);// 0000 0000 0000 0000 0000 0000 0000 0101 5 然后右移2位,高位补0:
// 0000 0000 0000 0000 0000 0000 0000 0001 1
testMove(nTestNum, size,operRS);// 1111 1111 1111 1111 1111 1111 1111 1011 -5
// 1111 1111 1111 1111 1111 1111 1111 1110 -2
System.out.println("无符号右移操作:");
//右移 高位补0
testMove(pTestNum, size,operNS);// 0000 0000 0000 0000 0000 0000 0000 0101 5 然后右移2位,高位补0:
// 0000 0000 0000 0000 0000 0000 0000 0001 1
testMove(nTestNum, size,operNS);// 1111 1111 1111 1111 1111 1111 1111 1011 -5
// 0011 1111 1111 1111 1111 1111 1111 1110 1073741822
System.out.println("按位与:");
// 位与:第一个操作数的的第n位于第二个操作数的第n位如果都是1,那么结果的第n为也为1,否则为0
testSize(firNum, secNum, andSize);
System.out.println("按位或:");
// 第一个操作数的的第n位于第二个操作数的第n位 只要有一个是1,那么结果的第n为也为1,否则为0
testSize(firNum, secNum, orSize);
System.out.println("按位异或:");
// 第一个操作数的的第n位于第二个操作数的第n位 相反,那么结果的第n为也为1,否则为0
testSize(firNum, secNum, dOrSize);
System.out.println("按位反:");
// 操作数的第n位为1,那么结果的第n位为0,反之。
testSize(firNum, secNum, negSize);
System.out.println("异或实现两数交换:");
exchangeNum(firNum, secNum);
System.out.println("位运算实现两数相加:");
System.out.println(firNum+"+"+secNum+"="+binaryAdd(firNum, secNum));
System.out.println("位运算实现两数相减:");
System.out.println(firNum+"-"+secNum+"="+binarySub(firNum, secNum));
System.out.println("位运算实现两数相乘:");
System.out.println(firNum+"*"+secNum+"="+binaryMultiply(firNum, secNum));
System.out.println("位运算实现两数相乘,其中有负数:");
System.out.println(firNum+"*"+"("+nTestNum+")"+"="+binaryMultiply(firNum, nTestNum));
System.out.println("位运算实现两数相除:");
System.out.println(firNum+"/"+secNum+"="+binaryDivide(firNum, secNum));
System.out.println("位运算实现两数相除,其中有负数:");
System.out.println(firNum+"/"+"("+nTestNum+")"+"="+binaryDivide(firNum, nTestNum));
}
/*
* 打印结果值
*/
public static void printNum(Object num){
System.out.print(num);
}
/*
* 显示数值的二进制表示
*/
public static String getBinarryValue(int num){
String binarryValue = Integer.toBinaryString(num);
return binarryValue;
}
/*
* 移位预算
*@param testNum:输入数值
*@param size:移位量
*@param operS 移位运算符
*@return 输出移位后的值以及相应的二进制表示
*/
public static void testMove(int testNum,int size,String operS){
int result = 0;
switch (operS) {
case "<<":
result = testNum<<size;
break;
case ">>":
result = testNum>>size;
break;
case ">>>":
result = testNum>>>size;
break;
default:
break;
}
String testNumBinarry = getBinarryValue(testNum);
String resultBinarry = getBinarryValue(result);
printNum(testNum+","+result+"\n"+testNumBinarry+"\n"+
resultBinarry+"\n");
}
/*
* 位运算 与、或、异或
* @param firNum 第一个数值
* @param secNum 第二个数值
* @param sizeC 位操作符
* @return addResult 按位与之后的结果值
*/
public static void testSize(int firNum,int secNum,String sizeC){
int result = 0;
switch (sizeC) {
case "&":
result = firNum & secNum;
break;
case "|":
result = firNum | secNum;
break;
case "^":
result = firNum^secNum;
break;
case "~":
result = ~firNum;
break;
default:
break;
}
String firNumBinarry = getBinarryValue(firNum);
String secNumBinarry = getBinarryValue(secNum);
String resultBinarry = getBinarryValue(result);
printNum(firNum+","+secNum+","+result+"\n");
printNum(firNumBinarry+"\n"+secNumBinarry+"\n"+resultBinarry+"\n");
}
/*
* 异或实现数字交换
*/
public static void exchangeNum(int firNum, int secNum){
firNum = firNum ^ secNum;
secNum = firNum ^ secNum;
firNum = firNum ^ secNum;
printNum(firNum+","+secNum+"\n");
}
/*
* 位操作实现:加、减、乘、除运算
* 1. 常用的一个等式:
* -n = ~(n - 1) = ~n + 1
* 2. 获取整数的二进制的最右边的1:n & (-n) 或 n & ~(n - 1)。
* 例如 n = 010100, -n = 101100,那么n & (-n) = 000100
* 3. 去除整数的二进制的最右边的1:n & (n - 1)。
* 例如 n = 010100,n-1 = 010011,n&(n-1) = 010000
*/
/*
* 加法操作
* 实现加法操作使用”异或“和”与“来实现。
* 对应位的异或操作可以得到该位的值,对应位的与操作可以产生该位对高位的进位值。
*/
public static int binaryAdd(int a, int b) {
int temp, addResult;
do {
addResult = a ^ b; //该操作得到本位的加法结果
temp = (a & b) << 1; //该操作得到该位对高位的进位值
a = addResult;
b = temp;
} while (temp != 0); //循环直到某次运算没有进位,运算结束
return addResult;
}
/*
* 减法操作
* 减法操作可以用加法操作来实现。
* 例如 a - b = a + (-b) = a + (~b + 1)
*/
//减法
public static int binarySub(int a, int b) {
return binaryAdd(a, binaryAdd(~b, 1));
}
/*
* 乘法操作
* 二进制的乘法与十进制原理类似:
* 都是将乘数的每一位和被乘数的每一位依次相乘,然后将相乘的结果相加即可。
* 如果乘数b的第i(i >= 1;i = 1是乘数最右侧的那一位)位为1,
* 那么该位与被乘数a相乘的结果S[i]就是(a << i);
* 然后将这些所有的结果S[i]相加即为最后结果。
*/
/*乘法
* 该过程中的bit_map是为了快速得到乘法过程中某位相乘的中间结果S[i]需要位移的位数。
* bit_map的键值是2^0, 2^1,2^2, ……之类的数,对应的值是0,1,2,……(即需要位移的位数)。 */
public static int binaryMultiply(int a, int b) {
int sum = 0;
HashMap<Integer, Integer> bit_map = new HashMap<>();
boolean neg = (b < 0);
if(b < 0){
b = -b;
}
for(int i = 0; i < 32; i++) {
bit_map.put(1<<i, i);
}
while(b > 0) {
//b & ~(b - 1)可以得到乘数b的二进制表示中最右侧1的位置 last_bit记录被乘数a需要位移的位数
int last_bit = bit_map.get(b & ~(b - 1));
//将得到的乘法结果全部相加即为最后结果
sum += (a << last_bit);
b &= b-1; //每次将b的二进制表示的最右侧1去掉用于下一次乘法
}
if(neg) sum = -sum;
return sum;
}
/*
* 除法过程:
* (1)第一次除法先找到除数应该左移的位数,使得除数是不大于除数的数,上图中将除数左移了三位(11<< 3 = 11000),变为11000;然后本次除法结果为(1 << 3);被除数变为了原来的被除数101011 减去当前的除数11000:10011,该被除数就是下一次除法的被除数。
* (2)第二次除法的被除数为10011,此次的除数为上一次除法右移一位,即(原始除数11左移两位:11<<2 = 1100);本次除法结果为(1<<2);被除数变为10011 - 1100 = 111,这作为下一次除法的被除数。
* (3)第三次除法的被除数变为111,除数是上一次除法右移一位,也就是初始除数11左移一位(11<< 1 = 110);本次除法结果为(1<<1);被除数为111 - 110 = 1;
* (4)乘法结束。商为(1 << 3 + 1 << 2 + 1 << 1) = 1000 + 100 + 10 = 1110 = 14。
*/
public static int binaryDivide(int a, int b){
boolean neg = (a > 0) ^ (b > 0);
if(a < 0)
a = -a;
if(b < 0)
b = -b;
if(a < b)
return 0;
int msb = 0;
//msd记录除数需要左移的位数
for(msb = 0; msb < 32; msb++) {
if((b << msb) >= a)
break;
}
int q = 0; //记录每次除法的商
for(int i = msb; i >= 0; i--) {
if((b << i) > a)
continue;
q |= (1 << i);
a -= (b << i);
}
if(neg)
return -q;
return q;
}
}
运行结果:
左移操作:
5,20
101
10100
-5,-20
11111111111111111111111111111011
11111111111111111111111111101100
右移操作:
5,1
101
1
-5,-2
11111111111111111111111111111011
11111111111111111111111111111110
无符号右移操作:
5,1
101
1
-5,1073741822
11111111111111111111111111111011
111111111111111111111111111110
按位与:
8,9,8
1000
1001
1000
按位或:
8,9,9
1000
1001
1001
按位异或:
8,9,1
1000
1001
1
按位反:
8,9,-9
1000
1001
11111111111111111111111111110111
异或实现两数交换:
9,8
位运算实现两数相加:
8+9=17
位运算实现两数相减:
8-9=-1
位运算实现两数相乘:
8*9=72
位运算实现两数相乘,其中有负数:
8*(-5)=-40
位运算实现两数相除:
8/9=0
位运算实现两数相除,其中有负数:
8/(-5)=-1