整数的二进制、位运算、逻辑与或
二进制
正整数的二进制表示 (假定类型是byte)
正整数的二进制表示与此类似, 只是在十进制中,每个位置可以有10个数字,从0到9,但在二进制中,每个位置只能是0或1。
例如: 0000 1010 ==> 10
负整数的二进制表示 (假定类型是byte)
十进制的负数表示就是在前面加一个负数符号 -,例如-123。但二进制如何表示负数呢?
其实概念是类似的,二进制使用最高位表示符号位,用1表示负数,用0表示正数。
但负数表示不是简单的将最高位变为1,比如说:
byte a = -1,如果只是将最高位变为1,二进制应该是10000001,但实际上,它应该是11111111。
byte a=-127,如果只是将最高位变为1,二进制应该是11111111,但实际上,它却应该是10000001。
和我们的直觉正好相反,这是什么表示法?这种表示法称为补码表示法,而符合我们直觉的表示称为原码表示法,补码表示就是在原码表示的基础上取反然后加1。取反就是将0变为1,1变为0。
负数的二进制表示就是对应的正数的补码表示,比如说:
-1:1的原码表示是00000001,取反是11111110,然后再加1,就是11111111。
-2:2的原码表示是00000010,取反是11111101,然后再加1,就是11111110
-127:127的原码表示是01111111,取反是10000000,然后再加1,就是10000001。
给定一个负数二进制表示,要想知道它的十进制值,可以采用相同的补码运算。比如:10010010,首先取反,变为01101101,然后加1,结果为01101110,它的十进制值为110,所以原值就是-110。
byte类型,正数最大表示是01111111,即127,负数最大表示是10000000,即-128,表示范围就是 -128到127。其他类型的整数也类似,负数能多表示一个数。
负整数为什么采用补码呢?
负整数为什么要采用这种奇怪的表示形式呢?原因是:只有这种形式,计算机才能实现正确的加减法。
计算机其实只能做加法,1-1其实是1+(-1)。如果用原码表示,计算结果是不对的。比如说:
1 -> 00000001
-1 -> 10000001
+ ------------------
-2 -> 10000010
用符合直觉的原码表示,1-1的结果是-2。
如果是补码表示:
1 -> 00000001
-1 -> 11111111
+ ------------------
0 -> 00000000
结果是正确的。
再比如,5-3:
5 -> 00000101
-3 -> 11111101
+ ------------------
2 -> 00000010
结果也是正确的。
就是这样的,看上去可能比较奇怪和难以理解,但这种表示其实是非常严谨和正确的。
理解了二进制加减法,我们就能理解为什么正数的运算结果可能出现负数了。当计算结果超出表示范围的时候,最高位往往是1,然后就会被看做负数。比如说,127+1:
127 -> 01111111
1 -> 00000001
+ ------------------
-128 ->10000000
计算结果超出了byte的表示范围,会被看做-128。
补码的好处
以 +1 和 -1 作加法运算为例,如下图所示:
相信你已经发现,1 + (-1) 这样的加法运算只要将二进制数相加,然后-1的末位就会变成2,根据逢2进1机制,从右至左依次所有位都会变成0。
最后,最左端的符号位也会进位1变成0,丢弃溢出的1,就得到最后的结果0的二进制表示32个0。
对照本节开头的图,会发现所有的减法都可以转换成二进制位的加法运算:1-2 可以转换成1+(-2),(-1)-(-2)可以转换成-1+2……
这跟数学中的表示是一样的,而且非常地方便计算(很多计算机科学家都是从数学领域转入计算机工程,所以在很多细微之处的都能见到数学的影子)。因此,现代计算机硬件结构实际上只设计了加法器,大部分的减法其实都是转换成加法后再运算。
备注:
以正数的二进制数表示为基准,负数的表示只改变符号位,这样的表示方式就是原码。因此,正数的表示方式都是原码。
反码就是将原码除符号位以外的值全部取反,原来是1的变为0,原来是0的变为1。
补码就是在反码的基础上,在二进制数的右端末位加1(逢2进1)。
小结
正数的原码和反码和补码都一致;负数的原码是正数的符号位取反;负数的反码是原码的非符号位取反;负数的补码是反码加1。
位运算
左移:操作符为<<,向左移动,右边的低位补0,高位的就舍弃掉了,将二进制看做整数,左移1位就相当于乘以2。
无符号右移:操作符为>>>,向右移动,右边的舍弃掉,左边补0。
有符号右移:操作符为>>,向右移动,右边的舍弃掉,左边补什么取决于原来最高位是什么,原来是1就补1,原来是0就补0,将二进制看做整数,右移1位相当于除以2。
逻辑与或
两种逻辑与(&&和&)的运算规则基本相同,两种逻辑或(|| 和 |)的运算规则也基本相同。 &和|运算是把逻辑表达式全部计算完,而&&和||运算具有短路计算功能。
所谓短路计算,是指系统从左至右进行逻辑表达式的计算,一旦出现计算结果已经确定的情况,则计算过程即被终止。 对于&&运算来说,只要运算符左端的值为false,则因无论运算符右端的值为true或为false,其最终结果都为false。 所以,系统一旦判断出&&运算符左端的值为false,则系统将终止其后的计算过程; 对于 || 运算来说,只要运算符左端的值为true,则因无论运算符右端的值为true或为false,其最终结果都为true。 所以,系统一旦判断出|| 运算符左端的值为true,则系统将终止其后的计算过程。
小技巧
⑴ 乘法除法:n * 2 等价于 n << 1; n * 5 等价于 n << 2 + 1; n / 2 等价于 n >> 1。
备注:JVM执行时会自动转化,大部分其它高级语言的编译器会做类似优化转换,所以除非有特殊的理由,否则别这么写。
⑵ 取低位:n & 0x0000FFFF;取高位:n & 0xFFFF0000。
⑶ 奇偶判断:n & 1,等于0为偶,等于1为奇。
⑷ 正负判断:(n >>> 31) & 1,等于0为正,等于1为负。
⑸ 取余:n % m ,如m为2的幂次方,可用(n & (m - 1))替代。
参考:老马说编程