位运算基础及基本应用

在处理整形数值时,可以直接对组成整形数值的各个位进行操作。这意味着可以使用屏蔽技术获得整数中的各个位(??)

位运算是针对整数的二进制进行的位移操作

整数 32位 , 正数符号为0,负数符号为1。十进制转二进制 不足32位的,最高位补符号位,其余补零

在Java中,整数的二进制是以补码的形式存在的

位运算计算完,还是补码的形式,要转成原码,再得出十进制值

正数:原码=反码=补码 负数:反码=原码忽略符号位取反, 补码=反码+1

例如:十进制4 转二进制在计算机中表示为(补码) 00000000 00000000 00000000 00000100

例如:十进制-4 转二进制在计算机中表示为(补码) 11111111 11111111 11111111 11111100

位运算符

&(与)

与( & )每一位进行比较,两位都为1,结果为1,否则为0

|(或)

或( | )每一位进行比较,两位有一位是1,结果就是1

^(异或)

每一位进行比较,相同为0,不同为1

规律

异或可以理解为不进位加法:

1+1=0

0+0=0

1+0=1

交换律。可任意交换运算因子的位置,结果不变

a ^ b = b ^ a

结合律

a ^ b ^ c = a ^ (b ^ c) = (a ^ b) ^ c

对于任何数x,(即同自己求异或为0,同0求异或为自己)

x ^ x = 0, x ^ 0 = x

自反性(即连续和同一个因子做异或运算,最终结果为自己)

a ^ b ^ b = a ^ 0 = a

~ (非/取反)

每一位进行比较,按位取反(符号位也要取反)

<

左移( << ) 整体左移,右边空出位补零,左边位舍弃 (-4 << 1 = -8)

>>(向右位移)

整体右移,左边空出位补零或补1(负数补1,整数补0),右边位舍弃 (-4 >> 1 = -2)

>>>(无符号右移)

同>>,但不管正数还是负数都左边位都补0 (-4 >>> 1 = 2147483646)

机器数和机器数的真值

机器数

一个数在计算机中的二进制表示形式,叫做这个数的机器数。机器数是带符号的,在计算机用机器数的最高位存放符号,正数为0,负数为1。

机器数的真值

由于机器数的第一位是符号位,所以机器数的形式值就不等于真正的数值.为了区别起见,将带符号的机器数对应的真正数值成为机器数的真值。比如0000 0001的真值 = +000 0001 = +1,1000 0001的真值 = –000 0001 = –1

原码、反码、补码

对于计算机而言,万物皆0、1,所有的数字最终都会转换成0、1的表示,有3种机器存储一个具体数字的编码方式,分别是:原码、反码和补码。

原码

原码表示法在数字前面增加了一位符号位,即最高位为符号位,正数位该位为0,负数位该位为1.比如十进制的5如果用8个二进制位来表示就是00000101,-5就是10000101。

反码

正数的反码是其本身,负数的反码在其原码的基础上,符号位不变,其余各个位取反。5的反码就是00000101,而-5的则为11111010。

补码

正数的补码是其本身,负数的补码在其原码的基础上,符号位不变,其余各位取反,最后+1。即在反码的基础上+1。5的反码就是00000101,而-5的则为11111011。

在计算机中负数采用二进制的补码表示,10进制转为二进制得到的是源码,将源码按位取反得到的是反码,反码加1得到补码

位运算应用

1.判断奇偶数

方法一

num%2 取模为0是偶数,反之则为奇数

String result=(num%2==0?)"偶数":"奇数";

方法二

偶数最低位是0.奇数最低位是1。对最后一位&1,为0是偶数,为1是奇数

String result=(num&1==0)?"偶数":"奇数";

2.获取二进制位是1还是0

实例:判断第五位的二进制位是1还是0

//方法一——将1左移4位,判断第五位是1还是0,然后右移4位判断0还是1)

