java位运算及其四则表示

来源:互联网 发布:eugen systems知乎 编辑:程序博客网 时间:2024/05/16 10:48

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; //1000int secNum = 9; //1001String andSize = "&";//1000 8String orSize = "|";    //1001 9String dOrSize = "^";   //0001 1String negSize = "~";   //11111111111111111111111111110111 -9System.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进制为20testMove(nTestNum, size,operLS);// 1111 1111 1111 1111 1111 1111 1111 1011 -5     // 1111 1111 1111 1111 1111 1111 1110 1100 -20System.out.println("右移操作:");//右移   高位补,正补0 负补1testMove(pTestNum, size,operRS);// 0000 0000 0000 0000 0000 0000 0000 0101  5 然后右移2位,高位补0:  // 0000 0000 0000 0000 0000 0000 0000 0001  1testMove(nTestNum, size,operRS);// 1111 1111 1111 1111 1111 1111 1111 1011 -5  // 1111 1111 1111 1111 1111 1111 1111 1110 -2System.out.println("无符号右移操作:");//右移  高位补0testMove(pTestNum, size,operNS);// 0000 0000 0000 0000 0000 0000 0000 0101  5 然后右移2位,高位补0:     // 0000 0000 0000 0000 0000 0000 0000 0001  1testMove(nTestNum, size,operNS);// 1111 1111 1111 1111 1111 1111 1111 1011  -5   // 0011 1111 1111 1111 1111 1111 1111 1110  1073741822System.out.println("按位与:");// 位与:第一个操作数的的第n位于第二个操作数的第n位如果都是1,那么结果的第n为也为1,否则为0testSize(firNum, secNum, andSize);System.out.println("按位或:");// 第一个操作数的的第n位于第二个操作数的第n位 只要有一个是1,那么结果的第n为也为1,否则为0testSize(firNum, secNum, orSize);System.out.println("按位异或:");// 第一个操作数的的第n位于第二个操作数的第n位 相反,那么结果的第n为也为1,否则为0testSize(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,2010110100-5,-201111111111111111111111111111101111111111111111111111111111101100右移操作:5,11011-5,-21111111111111111111111111111101111111111111111111111111111111110无符号右移操作:5,11011-5,107374182211111111111111111111111111111011111111111111111111111111111110按位与:8,9,8100010011000按位或:8,9,9100010011001按位异或:8,9,1100010011按位反:8,9,-91000100111111111111111111111111111110111异或实现两数交换:9,8位运算实现两数相加:8+9=17位运算实现两数相减:8-9=-1位运算实现两数相乘:8*9=72位运算实现两数相乘,其中有负数:8*(-5)=-40位运算实现两数相除:8/9=0位运算实现两数相除,其中有负数:8/(-5)=-1


0 0