计算机进制转换详解以及Java的二进制的运算方法

  本文详细介绍了计算机进制的基本概念,随后给出了常见进制转换的方法,然后介绍了整数的二进制的计算规则,最后说明了一些二进制计算需要注意的地方(坑)。

1 进制概述

  进制:就是进位制,是人们规定的一种进位方法。对于任何一种进制–X进制,就表示某一位置上的数运算时是逢X进一位。二进制就是逢二进一,八进制是逢八进一,十进制是逢十进一,十六进制是逢十六进一。
  二进制的由来:任何数据在计算机中都是以二进制的形式存在的。二进制早期由电信号开关演变而来,1表示开,0表示关。这样1,0组成的数据就是二进制数据。

1.2 计算机储存单位

  计算机中,一个二进制数占用一位(bit,也叫比特),八位二进制数组成一个字节(byte)。它们是计算机储存单位,用来储存数据。基本储存单位是bit,常用储存单位是字节Byte。
  常见计算机储存单位有:

Bit——比特
Byte——字节
KB——千字节
MB——兆字节
GB——吉字节
TB——太字节

  计算机储存单位的换算规则是:

1 Byte(B) = 8 bit
1 Kilo Byte(KB) = 1024B
1 Mega Byte(MB) = 1024 KB
1 Giga Byte (GB)= 1024 MB
1 Tera Byte(TB)= 1024 GB
1 Peta Byte(PB) = 1024 TB
1 Exa Byte(EB) = 1024 PB
1 Zetta Byte(ZB) = 1024 EB
1Yotta Byte(YB)= 1024 ZB
1 Bronto Byte(BB) = 1024 YB
1Nona Byte(NB)=1024 BB
1 Dogga Byte(DB)=1024 NB
1 Corydon Byte(CB)=1024DB

1.3 不同进制的组成

  二进制
由0,1组成。以0b开头
  八进制
由0,1,…7组成。以0开头
  十进制
由0,1,…9组成。整数默认是十进制的
  十六进制
由0,1,…9,a,b,c,d,e,f(大小写均可,分别代表10,11,12,13,14,15)。以0x开头

1.4 简单的不同进制的整数转换

系数:就是每一位上的数据。
基数:X进制,基数就是X。
权:在数的右边,从0开始编号,对应位上的编号即为该位的权。

1.4.1 其他进制整数到十进制

十进制 --------------------> 十进制
12345 =1 * 104+2*103+3 * 102+4*101+5 * 100 =12345
推论: 其他进制转换为十进制,把 系数*基数的权次幂 相加 即可。
练习:
把0b100,0100,0x100转换成十进制
0b100=1 * 22+0 * 21+0 * 20 = 4
0100=1 * 82+0 * 81+0 * 80= 64
0x100=1 * 162+0 * 161+0 * 160= 256

1.4.2 十进制整数到其他进制

推论:除基(要转换位某进制的基数)取余,直到商为0,余数反转。
把52分别表示成二进制,八进制,十六进制
在这里插入图片描述

1.4.3 任意x进制转换为y进制

方法一:
X进制 —— 十进制 —— y进制
方法二:
二进制到八进制 从右到左,3位组合,高位不足补零,分组转换为10进制,再组合
100110 100 - 4 110 -6 ——>046

二进制到十六进制 从右到左,3位组合,高位不足补零,分组转换为10进制,再组合
100110 0010 – 2 0110 – 6 ——>0x26

2 有符号数据表示法

  Java针对整数常量提供了4种表现形式:二进制 八进制 十进制 十六进制。二进制的操作至少应该有八位。不够位的在左边补零,首位是符号位,正数为0,负数为1。如没有指定是负数,则默认是正数。
  在计算机内,有符号数有3种表示法:原码、反码和补码。所有数据的运算都是采用补码进行的。补码至少为8位,不够补0。
原码:
  就是二进制定点表示法,即最高位为符号位,“0”表示正,“1”表示负,其余位表示数值的大小。
反码:
  正数的反码与其原码相同;负数的反码是对其原码逐位取反,但符号位除外。
补码:
  正数的补码与其原码相同;负数的补码是在其反码的末位加1。
补充
  移码:不管正负数,将其补码的符号位取反即可。
案例:使用源码,反码和补码表示+7和-7
7的二进制为:111
  源码:  符号位    数值位
  +7    0    0000111
  -7    1     0000111
  反码:
  +7    0    0000111
  -7    1    1111000
  补码:
  +7    0    0000111
  -7    1    1111001

