我们都知道,Java中有8种基本数据类型,每种类型都有取值范围,比如1字节的byte取值范围是[-128~127],4个字节的int取值范围[-2^31~2^31-1]
。因为能表示的值的范围不同,如果我们将int类型强转为byte类型的话,是很可能丢失精度的。比如:
byte a = (byte) 127; // a = 127
byte b = (byte) 128; // b = -128
byte c = (byte) 256; // c = 0
二进制
一个字节占用8个长度,就是指一个字节占用了八个比特的长度,也就是八个二进制位。我们这里先以十进制256位例:
31为-24位 23位-16位 15位-8位 7位-0位
00000000 00000000 00000001 00000000
高位 低位
↓ 舍弃高位
00000000
将4字节的int类型转换成单字节byte,最高位的三个字节的存储单元将会被舍弃掉,这才是损失精度的要义所在!所以,根据上图高位舍弃的强转后,你自己也可以看出来,最后得到的byte十进制表示数字为0。嗯,似乎也就那么回事,还是很好理解。但是,我们换成128试试?
00000000 00000000 00000000 10000000
128强转后,按照高位舍弃理论,无非是舍弃掉了高字节位无意义的24个0而已,最后的byte字节表示的还是原来那么大,还应该是128才对,为什么实际程序运行的结果却变成了-128?其实,是因为,Java的数据是带符号位的。你知道二进制中如何表示一个数的正负吗?对于有符号的二进制来说,为了区分数的正负,约定以最高位作为符号位,0表示正数,1表示负数
,除去符号位剩下的就是这个数的绝对值部分:
我们带上符号位,回过头来重新分析上面对128的强转;当高位的三个字节被舍弃掉之后,连同舍弃的还有它的符号位0,最终的结果就是强转成单字节后,原理表示数值部分的1变成了符号位,表示为负,除去符号位,能表示值的就只有后7位的0000000了。这样表示的十进制值为-0,在带符号的二进制中,-0被规定用来指代-128,+0才表示0。看来,只要带上符号位,本文最开始的输出结果是很好分析的。但是,有了符号位,这里又有疑问了,如果符号位占据了字节高位,当我们在进行算法运算的时候,符号位又该如何处理呢?
原码,反码和补码
- 原码:符号位加上原数值绝对值的二进制表示;
- 反码:正数的反码是其本身,负数的反码为保持符号位不变其余位置按位取反;
- 补码:正数补码依旧是其本身,负数补码为反码加1
其实,引入反码,我们已经可以将减法统一变作加法[1-1=1+(-1)]进行正确的计算了,已经解决了符号位的问题了,但是会产生-0和+0的问题,也就是0被带上了符号。虽然在人脑看来是正负0一样的,但是计算机可不这么认为,而且按照定义0会有两种原码表示,即0000 0000和1000 0000,这显然是有问题的。于是在反码的基础上加1变补码,彻底解决了正负0的问题,以前表示-0的10000000现在可以用来表示-128,因为-128=-1+-127=(1111 1111)补+(1000 0001)补=1000 0000。 这也是带符号位二进制能够多表示一个数的原因。
位运算
按位与(&)
相对应的二进制位同为 1 结果才为 1,否则都是 0,形如:
0&0=0,
0&1=0,
1&0=0,
1&1=1
利用这个特性,我们判断奇偶数就可以不用再传统的 n%2的方式了,直接用 n&1,结果为 1 就是奇数,为 0 就是偶数。why? 因为 0或正数,补码和原码相同,由于 1 的前 n 位都是 0 ,与 1 相与,结果肯定是 0 ,我们只关心最后一位,奇数肯定是 1,1与1相与结果为1;若为负数,原码转反码时,奇数最后一位由 1 变 0,但转补码后有加 1 操作,末尾为 1 ,判定同理。
按位或(|)
相对应的二进制位只要有一个为 1 ,结果即为 1,形如:
0|0=0,
0|1=1,
1|0=1,
1|1=1
按位异或(^)
相对应的二进制位数字不同,结果为 1 ,否则都是 0 ,形如:
0^0=0,
0^1=1,
1^0=1,
1^1=0
异或有个特性就是,任何数与 0 异或,结果都是其本身。利用这个特性,可用于数的交换,以此可以解决一些面试刁难:如何在不采用临时变量的情况下实现两个数的交换?
取反(~)
二进制位按位取反,0 变 1 ,1 变 0 。
左移(<<)
形如 a<<b,将 a 的各二进制位整体向左移 b 位,高位溢出位移出,低位补 0。在数值没有溢出的情况下,左移n位相当于乘 2 的n次方。例如 2<<3,即由二进制的 00000010 变成了 0010000,相当于 2 乘 2 的 3 次方,结果为 16。因为位运算是 CPU 直接支持的,这也就是上面提到的 2*8 最有效率的运算方法了。
右移(>>)
形如 a>>b ,原理同左移,只不过由于符号位在最高位,所以,如果右移的是负数,会在高位补 1 ,如果为正数,高位补 0
无符号右移(>>>)
与右移唯一的不同在于,不论原来最左边是什么数,移动后都在高位补 0。注意,没有无符号左移, 因为左移始终是在右边补 0 ,而符号位在左边,不存在补符号位的问题。
网友评论