java位运算及其四则表示

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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值