最近在看集合源码,发现ArrayDeque里面用到了大量的&运算,这牵扯到了二进制。突然发现自己对负数的二进制有点模糊了,对此进行了一些支持补充。
首先我们要对原码、反码和补码有个了解:
举一例,我们来看整数-1在计算机中如何表示。
假设这也是一个int类型,那么:
1、先取1的原码:00000000 00000000 00000000 00000001
2、得反码: 11111111 11111111 11111111 11111110
3、得补码: 11111111 11111111 11111111 11111111
十六进制表示: 0xFFFFFFFF
所以Java中Integer.toBinaryString(-5)结果为11111111111111111111111111111011. Integer是32位(bit)的.
负数的二进制规律:
1、取负数的绝对值的原码;
2、计算原码的反码;
3、对反码加一,获取补码。
我们知道数字在计算机中都是以二进制表示的,数字类型有:byte,short,int,long
我给计算机一个数字,计算机首先要确定他的类型,因为不同类型占有的空间是不一样的,
byte占有一个字节,总共8位,最高位是符号位,取值范围是-128-127
short占有两个字节
int占有4个字节
long占有8个字节
计算机中的二进制都是有符号位的二进制,所以对于一个byte类型的1111 1111,他是不能表示255的,首先我们看到最高位是1,他表示的是一个负数。
这里还要说明一个问题,虽然二进制的最高位表示符号位,1对应负,0对应正,但是绝对不是像咱们想的,负数就是在正数的基础上,把最高位改成就可以了。当然正数就是最高位是0,其他位按照咱们知道的计算即可。
那么负数到底是怎么表示的呢?为什么要这么不合我们常人的思维这样表示呢?
你给计算机一个负的十进制数,电脑中表示的思路是:先得到它对应的正值的二进制表示,然后取反,再加一,得到对应的负数的二进制表示
这也就是计算机中的补码表示法!!!
计算机中计算这样一个式子:8-1,8是正数,直接用它的原码(正数的原码,反码,补码都是一样的),-1是负数,用的它的补码,然后8+(-1),对应位相加即可。
这里如果按照都是byte类型来算的话:0000 1000 + 1111 1111((+1)0000 0001->(取反)1111 1110->(加一)1111 1111)= 0000 0111结果是7
上述过程,就是计算机计算加减法的实现方法。
对于如何由一个负数的二进制得到对应的十进制,是这样的:计算机得知这是一个负数的二进制,也就是最高位是1之后,先取反再加一,得到的是正值,去相反数也就是对应的负数的十进制表示。
至于具体为什么会出现反码和补码,网上相关的帖子很多,大家可以看一下,首先有一个-0和+0的问题,是通过反码补码解决的,这里谨希望大家不要想当然的认为负数在计算机中的表示就是把对应的正数的最高位改成1,例如-1的表示是1111 1111,而不是1000 0001,这就是一个活生生的例子。
为什么会想到分析这个问题,因为在写一个接受串口数据的程序的时候,遇到这个问题,具体如下:
接受到的数据流,Java是将其存储到byte类型的数组中的,自然就会把最高位当成符号位,而我对于数据的组织并不是把最高位当成符号位,因此需要将符号位计算进数值中。开始的思路是直接将得到的负数取相反数,然后再加上128,也就是像上面的错误理解,负数是在对应的正数的基础上把最高位变成1。
正确的做法应该是:
首先明确一下,因为你要让最高位表示数值,而byte类型的第八位肯定要表示符号的,所以,必然要将类型转换成空间更大的,这里转换成int类型,因为int类型的第八位不是符号位了。要让1-8位表示数值,而且肯定是正数,也就是除了1-8位其他的都是0,包括符号位,然而实际情况是:在将byte直接转化成int之后,低8位不变,其他位全部是1(例如上面提到的-1的int类型表示为1111 1111 1111 1111 1111 1111 1111 1111),很简单,因为int类型的负数的表示也是用补码的,必然也经过取反加一的过程,所以高的24位全部变成1,要让他们变成0,很简单,按位与上一个0xff(0000 0000 0000 0000 0000 0000 1111 1111)即可。
具体实现是int b = b1 & 0xff;
Java 十进制和十六制之间的转化(负数的处理)
在一些情况下,我们需要将数字在十进制和十六制下互相转化。
在 Java 中将十进制转化成十六进制非常的简单。
int var = 324;
String hex = Integer.toHexString(var);
long lvar = 3243143432134L;
String lhex = Long.toHexString(lvar);
这样就可以得 16进制的字符串了。当要从十六进制转化成十进制的时候也很方便 : Integer.parseInt(hex, 16);
Long.parseLong(hex, 16);
当 int 和 long 变量的值都是正数的时候,这样做一点问题都没问题,但当 int 和 long 变量的 值为负数的时候,问题就出现了 :
int var = -23243;
String hex = Integer.toHexString(var);
Integer.parseInt(hex, 16);
这时会报出 Exception:
Exception in thread "main" java.lang.NumberFormatException: For input string: "ffffa535" at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48) at java.lang.Integer.parseInt(Integer.java:459)
at demo.Main.main(Main.java:10)
这是为什么呢? 问题就在于 Integer.parseInteger 的时候,会将传入十六进制数字一致认为 成正数,所以这时候表示负数的十六进制字符串所表示的数字就超出了 Integer 所能表示的 数字范围, 所以会报出 NumberFormatException, 同时的问题也会现在 Long.parseLong 身上。
那么如何才解决这个问题呢?这时候的解决方案就是 BigInteger.
BigInteger bi = new BigInteger(hex, 16);
通过 BigInteger.intValue();和 BigInteger.longValue(); 得到你所需要的 int 或 long 型值。
直接进入主题....
问题:-3如何转换成二进制?
首先我们将+3转换成二进制,假设是为int类型(32位)的,那么二进制表示为:
0000 0000 0000 0000 0000 0000 0000 0011 --这称之为原码:一个整数的绝对值的二进制表示称之为原码。
负数转换成二进制分为3步:
1、首先将负数转换为对应的原码
-3的原码为(也就是+3转换成二进制后的字符串):
0000 0000 0000 0000 0000 0000 0000 0011
2、再将原码的每一位做取反操作得到反码。 取反操作:0变为1 1变为0; 取反后的结果即为:
1111 1111 1111 1111 1111 1111 1111 1100
3、将反码+1得到补码
1111 1111 1111 1111 1111 1111 1111 1101
现在用windows自带的计算器来验证一下,Win+R 输入calc,将计算器改为程序员,选择双字(4字节,32位)
-3的十进制以及二进制表示:
负数转换成八进制、十六进制,只需在补码(二进制)的基础上,3位合成一位计算,或者4位合成一位计算
-3的转换成二进制为:
1111 1111 1111 1111 1111 1111 1111 1101
八进制则将-3的二进制从右至左每3位为一个单元,不够三位用0补 即:
011 111 111 111 111 111 111 111 111 111 101
计算每一个单元,结果为:37777777775
十六进制则将-3的二进制从右至左每4位合并为一个单元,即:
1111 1111 1111 1111 1111 1111 1111 1101
计算后为: FFFFFFFD