练习:
  1.已知某数X的原码为10110100B,试求X的补码和反码。
源码  1  0110100
反码  1  1001011
补码  1  1001100

  2.已知某数X的补码11101110B,试求其原码和反码。
源码  1  0010010
反码  1  1101101
补码  1  1101110

3 整数的二进制运算规则

  Java中数的计算,在计算机中都被转换为二进制补码的计算。并且Java中的数都是有符号数。

3.1 加法运算

  同十进制的加法运算一样,满足2就进一位。例如,计算int类型的 7+6 的值:由于两个数都是正数,因此源码、反码、补码值是一样的:
  7的源码、反码、补码值为:0000 …… 0111 这里省略前面高位的n个0
  6的源码、反码、补码值为:0000 …… 0110 这里省略前面高位的n个0
  然后使用二进制加法,满足2就进一位:

0000 0111   ——7
0000 0110   ——6
——————————
0000 1101   ——13

0000 1101转换为十进制就是13。可以看到运算还是比较简单的。

3.2 减法运算

  实际上减法的实现比加法更加复杂,主要是减法的借位操作相比于加法的进位更加难以表示。cpu的运算器没有减法运算,减法运算都是转换为加法运算来实现的。
例1,计算int类型的 7-6 的值,可以转换为7+(-6)的值:
  7的源码、反码、补码值为:0000 …… 0111 这里省略前面高位的n个0
  -6,由于是负数,那么它的源码、反码、补码值是不一样的:
  源码:1000 …… 0110  这里省略前面高位的n个0
  反码:1111 …… 1001   这里省略前面高位的n个1
  补码:1111 …… 1010  这里省略前面高位的n个1

  0000 …… 0111  ——7
  1111 …… 1010  ——-6
————————————
1 0000 …… 0001  ——1

  我们看到,这里最终多进了一个一(超过了32位),计算对于这种情况处理的方法很简单,那就是舍去比最高位(32位)还要高的位数,那么最终的二进制补码为:0000 …… 0001(这里省略前面高位的n个0),那么最终的结果转换为十进制就是1。
例2,计算int类型的 7-8 的值,可以转换为7+(-8)的值:
  7的源码、反码、补码值为:0000 …… 0111 这里省略前面高位的n个0
  -6,由于是负数,那么它的源码、反码、补码值是不一样的:
  源码:1000 …… 1000  这里省略前面高位的n个0
  反码:1111 …… 0111  这里省略前面高位的n个1
  补码:1111 …… 1000   这里省略前面高位的n个1

0000 …… 0111  ——7
1111 …… 1000  ——-8
————————————
1111 …… 1111  —— -1

  我们看到,这里最终没有多进了一个一,那么最终的二进制补码为:1111 …… 1111(这里省略前面高位的n个0),那么最终的结果转换为十进制就是-1。

3.3 乘法运算

同理,cpu的乘法运算也是转变为对应的加法运算的,我们只需要两步:

  1. 计算乘数的绝对值的乘积(加法总和)
  2. 确定最终乘积的符号,这取决于乘数的符号,同号为证,异号为负,然后改变符号位的值即可。

例1,计算int类型的 7*6 的值,可以转换为7+7+7+7+7+7的值:
  7的源码、反码、补码值为:0000 …… 0111 这里省略前面高位的n个0
  在我们自己进行计算时,实际上也可以采用十进制乘法的思想:

    0000 0111  ——7
    0000 0110  ——6
————————————
    0000 0000
     0000 0111
    0000 0111
……
    0010 1010  ——最终值 42

  0010 1010转换为十进制就是42,可以看到采用十进制的运算规则还是比较简单的,但是注意这不是cpu的方法,这只是我们找出来的规律。

3.4 除法运算

  我们都知道Java中的除法/,实际上返回的是商的值,那么除法是怎么实现的呢。和乘法一样,实际上除法可以转换为减法,不停的用除数去减被除数,直到被除数小于除数时,此时所减的次数就是我们需要的商,此时的被除数就是余数,商符号的确定和乘法中符号的确定是一样的而减法有可以转换为加法。
  所以实际上计算机的加减乘除只需要加法器就能全部实现。
  例1,计算int类型的 7/6 的值,可以转换为7-6=1,然后除数变成1小于6,此时除法计算完毕,商为1,余数为1
  在我们自己进行计算时,实际上也可以采用十进制除法的思想,只不过这里的商变成了1和0组成。