String result1=(num&(1<<4)>>4)==0?"0":"1";

//方法二——第五位二级制数右移4位,&1判断0还是1

String result2=((num>>4)&1==0)?"0":"1";

问题:方法一中,左移4位判断为1还是0后,还有必要在右移4位吗?

3.交换两个变量的值

可参考我的另一篇博客

(Java版)算法——交换两个基本数据类型的变量值和数组中元素调换位置

代码及运算过程

int a = 50; //二进制 110010

int b = 60; //二进制 111100

a = a^b; //110010,111100——>001110

b = a^b; //001110,111100——>110010 ——>50

a = a^b; //001110,110010——>111100 ——>60

System.out.println(f+" "+g);//输出结果是:60 50

利用异或的规律证明

a = a^b;

b = a^b; //这里 b=a^b=(a^b)^b=a^b^b=a

a = a^b; //这里 a=a^b=(a^b)^a=b

4.不用判断语句,求整数的绝对值

num>>31,有符号右移,正数为0,负数为-1

num>>>31,无符号右移,正数为0,负数为1

num^0,为本身(同0求异或为自己)

num^-1,相当于取反;取反在+1,相当于是绝对值

int result=(num^(num>>31))+(num>>>31);

位运算实现加减乘除

加法

十进制的加法运算:

13 + 9 = 22

拆分运算过程:

不考虑进位,分别对各位数进行相加,结果为sum:

个位数3加上9为2;十位数1加上0为1; 最终结果为12;

考虑进位,结果为carry:

3 + 9 有进位,进位的值为10;

如果步骤2所得进位结果carry不为0,对步骤1所得sum,步骤2所得carry重复步骤1、 2、3;如果carry为0则结束,最终结果为步骤1所得sum:

这里即是对sum = 12 和carry = 10重复以上三个步骤,

不考虑进位,分别对各位数进行相加:sum = 22;

只考虑进位: 上一步没有进位,所以carry = 0;

步骤2carry = 0,结束,结果为sum = 22.

二进制实现上述过程:

13的二进制为0000 1101,9的二进制为0000 1001:

不考虑进位,分别对各位数进行相加:

sum = 0000 1101 + 0000 1001 = 0000 0100

考虑进位:

有两处进位,第0位和第3位,只考虑进位的结果为:

carry = 0001 0010

步骤2carry == 0 ?,不为0,重复步骤1 、2 、3;为0则结束,结果为sum:

本例中,

不考虑进位sum = 0001 0110;

只考虑进位carry = 0;

carry == 0,结束,结果为sum = 0001 0110

转换成十进制刚好是22.

伪代码推理:

以3+9为例

a = 0011, b = 1001;

start;

first loop;

1.1 sum = 1010

1.2 carry = 0010

1.3 carry != 0 , go on;

second loop;

2.1 sum = 1000;

2.2 carry = 0100;

2.3 carry != 0, go on;

third loop;

3.1 sum = 1100;

3.2 carry = 0000;

3.3 carry == 0, stop; result = sum;

end

有的加法操作是有连续进位的情况的,所以这里要在第三步检测carry是不是为0,如果为0则表示没有进位了,第一步的sum即为最终的结果。

代码实现

/**

* 功能描述:递归形式实现加法

*/

int add(int a ,int b) {

if (b == 0) {

return a;

} else {

//进位

int carry = (a & b) << 1;

//不进位加法

a = a ^ b;

return add(a, carry);

}

}

/**

* 功能描述:非递归形式实现加法

*/

int add2(int a ,int b){

int carry;

while (b != 0){

//进位

carry = (a & b) << 1;

//不进位加法

a = a ^b;

b = carry;

}

return a;

}

减法

思路:

减法操作,可以利用加法操作实现。例如:a+b=a+(-b)。需要将b由正数转为负数(二进制方式),然后执行加法操作。

为什么不实现减法器的原因:

