3-位运算

本文详细介绍了位运算(包括与、或、异或、取反等)、不同进制间的转换(如十进制、二进制、八进制和十六进制),以及计算机中整数的补码表示法及其在正负数运算中的应用。还讨论了算术位移和逻辑位移的区别,以及它们在处理正负数时的不同行为。
摘要由CSDN通过智能技术生成

学习王争的位运算,感觉没理解透,总结的也很一般

一,各种进制的转换

针对正整数的一些进制转换代码

共用的数组前后替换代码

private void swap(int[] arr, int n){
  int i = 0;
  int j = n - 1;
  while(i < j){
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
    i++;
    j--;
  }
}

1)整数转换成十进制数组

整数转换为十进制数组的逻辑是先对整数依次对10取余,并放入数组。

比如 1234 对10除余是 4, 再次除余是3,生成的数组是[4,3,2,1],此时再调转顺序,得到的就是转换好的十进制数组了。

public int[] convertToDeicimalArray(int a){
    int[] arr = new int[10];      //整数最大值就10位
    int i = 0;
    while(a != 0){
        arr[i++] = a%10;
        a = a/10;
    }
    swap(arr, i);						//调用公共的交换方法。
    return arr;
}

2)整数转换成二进制数组

同上面的整数生成十进制的数组逻辑相同,转换成二进制的本质是对2取余,代码如下:

public int[] convertToBinaryArray(int a){
    int[] arr = new int[32];			//整数的二进制是32位
    int i = 0;
    while(a != 0){
       arr[i++] = a%2;
       a = a/2;
    }
    swap(arr, i);
    return arr;
}

3)二进制数组转整数

二进制数组转整数有两种方式

方式1,从后往前加

public int convertToInt(int[] arr){
  int result = 0;
  int n = arr.length - 1;
  int weight = 1;
  for(int i = n ; i >= 0; i--){
      result = result + arr[i] * weight;
      weight *= 2;
  }
  return result;
}

方式2:从前往后加

public int convertToInt(int[] arr){
  int result = 0;
  for(int i = 0; i <= arr.length - 1; i++){
    result = result * 2 + arr[i];
  }
  return result;
}

4)整数转8进制或16进制数组

原理同转2进制。

另外8进制的表示方式为 089 这样,在数字前面加0

而16进制的表示方式为 0xA4/0XA4,在数字前面加0x/0X;

public int[] converToOct(int a){
  int[] arr = new int[20];
  int i = 0;
  while( a != 0){
    arr[i++] = a%8;
    a = a/8;
  }
  swap(arr, i);
  return arr;
}

//十六进制
public char[] converToHex(int a){
    char[] arr = new char[10];

    int i = 0;
    while(a != 0 ){
        int temp = a % 16;    // a & OxF 也可以,速度更快
        char c ;
        if(temp > 9){
            c = (char)('A' + (temp - 10));
        }else{
            c = (char)('0' + temp);
        }
        arr[i++] = c;
        a  = a/16;
    }
    swap(arr, i);
    return arr;
}

二,如何存计算机中存储整数:补码(重要)

1)原码表示法

计算机没有专门的硬件来识别正,负号,只能识别0,1这样的二进制,对于一串二进制以最高位做为符号位,其余位做为数值位,下图以byte和int来说明。

这样的表示方式是原码表示法。

对于一个字节的byte数值,其取值范围为 -127(1111 1111 十六进制-Ox7F) ~ 127(0111 1111 十六进制0x7F)),比较特殊的是0,有1000 0000 (-0),0000 0000(+0)两种方式表示。

2)补码表示法

为什么要用补码来存储整数

对于正数的加法比较简单,按位相加,篷二进一,如:

5 + 3 化作二进制就是(假设数据长度是byte) 0000 0101 + 0000 0011 = 0000 10000 为8

但是如果变成减法,5-3呢

不考虑符号位,会变成 0000 0101 + 1000 0011 = 1000 1000 为-8,明显结果不对,而如果要考虑符号位,就需要重新设计电路,太麻烦,后面综合考虑设计出了补码表示法。

补码的基本概念

补码的基本原则就是,正数的补码就是其原码,

负数的补码相对复杂点,符号位不变,数值位取反,末位+1,如下图:

补码的两个特殊点:0与最小值

