计算机原理(一)
数字在计算机中的存储形式
参考:https://blog.csdn.net/alpinistwang/article/details/87994617
1个字节是8位
只有8种基本类型可以算.其他引用类型都是由java虚拟机决定的自己不能操作
byte 1字节
short 2字节
int 4字节
long 8字节
float 4字节
double 8字节
char 2字节
boolean 1字节
F的二进制码为 1111
7的二进制码为 0111
这样一来,整个整数 0x7FFFFFFF 的二进制表示就是除了首位是 0,其余都是1
int 二进制位数:32
short 二进制位数:16
long 二进制位数:64
float 二进制位数:32
double 二进制位数:64
int型数据在计算机中以二进制存储,一个int型数据占4个字节,一个字节占8位,一共32位。
(1)第一位是标志位,标志位为0表示正数,标志位为1表示负数。
(2)剩余的31位是用来表示数字部分的
由原理可知,计算机存储数字时,第一位是标志位,只有31位用来存储数字的值。所以最大表示的正数为0111 1111 1111 1111 1111 1111 1111 1111,即:2^31−1
int转二进制数
public static void main(String[] args) {
int a = 7;
String binaryString = Integer.toBinaryString(a);
System.out.println(binaryString);
}
输出:
111
计算机存储
原码
int型数据在计算机中以二进制存储,一个int型数据占4个字节,一个字节占8位,一共32位。
(1)第一位是标志位,标志位为0表示正数,标志位为1表示负数。
(2)剩余的31位是用来表示数字部分的
补码
在计算机中,数字以补码存储。正数的补码是其本身,负数的补码是除标志位外,其他位按位取反再加一。
补码的特性
1、一个负整数(或原码)与其补数(或补码)相加,和为模。
2、对一个整数的补码再求补码,等于该整数自身。
3、补码的正零与负零表示方法相同。
时钟的计量范围是0~23,所以时间的模等于24。假设当前时针指向17点,而准确时间是9点,调整时间可有以下两种方法:
1.倒拨8小时,即:17 - 8 = 9;
2.顺拨16小时:17 + 16 = 33 ; 33 % 24 = 9
此例中, 16 就是 -8 在 24 进制中的补码表示。用 16 表示 -8 的好处是将减法转为了加法。
如果正数和负数都用原码表示,计算机计算加减法需要做不同的处理。而如果使用补码表示,计算机计算加减法时统一使用加法计算即可,减轻了计算机的负担。
第一个例子:7的存储形式
原码
(1)7是正数,所以标志位为0
(2)剩余的31位表示数字部分:000 0000 0000 0000 0000 0000 0000 0111
所以7的原码是:
0000 0000 0000 0000 0000 0000 0000 0111
补码
正数的补码与原码一样,所以7在计算机中的存储形式为:
0000 0000 0000 0000 0000 0000 0000 0111
第二个例子:-7的存储形式
原码
(1)-7是负数,所以标志位为1
(2)剩余的31位表示数字部分:000 0000 0000 0000 0000 0000 0000 0111
所以-7的原码是:
1000 0000 0000 0000 0000 0000 0000 0111
补码
负数的补码是除标志位外,其他位按位取反再加一。所以-7在计算机中的存储形式为:
1111 1111 1111 1111 1111 1111 1111 1001
计算 9 + 5
9 的补码表示为:
0000 0000 0000 0000 0000 0000 0000 1001
5 的补码表示为:
0000 0000 0000 0000 0000 0000 0000 0101
两个补码相加,并去掉32位以外的数:
0000 0000 0000 0000 0000 0000 0000 1110
即得到了结果 14
计算 9 - 5
9 的补码表示为:
0000 0000 0000 0000 0000 0000 0000 1001
-5 的补码表示为:
1111 1111 1111 1111 1111 1111 1111 1011
两个补码相加,并去掉32位以外的数:
0000 0000 0000 0000 0000 0000 0000 0100
即得到了结果 4
逻辑运算符
~ (非运算符)
1.特点:一元操作符,
2.规则:生成与输入位相反的值--若输入0,则输出1;若输入1,则输入0
3.案例:
int a = ~ 2 ;
System.out.println(a); //结果为
4.分析:
2的二进制补码表示为:
00000000 00000000 00000000 00000010
运算:
~ 00000000 00000000 00000000 00000010
------------------------------------------
11111111 11111111 11111111 11111101 //该补码对应十进制为:-3
应用
1:~n=-(n+1),比如:~3=-4
2:获取整数n的二进制串中最后一个1:-n&n=~(n-1)&n
3:去掉整数n的二进制串中最后一个1:n&(n-1)。
& (与运算符)
1.特点:二元操作符,操作两个二进制数据;两个二进制数最低位对齐,只有当两个对位数都是1时才为1,否则为0
2.案例:
int a = 3 & 2 ;
System.out.println(a); //结果为 2
3.分析:
3的二进制补码表示为:
00000000 00000000 00000000 00000011
2的二进制补码表示为:
00000000 00000000 00000000 00000010
运算:3 & 2
00000000 00000000 00000000 00000011
& 00000000 00000000 00000000 00000010
-------------------------------------------
00000000 00000000 00000000 00000010 二进制是2
应用
判断一个数n的奇偶性
n&1 == 1?”奇数”:”偶数”
为什么与1能判断奇偶?所谓的二进制就是满2进1,那么好了,偶数的最低位肯定是0(恰好满2,对不对?),同理,奇数的最低位肯定是1.int类型的1,前31位都是0,无论是1&0还是0&0结果都是0,那么有区别的就是1的最低位上的1了,若n的二进制最低位是1(奇数)与上1,结果为1,反则结果为0.
int a = 8;
if (a % 2 == 0) {
System.out.println("a是偶数");
} else {
System.out.println("a是奇数");
}
if ((a & 0x1) == 0) {
System.out.println("a是偶数");
} else {
System.out.println("a是奇数");
}
求一个整数的二进制中1的个数
思路:将整数n与1进行与运算,当整数n最低位是1时,则结果非零,否则结果为0。
然后将1左移一位,继续与n进行与运算,当次低位是1时,结果非零,否则结果为0。
循环以上操作,记录非零的次数即可。
代码如下:
public class TestDemo {
public static void main(String[] args) {
int a = 7;
int retNum = times1(a);
System.out.println(retNum);
}
public static int times1(int n ){
int count = 0;
int flag = 1;
while(flag <= n){
if((n&flag) != 0)
count++;
flag = flag<<1;
}
return count;
}
}
输出:
3
优化的解法
思路:
1.对一个整数n,比如10,它的二进制是1010。
2.将10减一变为9,9的二进制是1001.
3.比较10和9的二进制数,对10减一操作就等于将10的二进制的最低位上的1以及后面的位取反,前面的数不变。
总结:把一个整数减去1,再和原整数做与运算,会把该整数最右边1一个1变成0。那么一个整数的二进制表示中有多少个1,就可以进行多少次这样的操作。从而可以减少比较的次数。
public class TestDemo {
public static void main(String[] args) {
int a = 7;
int retNum = times2(a);
System.out.println(retNum);
}
public static int times2(int n){
int count = 0;
while(n!=0){
count++;
n = n&(n-1);
}
return count;
}
}
输出:
3
| (或运算符)
1.特点:二元操作符,操作两个二进制数据;两个二进制数最低位对齐,当两个对位数只要有一个是1则为1,否则为0
2.案例:
int a = 3 | 2 ;
System.out.println(a); //结果为 3
3.分析:
3的二进制补码表示为:
00000000 00000000 00000000 00000011
2的二进制补码表示为:
00000000 00000000 00000000 00000010
运算:3 | 2
00000000 00000000 00000000 00000011
| 00000000 00000000 00000000 00000010
-------------------------------------------
00000000 00000000 00000000 00000011 该补码对应十进制为3
^ (异或运算符)
1.特点:二元操作符,操作两个二进制数据;两个二进制数最低位对齐,当两个对位数只要有一个是1则为1,否则为0
2.案例:
int a = 3 | 2 ;
System.out.println(a); //结果为 3
3.分析:
3的二进制补码表示为:
00000000 00000000 00000000 00000011
2的二进制补码表示为:
00000000 00000000 00000000 00000010
运算:3 | 2
00000000 00000000 00000000 00000011
| 00000000 00000000 00000000 00000010
-------------------------------------------
00000000 00000000 00000000 00000011 该补码对应十进制为3
^ (异或运算符)
1.特点:二元操作符,操作两个二进制数据;两个二进制数最低位对齐,只有当两个对位数字不同时为1,相同为0
2.案例:
int a = 3 ^ 2 ;
System.out.println(a); //结果为 1
3.分析:
3的二进制补码表示为:
00000000 00000000 00000000 00000011
2的二进制补码表示为:
00000000 00000000 00000000 00000010
运算:3 ^ 2
00000000 00000000 00000000 00000011
^ 00000000 00000000 00000000 00000010
-------------------------------------------
00000000 00000000 00000000 00000001 该补码对应十进制为1
应用
将大写字母变为小写,将小写字母变为大写( charArray[i]^= 32, 因为在ASCII码中,大写字母与小写字母差了32,因此使用异或运算符,通过与 0 相异或,原字符的二进制形式在其他位保留原有的值,在第 6 位相异或,如果原有位为 0 则变为 1, 原有位为 1 则变为 0):
public class TestDemo {
public static void main(String[] args) {
String tempString = "1a2b3E5F6P7p";
char [] charArray = tempString.toCharArray();
for(int i = 0; i < charArray.length; i++)
if(Character.isLetter(charArray[i])) charArray[i] ^= 32;
System.out.println(String.valueOf(charArray)); // result is 1A2B3e5f6p7P
}
}
输出:
1A2B3e5f6p7P
不用临时变量交换两个数
在int[]数组首尾互换中,是不看到过这样的代码:
public static int[] reverse(int[] nums){
int i = 0;
int j = nums.length-1;
while(j>i){
nums[i]= nums[i]^nums[j];
nums[j] = nums[j]^nums[i];
nums[i] = nums[i]^nums[j];
j--;
i++;
}
return nums;
}
连续三次使用异或,并没有临时变量就完成了两个数字交换,怎么实现的呢?
我们可以对以上公式做如下的推导:
任何数异或本身结果为0.且有定理a^b=b^a。异或是一个无顺序的运算符,则b^a^b=b^b^a,结果为0^a。
再次列出异或的计算表:
操作数1 |
0 |
0 |
1 |
1 |
操作数2 |
0 |
1 |
0 |
1 |
按位异或 |
0 |
1 |
1 |
0 |
可以发现,异或0具有保持的特点,而异或1具有翻转的特点。使用这些特点可以进行取数的操作。
那么0^a,使用异或0具有保持的特点,最终结果就是a。
其实java中的异或运算法则完全遵守数学中的计算法则:
① a ^ a =0
② a ^ b =b ^ a
③ a ^b ^ c = a ^ (b ^ c) = (a ^ b) ^ c;
④ d = a ^b ^ c 可以推出 a = d ^ b ^ c.
⑤ a ^ b ^a = b.
<<(左移运算符)
0.形式: m << n
1.特点:二元操作符,m数字二进制向左移动n位的结果;结果相当于: m*(2的n次方)
2.案例:
int a = 3 << 2 ;
System.out.println(a); //结果为 12
3.分析:
3的二进制补码表示为:
00000000 00000000 00000000 00000011
运算:3 << 2
00000000 00000000 00000000 00000011
<< 2
-------------------------------------------
00000000 00000000 00000000 00001100 该补码对应十进制为12
应用
可以使用m< System.out.println("2^3=" + (1<<3));//2^3=8 System.out.println("3*2^3=" + (3<<3));//3*2^3=24 计算结果是不是很正确呢?如果非要说2<<-1为什么不等于0.5,前面说过,位运算的操作数只能是整型和字符型。在求int所能表示的最小值时,可以使用 //minInt System.out.println(1 << 31); System.out.println(1 << -1); 可以发现左移31位和-1位所得的结果是一样的,同理,左移30位和左移-2所得的结果也是一样的。移动一个负数位,是不是等同于右移该负数的绝对值位呢?输出一下就能发现不是的。java中int所能表示的最大数值是31位,加上符号位共32位。在这里可以有这样的位移法则: 法则一:任何数左移(右移)32的倍数位等于该数本身。 法则二:在位移运算m< 左移是乘以2的幂,对应着右移则是除以2的幂。 >> (右移运算符) 0.形式: m >> n 运算:3 >> 2 >>> (无符号右移运算符) 1.二元操作符 应用求绝对值 输出: 3 取绝对值 (a^(a>>31))-(a>>31) 先整理一下使用位运算取绝对值的思路:若a为正数,则不变,需要用异或0保持的特点;若a为负数,则其补码为源码翻转每一位后+1,先求其源码,补码-1后再翻转每一位,此时需要使用异或1具有翻转的特点。 任何正数右移31后只剩符号位0,最终结果为0,任何负数右移31后也只剩符号位1,溢出的31位截断,空出的31位补符号位1,最终结果为-1.右移31操作可以取得任何整数的符号位。 那么综合上面的步骤,可得到公式。a>>31取得a的符号,若a为正数,a>>31等于0,a^0=a,不变;若a为负数,a>>31等于-1 ,a^-1翻转每一位. 位运算加法 由a^b可得按位相加后没有进位的和; 由a&b可得可以产生进位的地方; 由(a&b)<<1得到进位后的值。 那么 按位相加后原位和+进位和 就是加法的和了,而 a^b + (a&b)<<1 相当于把 + 两边再代入上述三步进行加法计算。直到进位和为0说明没有进位了则此时原位和即所求和。 输出: -2 位运算减法 减法:a-b 由-b=+(-b),~(b-1)=-b可得a-b=a+(-b)=a+(~(b-1))。把减法转化为加法即可。 输出: -4 位运算乘法 乘法:a*b 先来看一下二进制乘法是怎么做的: (1011<<1,相当于乘以0010) 可以看到,二进制乘法的原理是:从乘数的低位到高位,遇到1并且这个1在乘数的右起第i(i从0开始数)位,那么就把被乘数左移i位得到 temp_i 。直到乘数中的1遍历完后,把根据各位1而得到的被乘数的左移值们 temp_i 相加起来即得乘法结果。那么根据这个原理,可以得到实现代码:这里要点为:用i记录当前遍历的乘数位,当前位为1则被乘数左移i位并加到和中,同时i++处理下一位;为0则乘数右移,i++,处理下一位......直到乘数==0说明乘数中的1遍历完了。此时把和返回即可。 输出: -6 位运算除法 除法:a/b 除法的意义就在于:求a可以由多少个b组成。那么由此我们可得除法的实现:求a能减去多少个b,做减法的次数就是除法的商。 输出: 3 数学基础 12的约数包括:1,2,3,4,6,12。共6个 一个数能够整除另一数,这个数就是另一数的约数。如2,3,4,6都能整除12,因此2,3,4,6都是12的约数。也叫因数。 计算约数的个数 输出: 6 质数定义为在大于1的自然数中,除了1和它本身以外不再有其他因数。 输出所有质数 输出: 2 合数指自然数中除了能被1和本身整除外,还能被其他数(0除外)整除的数。与之相对的是质数,而1既不属于质数也不属于合数。最小的合数是4。 思路(100-1000之间的数字减去质数的数量就是所谓的合数) 输出合数的个数 输出: 758
1.特点:二元操作符,m数字二进制向右移动n位的结果;结果相当于: m / (2的n次方) 的结果向上取整
2.案例:
int a = 3 >> 2 ;
System.out.println(a); //结果为 0
3.分析:
3的二进制补码表示为:
00000000 00000000 00000000 00000011
00000000 00000000 00000000 00000011
>> 2
-------------------------------------------
00000000 00000000 00000000 00000000 该补码对应十进制为0
4.注意: 最后的11向右移动两位,正好补上左边补上0; 所以结果为0
2.特点:它使用0扩展,无论正负都在最高位补0
3.案例:
int a = 3 >>> 2 ;
System.out.println(a); //结果为 0
4.分析:
3的二进制补码表示为:
00000000 00000000 00000000 00000011
运算:
00000000 00000000 00000000 00000011
>>>2
------------------------------------------
00000000 00000000 00000000 00000000 public class TestDemo {
public static void main(String[] args) {
int a = -3;
System.out.println(abs(a));
}
public static int abs(int val) {
// 看上去是够累的
return (((val & (~0 << 31)) >>> 31) == 1) ? ((~val) + 1) & (~(~0 << 31)) : val;
}
}
public class TestDemo {
public static void main(String[] args) {
int a = -3;
int b = 1;
System.out.println(add(a,b));
}
public static int add(int a,int b) {
int res=a;
int xor=a^b;//得到原位和
int forward=(a&b)<<1;//得到进位和
if(forward!=0){//若进位和不为0,则递归求原位和+进位和
res=add(xor, forward);
}else{
res=xor;//若进位和为0,则此时原位和为所求和
}
return res;
}
}
public class TestDemo {
public static void main(String[] args) {
int a = -3;
int b = 1;
System.out.println(minus(a,b));
}
public static int add(int a,int b) {
int res=a;
int xor=a^b;//得到原位和
int forward=(a&b)<<1;//得到进位和
if(forward!=0){//若进位和不为0,则递归求原位和+进位和
res=add(xor, forward);
}else{
res=xor;//若进位和为0,则此时原位和为所求和
}
return res;
}
public static int minus(int a,int b) {
int B=~(b-1);
return add(a, B);
}
}
(1011<<3,相当于乘以1000) public class TestDemo {
public static void main(String[] args) {
int a = -3;
int b = 2;
System.out.println(multi(a,b));
}
public static int multi(int a,int b){
int i=0;
int res=0;
while(b!=0){//乘数为0则结束
//处理乘数当前位
if((b&1)==1){
res+=(a<>1;
++i;//i记录当前位是第几位
}else{
b=b>>1;
++i;
}
}
return res;
}
}
public class TestDemo {
public static void main(String[] args) {
int a = 6;
int b = 2;
System.out.println(sub(a,b));
}
public static int sub(int a,int b) {
int res=-1;
if(a
public class TestDemo {
public static void main(String[] args) {
int count = 0;
int n = 12;
for(int i = 1;i <= n;i++) {
if(n % i == 0)
count++;
}
System.out.println(count);
}
}
public class TestDemo {
public static void main(String[] args) {
boolean flag = false;
for (int i = 2; i <= 10; i++) {
for (int j = 2; j * j < i; j++) {
if (i % j == 0) {
flag = true;
break;
}
}
if (flag == false) {
System.out.println(i);
}
flag = false;
}
}
}
3
4
5
7
9public class TestDemo {
public static void main(String[] args) {
int a = 0;
int b = 0;
for (int i = 100; i <= 1000; i++) {
a++;
boolean d = true;
for (int j = i / 2 + 1; j > 1; j--) {
if (i % j == 0) {
d = false;
}
}
if (d == true) {
b++;
}
}
int c = a - b;
System.out.println(c);
}
}