java开发经验-java中的数学应用

java开发经验-java中的数学应用

数字在计算机中的存储形式

参考:

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;
        }

连续三次使用异或,并没有临时变量就完成了两个数字交换,怎么实现的呢?


上面的计算主要遵循了一个计算公式:b^(a^b)=a。

我们可以对以上公式做如下的推导:

任何数异或本身结果为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<<n求得结果,如:

        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<<n的计算中,若n为正数,则实际移动的位数为n%32,若n为负数,则实际移动的位数为(32+n%32),右移,同理。

左移是乘以2的幂,对应着右移则是除以2的幂。

>> (右移运算符)

0.形式: m >> n 
1.特点:二元操作符,m数字二进制向右移动n位的结果;结果相当于: m / (2的n次方) 的结果向上取整
2.案例:
    int a = 3 >> 2 ; 
    System.out.println(a); //结果为 0 
3.分析:
    3的二进制补码表示为:
        00000000 00000000 00000000 00000011

    运算:3 >> 2 
            00000000 00000000 00000000 00000011
    >> 2    
    -------------------------------------------
            00000000 00000000 00000000 00000000           该补码对应十进制为0
4.注意: 最后的11向右移动两位,正好补上左边补上0; 所以结果为0             

>>> (无符号右移运算符)

1.二元操作符
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;
}
}

输出:

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说明没有进位了则此时原位和即所求和。

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;                
}
}

输出:

-2

位运算减法

减法:a-b

由-b=+(-b),~(b-1)=-b可得a-b=a+(-b)=a+(~(b-1))。把减法转化为加法即可。

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);        
    }
}

输出:

-4

位运算乘法

乘法:a*b

先来看一下二进制乘法是怎么做的:

(1011<<1,相当于乘以0010)  
(1011<<3,相当于乘以1000)  

可以看到,二进制乘法的原理是:从乘数的低位到高位,遇到1并且这个1在乘数的右起第i(i从0开始数)位,那么就把被乘数左移i位得到 temp_i 。直到乘数中的1遍历完后,把根据各位1而得到的被乘数的左移值们 temp_i 相加起来即得乘法结果。那么根据这个原理,可以得到实现代码:这里要点为:用i记录当前遍历的乘数位,当前位为1则被乘数左移i位并加到和中,同时i++处理下一位;为0则乘数右移,i++,处理下一位......直到乘数==0说明乘数中的1遍历完了。此时把和返回即可。

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<<i);
            b=b>>1;
            ++i;//i记录当前位是第几位
        }else{
            b=b>>1;
            ++i;
        }
    }
    return res;
}
}

输出:

-6

位运算除法

除法:a/b

除法的意义就在于:求a可以由多少个b组成。那么由此我们可得除法的实现:求a能减去多少个b,做减法的次数就是除法的商。

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<b){
        return 0;
    }else{
        res=sub(minus(a, b), b)+1;
    }
    return res;
}
public static int minus(int a,int b) {
    int B=~(b-1);
    return 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;                
}
}

输出:

3

数学基础

12的约数包括:1,2,3,4,6,12。共6个

一个数能够整除另一数,这个数就是另一数的约数。如2,3,4,6都能整除12,因此2,3,4,6都是12的约数。也叫因数。

计算约数的个数

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);
    }
}

输出:

6

质数定义为在大于1的自然数中,除了1和它本身以外不再有其他因数。

输出所有质数

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;
		}
	}
}

输出:

2
3
4
5
7
9

合数指自然数中除了能被1和本身整除外,还能被其他数(0除外)整除的数。与之相对的是质数,而1既不属于质数也不属于合数。最小的合数是4。

思路(100-1000之间的数字减去质数的数量就是所谓的合数)

输出合数的个数

public 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);
	}
}

输出:

758
 

猜你喜欢

转载自blog.csdn.net/qq_35029061/article/details/92529240