减法比加法来的复杂,实现起来比较困难。加法运算其实只有两个操作,加、 进位,而减法呢,减法会有借位操作,如果当前位不够减那就从高位借位来做减法,这里就会问题了,借位怎么表示呢?加法运算中,进位通过与运算并左移一位实现,而借位就真的不好表示了。所以我们自然的想到将减法运算转变成加法运算。

正数变为负数,二进制如何改变?

通过2的补码来表示负数的,将数字的正负号变号(即取反+1)

第一步,每一个二进制位都取相反值,0变成1,1变成0(即反码)。

第二步,将上一步得到的值(反码)加1。

代码实现

/**

* 功能描述:减法(加上一个负数,负数=正数取反+1)

*/

int subtraction(int a ,int b){

//b由正数转为负数

b = add(~b,1);

return add(a,b);

}

乘法

实现方式一——利用加法累加

思路

乘数加上乘数倍的自己,然后处理正负号的问题。

处理乘数和被乘数的绝对值的乘积。

根据它们的符号确定最终结果的符号。

代码实现

/**

* 功能描述:乘法(利用加法实现)

* @param a 被乘数

* @param b 乘数

* @return 乘法结果

*/

int multiplication(int a,int b){

// 取绝对值,如果为负则取反加一得其补码,即正数

int multiplicand = a < 0 ? add(~a, 1) : a;

int multiplier = b < 0 ? add(~b , 1) : b;

// 计算绝对值的乘积

int product = 0;

int count = 0;

while(count < multiplier) {

product = add(product, multiplicand);

// 这里可别用count++,都说了这里是位运算实现加法

count = add(count, 1);

}

// 确定乘积的符号

// 只考虑最高位,如果a,b异号,则异或后最高位为1;如果同号,则异或后最高位为0;

if((a ^ b) < 0) {

product = add(~product, 1);

}

return product;

}

缺点

第一步对绝对值作乘积运算我们是通过不断累加的方式来求乘积的,这在乘数比较小的情况下还是可以接受的,但在乘数比较大的时候,累加的次数也会增多,这样的效率不是很高

实现方式二——求乘积

思路

以13*14为例

如果乘数当前位为1,则取被乘数左移一位的结果加到最终结果中;如果当前位为0,则取0加到乘积中(加0也就是什么也不做);

实现步骤

判断乘数是否为0,为0跳转至步骤4

将乘数与1作与运算,确定末尾位为1还是为0,如果为1,则相加数为当前被乘数;如果为0,则相加数为0;将相加数加到最终结果中;

被乘数左移一位,乘数右移一位;回到步骤1

确定符号位,输出结果;

代码实现

/**

* 功能描述:乘法(推荐)

* 考虑符号问题

* @param a 被乘数

* @param b 乘数

* @return 乘法结果

*/

int multiplication(int a,int b){

//将乘数和被乘数都取绝对值

int multiplicand = a < 0 ? add(~a, 1) : a;

int multiplier = b < 0 ? add(~b , 1) : b;

//计算绝对值的乘积

int product = 0;

while(multiplier > 0) {

// 每次考察乘数的最后一位

if((multiplier & 0x1) > 0) {

product = add(product, multiplicand);

}

// 每运算一次,被乘数要左移一位

multiplicand = multiplicand << 1;

// 每运算一次,乘数要右移一位(可对照上图理解)

multiplier = multiplier >> 1;

}

//计算乘积的符号

if((a ^ b) < 0) {

product = add(~product, 1);

}

return product;

}

除法

实现方式一——利用减法累减

思路

除数去减被除数,直到被除数小于除数时,此时所减的次数就是我们需要的商,而此时的被除数就是余数。

注意

需注意的是符号的确定,商的符号和乘法运算中乘积的符号确定一样,即取决于除数和被除数,同号为正,异号为负;余数的符号和被除数一样。

代码实现

/**

* 功能描述:除法(减法实现)

*/