0000 0111  ——7
0000 0110  ——6
————————
0000 0001  ——最终值 1

  0000 0001转换为十进制就是1。

3 二进制运算的坑

  从上面的运算我们可以知道,转换为加法时,可能会用到进位操作,那么问题就来了,如果两个数的和超过所属类型的最大长度,计算机会如何处理呢?
  我们来看下面几个数的运算:

@Test
public void test4() {
    System.out.println(Integer.MAX_VALUE + 1);
    System.out.println(Integer.MAX_VALUE + (Integer.MAX_VALUE >> 1));
    System.out.println(Integer.MAX_VALUE + (Integer.MAX_VALUE >> 1) - Integer.MAX_VALUE);
    System.out.println(Integer.MAX_VALUE + 1 - Integer.MAX_VALUE);
}

  我们知道int类型占据4个字节,32位,由于是有符号的数,那么int类型的取值范围就是-231——231-1(-2147483648——2147483647)。Integer.MAX_VALUE表示int类型的最大值,即2147483647。
  首先看第一个运算,Integer.MAX_VALUE+1,转换为二进制加法运算,如下:

01111111 11111111 11111111 11111111    ——2147483647

00000000 00000000 00000000 00000001   ——1
————————————————————————————
10000000 00000000 00000000 00000000   —— -2147483648

  我们可以看到最终计算出来的值,由于进位的关系,导致原本的符号位从0变成了1,导致变成了非常大的负数,但是,注意计算机认为这种计算是合理的,此时计算机会将计算结果从补码-反码-源码-十进制的展示给我们,那么我们获得的十进制结果就变成了-2147483648,即int类型的最小值。
  然后看第二个运算,这里右边的括号里表示移位运算符,>>表示右移,移位运算符可以转换为m/(2^n)的形式,m表示被移位的数,n表示移动的位数。此时第二个运算转变成了Integer.MAX_VALUE + Integer.MAX_VALUE / 2。后面的运算是除法,得到的值是1073741823。最后就是加法,转换为二进制加法运算,如下:

01111111 11111111 11111111 11111111   ——2147483647
00111111 11111111 11111111 11111111   ——1073741823
——————————————————————————
10111111 11111111 11111111 11111110   —— -1073741826

  我们可以看到最终计算出来的值,同样由于符号位的进位,变成了负数。
  接下来看了第三个运算,Integer.MAX_VALUE + (Integer.MAX_VALUE >> 1) -Integer.MAX_VALUE。前面的值我们已经计算出来了,因此我们直接计算后面的减法,转换为二进制加法运算,如下:

 10111111 11111111 11111111 11111110      ——-1073741826
 10000000 00000000 00000000 00000001   —— -2147483647
——————————————————————————————
1 00111111 11111111 11111111 11111111     ——舍弃超过长度的位数

 00111111 11111111 11111111 11111111      ——1073741823

  这一次,我们可以看到符号位同样被改变了,同时,由于符号位都是1,那么自然继续向前进位,明显超出了32位长度,注意,此时计算机的处理很简单,直接将比符号位更高位数的值舍弃,那么最终剩下的二进制补码转换为十进制就是1073741823。
  接下来看了第四个运算,Integer.MAX_VALUE + 1 - Integer.MAX_VALUE。前面的值我们已经计算出来了,因此我们直接计算后面的减法,转换为二进制加法运算,如下:

 10000000 00000000 00000000 00000000   —— -2147483648
 10000000 00000000 00000000 00000001   —— -2147483647
————————————————————————————————————
1 00000000 00000000 00000000 00000001  ——舍弃超过长度的位数
 00000000 00000000 00000000 00000001   ——1

最终计算的出来的十进制值为1。
  从上面的案例中可以看出来,如果规定了一个数的类型,那么无论怎么相加,无论加多少,最终的值一定会属于原本数据类型的范围之内,就像一个循环,当超过一端的极值时,会从另一端开始,而不能突破它们的边界!
  明白了这些,对于我们的编码会很有帮助,特别是理解某些数组集合中关于数组容量上限判断的源码,比如ArrayList的hugeCapacity方法,当size的值变成Integer.MAX_VALUE时,它再添加元素时,size+1会变成-2147483648。此时即可表示数组长度超过上限(Java数组长度上限为Integer.MAX_VALUE)。

如果有什么不懂或者需要交流,可以留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!

猜你喜欢

转载自blog.csdn.net/weixin_43767015/article/details/106482990
今日推荐