位运算浅析

本文深入探讨位运算的概念,包括位、字节和字符的关系,无符号数与有符号数的区别,以及原码、反码和补码的原理。通过分析Java中的基本数据类型,解释位运算如位与(&)、位或(|)、异或(^)、取反(~)、左移(<<)、右移(>>)和无符号右移(>>>), 并提供了一个使用位运算解决剑指Offer编程题第48题的例子,展示了位运算在实际问题中的应用。" 20731,3651,菜鸟教程:服务器搭建CGI与PHP支持,"['服务器', 'CGI', 'PHP', 'IIS', 'Apache']

由于最近刷题时遇见了几道与位运算有关的题目,因此想借此机会把与位运算有关的知识点都整理一下。

要了解位运算,首先来整理一下二进制,二进制即用0和1两个数组及其组合表示任何数,二进制的进位规则是“逢二进一”,数字1在不同的位上代表不同的值,按从右至左的顺序,以2倍递增,二进制在计算机技术中广泛应用。

1.位

而每个二进制(即每个0或1)就被称为位,称为比特(bit),简记为b,是数据存储的最小单位,计算机中的CPU位数指的是CPU一次能处理的最大位数,如32位计算机的CPU一个机器周期内可以处理32位二进制数据的计算。

2.字节

字节(Byte)是计算机信息技术用于计量存储容量的一种计量单位,通常情况下一字节等于八位,也就是说一个字节用八个二进制(0或1)来表示。

3.字符与字节

字符是指计算机中使用的字母、数字、字和符号,是一种可以使用多种不同字符方案或代码页来表示的抽象实体。

其中有三种常用的字符方案:

ASCII码:一个英文字母(不分大小写)占一个字节的空间,一个二进制数字序列,在计算机中作为一个数字单元,一般为8位二进制数,换算为十进制,最小-128,最大127。一个ASCII码就是一个字节。

UTF-8编码:一个英文字符等于一个字节,一个中文(含繁体)等于三个字节,中文标点占三个字节,英文标点占一个字节。

Unicode编码:一个英文等于两个字节,一个中文(含繁体)等于两个字节,中文标点占两个字节,英文标点也占两个字节。

4.无符号数和有符号数

有符号数:机器自己是识别不出来正负数的,因此需要进行规定,如规定“0”表示正数,“1”表示负数,并规定将其放在有效数字的前面,从而组成有符号数。

无符号数:无符号数是针对二进制来讲的,无符号的表示范围是非负数,所有的二进制数都代表数值。

5.原码、反码和补码

首先,计算机在运算时,是按照补码的方式来进行运算的,而补码又是什么呢?补码、反码和原码是针对有符号数而言的,而有符号数中的正数这三者都是一样的,对于负数来说,反码是原码的除符号位全部取反,而补码从字面上理解就行,就是反码之后再进行补充,补1,也就是说反码是原码除符号位外取反在加一。就比如按照八位二进制而言,4在计算机中是这样表示的:0000 0100;而-4应该表示为1111 1100(先求原码,再补码)这样的话4+(-4) = 0000 0000,即0。这样就很正确了。

6.java中的基本数据类型

java中有八个基本数据类型。

byte占八位,即一个字节,通常说其范围是-128-127。看到这里你也许会想,那二进制就是1111 1111到0111 1111,博主也是这样想的:) 然而并不是,你可以自己再算一下,咦,为什么换成十进制是-127-127??因为你算错了^_^,看上面那句话,计算机在运算时,是按照补码进行运算的。那么都需要化成补码,0111 1111化成补码不变,那换算十进制还是127,没错,但是1111 1111换算成补码是1000 0001,是-1,这是最大的负数。那么最小的负数是什么呢?逆推的话应该原码是1000 0000,再转换为补码则是1111 1111 + 0000 0001 = 1000 0000,又回来了....再回来看这个源码1000 0000 这不是表示0么?对,那0000 0000 表示的是什么呢?也是0。那总不能用两种不同的字节表示一个数吧。因此就规定0000 0000 是+0,而1000 0000是-0,而-0的补码正是-127-1 = -128。所以-0也就表示-128了。

上述是我在网上找到的一个比较好的解释,具体是不是正确的有待探究,暂用此解释作为理解吧(谁叫本博主菜:)

因此,byte的取值范围应该是0000 0000 到0111 1111和1000 0000 到1111 1111,即-128~127了。

char占16位,即两个字节,由于java用的是Unicode编码,不过8位的ASCII码包含在Unicode中,其实它是用一个16位二进制数表示一个符号,从而将世界上所有的字符都囊括进去,并且规定char类型是非负无符号的,所以可以取值的范围是0000 0000 0000 0000到1111 1111 1111 1111,即0~65535(2的16次方-1).

short占16位,即两个字节,短整型数,和byte思想类似,取值范围为0000 0000 0000 0000 到 0111 1111 1111 1111和1000 0000 0000 0000 到 1111 1111 1111 1111,即-32768~32767(-2的15次方~2的15次方-1)

int占32位,四个字节,最常用的整型数,取值范围为0000 0000 0000 0000 0000 0000 0000 0000 到0111 1111 1111 1111 1111 1111 1111 1111和1111 1111 1111 1111 1111 1111 1111 1111到 1000 0000 0000 0000 0000 0000 0000 0000。即-2147,483,648~2147,483,687(-2的31次方~2的31次方-1)。嗯,二十一亿左右。

