位运算
在数学领域123属于整数、实数、正数等等,而在计算机领域123属于十进制数,即以10为基数的n次方的数制,当然面向对象是人类,那么对于计算机底层逻辑来讲,怎么存储123 这个数字呢?我们认知的正数、负数在计算机中是如何进行存储的呢?又是如何进行运算的呢?
123是如何在计算机中进行存储的
答:用0,1表示的二进制进行存储的。
这里默认大家都知道java中的int类型数和long类型数,我们都知道java中的int类型数是32位,long类型是64位,那这32位是什么呢?没错,就是32个0,1组成的数制。
先给答案,int类型数 123 在计算机中的存储:
00000000000000000000000001111011
如上就是123在计算机中的存储形式。那么怎么和十进制进行转换的呢?我们都知道二进制就是以2为基数的数制,逢二进一,和十进制进行转换即以2为基n的次方计算求得,其中n表示的就是哪一位上非0,从右向左分别为0,1,2...以此类推。
那么上面的可以进行计算为:
2^0 + 2^1 + 2^3 + 2^4 + 2^5 + 2^6 = 123
那么从二进制转换为十进制数如此,十进制转换为二进制即为相反的过程,此处不在赘述。
正数、负数在计算机中如何存储
答:最高位0表示正数,1表示负数
举个例子
10000000000000000000000001111011 最左边的1表示此数是一个负数
00000000000000000000000001111011 最左边的0表示此数是一个正数
在计算机中数字是如何进行计算的
此处先讲一下简单的加法运算,后续再讲复杂的运算。加法运算和我们在十进制数是一样的,两数相加,“逢二进一”。
与C语言不同,Java中没有无符号整数
- 无符号数顾名思义就是没有正负之说,它的范围是0~2^32-1,并且它最高
位的0或者1没有意义,表示数值。- 有符号数是区分正数和负数的,范围是-2^31~ 2^31 -1 ,最高位上的0表示正数,1表示负数
举个例子
- 正数的表示和转换为十进制数上述有例子,此处进行展示,不做详细(123)
00000000000000000000000001111011
计算方式
2^0 + 2^1 + 2^3 + 2^4 + 2^5 + 2^6 = 123
- 负数的表示和转换为十进制数(-123)
11111111111111111111111110000101
计算方式
便于计算机的快速性,也为了解决有无符号的一个通用性,使计算机底层对于任何数都是同一个计算方式,此处对于负数的计算有一个巧思:“取反加一”
~+1(取反加一)
第一步取反:
1 0000000000000000000000001111010 // 最高位的1不在计算数值中,表示负号(-)
第二步+1
1 0000000000000000000000001111011
-(2^0 + 2^1 + 2^3 + 2^4 + 2^5 + 2^6) = -123
总结一下:-123在计算机中存储是
11111111111111111111111110000101
它转换为十进制数时最高位表示负号不计数,后面的31位进行“取反加一”的操作后通过基底转换即可得到十进制数-123。
数讲完了,那什么是位运算呢?
位运算是程序设计中对位模式按位或二进制数的一元和二元操作。
古老的架构中,位运算是略快于加减法运算,比乘除法运算要快很多
现代的架构中,位元算和加法运算速度是相同的,但仍快于乘除法运算
位运算符
~ 取反
& 按位与
| 按位或
^ 按位异或
<< 左移
>> 带符号右移
>>> 不带符号右移
取反
一元运算,具有右结合性
例:~(1001) 取反后得---> 0110
按位与
二元运算,都是1即为1,其余均是0。
例:1 & 1 = 1,1 & 0 = 0,0 & 1 = 0,0 &0 = 0
按位或
二元运算,有1即为1。
例:1 | 1 = 1,1 | 0 = 1,0 | 1 = 1, 0 | 0 = 0
按位异或
二元运算,相同为0,不同为1(相异为1)
例:1^1 = 0,1^0 = 1,0^1 = 1,0^0 = 0
左移
二元运算,左移n位就是乘以2的n次方,高位丢弃,低位补0。
例:1左移1位,1<<1 = 2; 简单用8位数表示 00000001 << 1 == 00000010
⚠️ 当左移的数值超过int类型的边界,会出现溢出的情况
int num = Integer.MAX_VALUE;
print(num);
print(num << 1);
System.out.println(num<<1);
//输出结果
01111111111111111111111111111111
11111111111111111111111111111110
-2
int num = Integer.MIN_VALUE;
print(num);
print(num << 1);
System.out.println(num<<1);
//输出结果
10000000000000000000000000000000
00000000000000000000000000000000
0
带符号右移(有符号数)
二元运算,右移n位表示除以2的n次方。
带符号右移的意思是,当最高位是1右移时高位空出位置补1,当最高位是0右移时高位空出位置补0。同理,当右移后的数值大于int类型的边界,则会溢出。
int numRight = Integer.MIN_VALUE;
print(numRight);
print(numRight >> 1); //带符号右移,依然表示负数
System.out.println(numRight >> 1);
//输出结果
10000000000000000000000000000000
11000000000000000000000000000000
-1073741824
不带符号右移(无符号数)
二元运算,右移n位表示除以2的n次方,。最高位不管是0还是1,空出的高位均补0。
int numRight = Integer.MIN_VALUE;
print(numRight);
print(numRight >>> 1); //不带符号右移,表示正数
System.out.println(numRight >>> 1);
//输出结果
10000000000000000000000000000000
01000000000000000000000000000000
1073741824
此处可以看出来,当不带符号右移时,负数会变成正数。
再补充一下相反数,a的相反数是-a,-a的相反数是a
它的表述方式:-a 和 ~a + 1(“取反加一” 也表示相反数)a 为任何数
⚠️0的相反数是0,可通过“取反加一”观察,-2^31相反数是本身,也可通过“取反加一”计算出来。
int num = 0;
int numFan = -num;
int numFan1 = ~num + 1;
print(num);
print(numFan);
print(numFan1);
//输出结果
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
int num = Integer.MIN_VALUE;
int numFan = -num;
int numFan1 = ~num + 1;
print(num);
print(numFan);
print(numFan1);
//输出结果
10000000000000000000000000000000
10000000000000000000000000000000
10000000000000000000000000000000