编程语言是如何完成运算的
# 简述一下二进制的计算
我们都知道计算机表示数据都是用二进制来表示,我们不妨来回忆一下二进制的计算,如下图所示,计算8的二进制时,我们只需不断除2,余数作为二进制数,得数做为继续计算的被除数,直至算到得数为0了为止。

# 为什么负数要使用补码进行表示
这一点我们可以用反正法来看看结果,就以-2+1为例,我们都知道负数高位为1,所以-2如果不用反码的话二进制为

而1的二进制为
按照这种表示方式,最终计算-2+1结果为-3,很明显这是不符合预期的,当然也不是说不能用,只是说处理负数+正数的时候,我们需要特殊处理一下,例如加法操作变成减法操作等。
# 使用反码进行计算
不难看出上述的正负数相加实现加法非常的恶心,所以我们需要进行改良,可以看出正负数是相反的,那么我们有没有一种方法来表示这样的对称关系呢? 反码,说白了就是将正数的二进制表示形式全部反过来,我们还是以-2+1为例进行一下计算
老规矩1的二进制为
00000000 00000000 00000000 00000001
-2的使用反码可以表示为
1111111 11111111 11111111 11111101
最终相加可得如下,我们都知道高位为1是负数,所以我们取反回去看看正数是多少加个负号就是结果了,最终得出结果为-1
11111111 11111111 11111111 11111110
# 为什么反码就能解决正负数相加问题,我们还需要用补码来表示负数呢?
很简单,我们用反码计算方式算一下3+(-2)
1的二进制为
0000000 00000000 00000000 00000001
-2用反码表示为
11111111 11111111 111111111 11111101
最终结果为全0,很明显计算异常了
00000000 00000000 00000000 000000000
所以为了避免这种计算异常我们就需要对反码+1得到补码,避免反码和正常二进制相加数据溢出导致全0的问题
所以-2使用补码可以表示为
11111111 11111111 11111111 1111110
最终运算结果就是1
# 小数的二进制转换
负数的二进制转换很简单,不断乘2,得到的整数作为结果,小数部分继续乘2,直到没有小数为止
话不多说,我们就以8.625为例,可以看到整数部分8得到1000无异议,0.625经过计算得到101
最终结果为1000.101

当然,也不是所有的小数都可以得到这样完美的结果,例如0.1,所以面对这种情况我们只能用近似值来表示了

需要注意的是1000.101小数还原为十进制的计算方式有点特殊十分位的二进制计算是2* 10 ^ (-1),依次类推,和二进制正数计算方式有所区别,具体可以参照下图

# 计算机如何保存带有小数的数据
# 介绍一下科学计数法
首先我们需要了解一下科学计数法,例如1230000化为1.23*10(^6)这种就是科学计数法,即整数部分只有一个数字,像 12.3 *10(^5)就不是科学计数法了
同理上文,8.625得到的二进制值为1000.101,参照十进制的计算方式我们也可以得出科学计数法结果为1.000101*2(^3)(小数点向左移动三位变小了,所以需要*2(^3))
# 如何用二进制空间表示科学计数法的结果
众所周知,float使用二进制32位表示,double用二进制64位表示,下图便是他们存储带有小数的数值的内存空间划分
我们不妨用10.625作为例子看看将其存到float内存空间是什么样子的
- 首先计算出10.625的二进制值
1010.101
- 将其化为科学计数法
1.010101*2(^3)
- 存到float的内存空间,通过上文我们可以了解到float的内存空间如下所示
然后看看我的科学计数表示的值,很明显是个正数所以符号为为0
因为科学计数*2(^3),所以指数位为3,但是指数为可能存在正负数的情况,计算机为了方便表示都会加一个偏移量127,例如我们这里的指数为3,那么指数位就是1000010,如果是-3那就表示为01111010最后是尾数010101由于只有6位,用0填充剩下的18位得010101000000000000000000综上所述得
0 1000010 010101000000000000000000
可能细心的同学会发现正数为的1没了,回答这个问题也很简单, IEEE 标准规定整数为只能1位且为1(像0.5这样的十进制通通用1*(2^-1)的二进制数表示)
既然将了换算成二进制,那么我们就来讲讲怎么还原为十进制吧
- 高位为0即正数 +
- 指数位为130-127=3
- 尾数为为010101可得1/4+1/16+1/64
- 再加上正数为的1得1.328125
- 1.328125*8=10.625
# 0.1+0.2==0.3返回false
# 问题描述
由上面的篇幅,我们大概就知道了0.1+0.2为什么不等于0.3了,我们不妨计算一下 就用float为例 0.1可得
0 01111011 10011001100110011001101
0.2用float存可得
0 01111100 10011001100110011001101
相加可得

# 解决方案
所以我们在使用java进行小数比较计算时建议使用BigDecimal 来解决,因为BigDecimal 通过借助整数来表示小数的方式,因为对于整数而言,二进制和十进制是完全一一对应的,用整数来表示小数,再记录下小数的位数,就可以完美的解决该问题。工具类如下:
public class ArithUtil {
// 除法运算默认精度
private static final int DEF_DIV_SCALE = 10;
private ArithUtil() {
}
/**
* 精确加法
*/
public static double add(double value1, double value2) {
BigDecimal b1 = BigDecimal.valueOf(value1);
BigDecimal b2 = BigDecimal.valueOf(value2);
return b1.add(b2).doubleValue();
}
/**
* 精确减法
*/
public static double sub(double value1, double value2) {
BigDecimal b1 = BigDecimal.valueOf(value1);
BigDecimal b2 = BigDecimal.valueOf(value2);
return b1.subtract(b2).doubleValue();
}
/**
* 精确乘法
*/
public static double mul(double value1, double value2) {
BigDecimal b1 = BigDecimal.valueOf(value1);
BigDecimal b2 = BigDecimal.valueOf(value2);
return b1.multiply(b2).doubleValue();
}
/**
* 精确除法 使用默认精度
*/
public static double div(double value1, double value2) throws IllegalAccessException {
return div(value1, value2, DEF_DIV_SCALE);
}
/**
* 精确除法
* @param scale 精度
*/
public static double div(double value1, double value2, int scale) throws IllegalAccessException {
if(scale < 0) {
throw new IllegalAccessException("精确度不能小于0");
}
BigDecimal b1 = BigDecimal.valueOf(value1);
BigDecimal b2 = BigDecimal.valueOf(value2);
// return b1.divide(b2, scale).doubleValue();
return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
}
/**
* 四舍五入
* @param scale 小数点后保留几位
*/
public static double round(double v, int scale) throws IllegalAccessException {
return div(v, 1, scale);
}
/**
* 比较大小
*/
public static boolean equalTo(BigDecimal b1, BigDecimal b2) {
if(b1 == null || b2 == null) {
return false;
}
return 0 == b1.compareTo(b2);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
测试:
public static void main(String[] args) {
double result = ArithUtil.add(1.2d, -1.1d);
System.out.println(result==0.1); //true
}
2
3
4
# 参考文献
为什么 0.1 + 0.2 不等于 0.3 ? (opens new window) 为什么计算机负数的补码取反后要加一?要加一?要加一? (opens new window) Java中的小数运算与精度损失 (opens new window)