long占64位,八个字节,长整型数,取值范围,emmm....我就不打0了,手痛....取值范围是-9,223,372,036,854,775,808~9,223,372,036,854,775,807(-2的63次方~2的63次方)emmm....兆上面是什么来着?

float占32位,四个字节,单精度类型,精度是八位有效数字,取值范围是3.402823e+38~1.401298e-45(e+38表示10的38次方,e-45表示10的-45次方) 注意,通常浮点型的数据在不声明的情况下都是double型的,如果表示一个float型的,需要在数据后面加上“F”。

double占64位,八个字节,双精度类型,精度是十七位有效数组,取值范围是1.797693e+308~4.9000000e-324。注意浮点型的数据是不能完全精准的,所以有的时候在计算的时候可能会在小数点最后几位出现浮动,属于正常情况。

上面两个浮点数还是没有搞明白怎么回事,等下一次来填坑吧,想要继续深挖可参照大神博客:浮点数范围和取值

boolean 布尔型变量,具体大小没有明确规定,只有两个值:true和false,JVM会在编译时期将boolean类型转为int型,使用1来表示true,使用0来表示false.

 

额,好像有点跑偏了,转回正题,继续研究位运算。

7.位运算

a.位与(&)运算:

例如 9 & 5 = 1

转换为二进制即为 0000 1001 & 0000 0101 = 0000 0001 结果为1,从上面计算可以看出,位与就是将数化为二进制表示,然后每位二进制一一相与,即0 & 0 = 0,0 & 1 = 0,1 & 0 = 0,1 & 1 = 1.简言之,1 & 1 = 1,其余为0.

b.位或(|)运算:

例如 9 | 5 = 13

转换为二进制即为 0000 1001 | 0000 0101 = 0000 1101 结果为13,同理,运算规则为 0 | 0 = 0,0 | 1 = 1,1 | 0 = 1,1 | 1 = 1,简言之,0 | 0 = 0,其余为1.

c.异或(^)运算:

例如 9 ^ 5 = 12

转换为二进制即为 0000 1001 ^ 0000 0101 = 0000 1100 结果为12,根据上面计算可以看出,异或比较特别,相当于把两个二进制数相加,但是不进位。所以得出运算规则为 0 ^ 0 = 0, 0 ^ 1 = 1,1 ^ 0 = 1,1 ^ 1 = 0.

d.取反(~)运算:

例如 ~9 = -10

取反其实就是让其二进制所有的位数都翻转一下,如0000 1001取反则是1111 0110,但是这并不是-118,因为我们知道计算机在进行运算时,都是使用补码来进行运算的。因此取反得到的是补码,转换为容易理解的原码,即取反再加一,可以等到1000 1010,即-10.简言之,取反就是在该数原码的基础上,改变最高符号位,再加一就行了。

e.左移(<<)运算:

例如 9 << 2 = 36

左移其实就是将其二进制向左移动一定位数,左边的二进制位直接舍弃,右边的补0。即0000 1001 << 2 = 0010 0100 结果为36,简言之,m左移n位即为m*2^n(忽略符号的情况下)。

f.右移(>>)运算:

例如 -4 >> 2 = -1

右移和左移相对,即将其二进制向右移动一定位数,右边的二进制位直接舍弃,左边的正数补0,负数补1。即 1000 0100(原码)->1111 1100(补码)>> 2 = 1111 1111(补码) ->1000 0001(原码),简言之,m右移n位即为m*(1/2)^n。

g.无符号右移(>>>)运算:

例如 -4 >>> 2 = 63

无符号右移即无论正负,将二进制向右移动一定位数,右边二进制直接舍弃,左边直接补0。即1000 0100(原码)->1111 1100(补码) >>> 2 = 0011 1111(原码) 结果为 63.

无符号左移,哪来的什么无符号左移,你看看左移运算就知道了。

好,位运算基本梳理完了,接下来上题!

题目描述:

写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。(出自剑指Offer编程题第48题)

分析:既然不能用四则运算,那就只能向位运算的方向想了。先想想六大位运算中哪一个最像加运算,没错,就是亦或,但是上面明确说了,亦或不进位,什么情况下不进位呢,1 ^ 1的时候不进位,那就想办法让它进位!那么多位运算中哪一个像进位呢?左移运算!但是左移不能全左移,必须需要进位的那一个左移,所以将其它不需要进位的全都化为0,这就用到了位与运算了。

上代码:

public class Solution48{

    public static void main(String args){

    }
    //运行时间:28ms 占用内存:9200k
    //主要考察的是位运算,异或,与运算
    public int Add(int num1,int num2) {
        while(num2 != 0){
            int temp = num1 ^ num2;
            num2 = (num1 & num2) << 1;
            num1 = temp;
        }
        return num1;
    }
}

 

参考材料:

https://baike.baidu.com/item/%E8%A1%A5%E7%A0%81/6854613?fr=aladdin

https://baike.baidu.com/item/%E4%BD%8D/7202673?fr=aladdin

https://baike.baidu.com/item/%E5%AD%97%E8%8A%82/1096318?fr=aladdin

https://baike.baidu.com/item/%E5%AD%97%E7%AC%A6/4768913

https://www.cnblogs.com/fuhaots2009/p/3476502.html

https://blog.youkuaiyun.com/zheng2008hua/article/details/6642200

https://www.cnblogs.com/maoxiuying/p/8522241.html

https://blog.youkuaiyun.com/xiaopihaierletian/article/details/78162863

https://blog.youkuaiyun.com/u013291076/article/details/84950188

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值