1,原码的0可以有 1000 0000 和0000 0000两种表示方法,而对于补码,只有 0000 0000这一种表示方法。-0没有对应的补码

2,对于长度为n(n为二进制位,如int为32)的数据类型,最高位为1,其余位为0的二进制串,

表示的是-2^(n-1)的补码,比如byte,1000 0000 表示的是-128(-2^(8-1))的补码。且其没有原码。

这就是为什么整数类型的数据范围负数比正数多1的原因,其是以补码进行存储的。

正数的减法

对于正数的加法,因为其补码与原码一样,所以和原码的加法没有区别

看看正数的减法,还是以5-3为例(假设长度为byte)

可以理解为 5+(-3)替换为补码的加法,而补码的加法是不区分符号位与数值位的,所有的都是按位相加,逢二进1,溢出则截取。

补码表示为: 0000 0101 + 1111 1101 = 1 0000 0010 高位进行截取,变为 0000 0010 结果为2.

补码的应用

溢出

以如下人码来说明

int a = 2148473647;				//int的最大值0x7fffffff;
int b = 1;
int c = a + b;
System.out.println(c);			//输出-2148473648

a和b都是正数,原码,补码相同,a的值转换为二进制的补码为 0x7fffffff,b为 0x00000001,相加后为 0x80000000; 其最高位为1,其余位为0,刚好是最大值的补码,即-2148473648, 特殊值-2^31的补码。

防止溢出可以使用如下代码

public int sum(int a, int b){
   boolean downOverFlow = a < 0 && b < 0 && a < (Integer.MIN_VALUE - b);
   boolean upOverFlow = a > 0 && b > 0 && a > (Integer.MAX_VALUE - b);
   if(downOverFlow || upOverFlow){
     throw new RuntimeException("OverFlow");
   }
   return a + b;
}

忌如下代码:

public int sum(int a, int b){
  if((a + b) > Integer.MAX_VALUE || (a + b) < Integer.MIN_VALUE){
     throw new RuntimeException("OverFlow");
  }
  return a + b;
}

整数自动类型转换

自动类型转换,以short转换为int为例,正数在其前面补0,负数则补1,这样可以保证其值的不变。

正数的好理解,左侧的零不会改变值。

负数:补码的规则是符号位不变,数值位取反,末位+1,

比如-3 是byte时,原码为1000 0011 补码为 1111 1101,发现其转换成补码后,后面的值有个取反+1的操作,而前面是直接将其原来的值取反,而整数自动类型转换都是由取值范围小的向范围大的,其原码原先的位置肯定是0,取反就是1,所以补1。如 -3 byte转换成short时,就是

原码

补码

byte

1000 0011

1111 1101

short

1000 0000 0000 0011

1111 1111 1111 1101

另还可以观察下byte的原码与补码对比

同时有个知识点,补码的补码就是原码,网络证明如下,我绕进去没写出来,下面这个答案也没太看明白...

三,位运算

1)常见的位运算

与:&

或:|

非:!

异或:^ a^b^c == a^c^b; 同时a^a == 0; a^0 == a;

取反:~ ~~a = a;

如 byte a = 3 是 0000 0011 ~a 的字节码为 1111 1100。

位移: 算术位移:>> <<

逻辑位移: >>> <<<

2)逻辑位移和算术位移

不管逻辑位移还是算术位移都是针对补码

逻辑位移:>>>

逻辑位移不管是向右还是向左,不区分符号,都是在后面补0

算术位移:>>

算术位移右移

正数:在后面补0(与逻辑位移右移一样)

负数:在后面补1

算术位移左移

与逻辑左移一样,都是在后面补0,无论正反。

算术左移与逻辑左移都会截取掉高位,如上图,左移后负数变正数。

而负数的算术右移,如果一直下去,结果为会一直为-1,因为其高位补1,最后会一直是1111 1111 这样。

在不超出范围的情况下,算术右移>> 相当于除以2,对正数不停右移最后会为0,负数因为特殊会为-1。算术左移相当于乘以2,但得考虑超出范围被截取的情况。

所以算术左移与逻辑左移一样,都是后面补0,高位截图。相当于乘以2,但是很容易数据溢出,然后截取。

而算术右移当为正数时与逻辑右移一样,都是后面补0,相当于除以2,但是对负数,算术右移是补1,一直右移最后结果会一直为-1。

  • 14
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值