int division(int a,int b){

// 先取被除数和除数的绝对值

int dividend = a > 0 ? a : add(~a, 1);

int divisor = b > 0 ? a : add(~b, 1);

int quotient = 0;// 商

int remainder = 0;// 余数

// 不断用除数去减被除数,直到被除数小于被除数(即除不尽了),直到商小于被除数

while(dividend >= divisor){

dividend = subtraction(dividend, divisor);

quotient = add(quotient, 1);

}

// 确定商的符号,如果除数和被除数异号,则商为负数

if((a ^ b) < 0){

quotient = add(~quotient, 1);

}

// 确定余数符号

remainder = b > 0 ? dividend : add(~dividend, 1);

// 返回商

return quotient;

}

缺点

如果被除数非常大,除数非常小,那就要进行很多次减法运算,效率低。

实现方式二一增大步长使用减法累减

思路

所有的int型数据都可以用[2 ^ 0, 2 ^ 1,…,2 ^ 31]这样一组基来表示(int型最高31位)。不难想到用除数的[2 ^ 31,2 ^ 30,…,2 ^ 2,2 ^ 1,2 ^ 0]倍尝试去减被除数,如果减得动,则把相应的倍数加到商中;如果减不动,则依次尝试更小的倍数。这样就可以快速逼近最终的结果。

2的i次方其实就相当于左移i位,因为int型数据最大值就是2^31,所以从31位开始

代码实现

/**

* 功能描述:除法(推荐)

*/

int division(int a,int b){

// 先取被除数和除数的绝对值

int dividend = a > 0 ? a : add(~a, 1);

int divisor = b > 0 ? a : add(~b, 1);

// 商

int quotient = 0;

// 余数

int remainder = 0;

for(int i = 31; i >= 0; i--) {

//比较dividend是否大于divisor的(1<>i)与divisor比较,

//效果一样,但是可以避免因(divisor> i) >= divisor) {

quotient = add(quotient, 1 << i);

dividend = subtraction(dividend, divisor << i);

}

}

// 确定商的符号

if((a ^ b) < 0){

// 如果除数和被除数异号,则商为负数

quotient = add(~quotient, 1);

}

// 确定余数符号

remainder = b > 0 ? dividend : add(~dividend, 1);

// 返回商

return quotient;

}

下篇文章总结下位运算在算法解题中具体如何使用:

(Java)算法——位运算在算法题中的应用

java游戏将相_(Java)算法——位运算基础及基本应用相关推荐

  1. yxc_第一章 基础算法(三)_双指针算法位运算

    一.双指针算法 1.双指针算法模板 2.AcWing 799 给定一个长度为 n 的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度. 输入格式 第一行包含整数 n. 第二行包含 n 个整 ...

  2. java青蛙过河打字_趣味算法——青蛙过河(JAVA)

    青蛙过河是一个非常有趣的智力游戏,其大意如下: 一条河之间有若干个石块间隔,有两队青蛙在过河,每队有3只青蛙,这些青蛙只能向前移动,不能向后移动,且一次只能有一只青蛙向前移动.在移动过程中,青蛙可以向 ...

  3. java游戏重新开始_问题1:java问题--某局小游戏结束后怎么重新开始?

    有个问题,想了好久没解决,上网找了下,貌似也没找到好的解决方法(谷歌暂时没上去),现在这里写下来吧: 最近在写一个五子棋的小游戏,也算是刚刚开始java入门的一个作品了吧,刚刚完成到人人对战(当然,还 ...

  4. java雪花数据库长度_雪花算法(SnowFlake)Java实现

    算法原理 SnowFlake算法生成id的结果是一个64bit大小的整数,它的结构如下图: 由于在Java中64bit的整数是long类型,所以在Java中SnowFlake算法生成的id就是long ...

  5. java游戏修改存档_【教你在用一键存档手机端修改JAVA游戏人物属性BT】详细教程...

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 教你详细用手机修改JAVA游戏人物属性.金钱.先下载下面的附件.一键(植入)存档(JAR).下载完成后安装该软件-打开该软件(选择要植入的RMS存档.植入 ...

  6. java游戏修改文件_教你使用JavaMagic来改JAVA手机游戏软件键值和改全屏

    教你使用JavaMagic来改JAVA手机游戏软件键值和改全屏 作者:admin教程来源:百科原创点击数:6688 更新时间:2009-6-4 JavaMagic是一款为解决手机Java游戏某些兼容性 ...

  7. 诺基亚java游戏宠物孵化_就这两招让诺基亚Java游戏运行于其他品牌的手机上

    就这两招让诺基亚 Java 游戏运行于其他品牌的手机上 佚名 [期刊名称] <数字通信> [年 ( 卷 ), 期] 2006(000)014 [摘要] Java 游戏是手机游戏中的主力军, ...

  8. JAVA PHP 按位异或运算_对php位运算^(按位异或)的理解

    最近在看一些加密函数,其中总涉及到一些位运算,尤其是^(按位异或),经过多方查找资料,对^的理解深入,分享资料留作纪念! /* 手册资料: 位运算符 位运算符允许对整型数中指定的位进行置位.如果左右参 ...

  9. java 生成objectid_算法~位运算ObjectId生成时的秒用

    位运算控制数字范围 通过位运算,可以对一个数进行限制,保证这个数在2n-1(3,7,15,31,63,127,255...)范围内,当大于指定的数时,会取这个0到2n-1里的某个数,不会让它溢出. 之 ...

最新文章

  1. html中绝对定位的父级,父元素相对定位,子元素绝对定位
  2. mqtt 域名连接_中国移动OneNet物联网平台,如何使用MQTT协议,进行连接
  3. Python二级笔记(18,19合集操作篇)
  4. Audirvana for Mac(高品质音乐播放器)
  5. 安装bootcamp时遇到的几个坑
  6. mysql中索引创建 查看和删除语句_MySQL如何创建和删除索引?
  7. 用微PE安装KALI LINUX到U盘,【U盘安装kali】U盘+kali+pe三合一教程!装机,存储(自己用来做U盘使用的空间)...
  8. PHP(3):PHP读取Excel文件的记录-方法1
  9. LPDDR4协议规范之 (四)命令和时序
  10. css中怎么把数字改成罗马数字,罗马数字和阿拉伯数字相互转换
  11. 天翼云服务器怎么重装系统,天翼云操作系统介绍
  12. 2022-01-15 OpenCV(3.4.1) Error: Image step is wrong (The matrix is not continuous, thus its
  13. python 百度地图api_使用Python玩转百度地图Api
  14. 益丰大药房互联网医院,积极推动中国大健康产业发展变革
  15. Mac M1系统 miniconda安装、配置conda环境,及在conda环境中安装激活QIIME2
  16. git 移除项目版本控制_Git - 关于版本控制
  17. PCB射频电路四大基础特性
  18. 智能家居2.0 - Matter 1.0 标准和受益者
  19. 第 9 章 Part / Chapter / Section
  20. 易经读书笔记15地山谦

热门文章

  1. 博学谷html css,博学谷 - CSS笔记12 - 清除浮动
  2. 物联网专科专业必修课程_江西自考专科物联网技术专业的考试课程/科目
  3. Qt实现桌面右下角放置窗体
  4. Linux内核深入理解定时器和时间管理(4):定时器 timer
  5. 实时Linux内核调度器 | Real-Time Linux Kernel Scheduler
  6. gc:C语言的垃圾回收库-中文
  7. PHP MySQL Functions (PDO_MYSQL)
  8. python中typeerror_python – TypeError:ufunc subtract不能使用类型为dtype(‘
  9. python之字典的操作
  10. android 下拉框大全,Android 下拉列表Spinner