关于Java中精确计算的问题

世界上有10种人,一种是懂二进制的,一种是不懂二进制的

发现的问题

之前看公司的代码发现有一个提供精确运算的工具类。想起以前确实有听说过浮点数用于计算精确度丢失的问题,但一直是知其然不知其所以然,如今有这个机会,就想着深入的学习一下这方面的相关东西。

由于计算机对于大小的比较会严格按照值的大小进行。计算时会有误差出现,这种误差会导致进行数值比较时出错。例如计算机不会认为0.9999999等于1。

找到的答案

查阅资料发现,原因在于进制的转换问题,在一些情况下十进制数的二进制表示形式可能不够精确。首先,对于十进制的整数,转换成二进制以后是能够除尽的,所以整数能够用二进制精度表示。问题出在小数这里,就像十进制数也有除不尽的数一样,二进制同样存在,而且是一些在十进制下能除尽的数使用二进制无法除尽,在这个进制下是无限小数,超过表示的位数之后就会将之后的部分舍弃,这样就会导致明明精准输入的数在二进制下不够精准,也就出现了精度的丢失。

这个进制真的也是很神奇的东西,将十进制向二进制转换,整数部分使用“除二取余法”,小数部分使用的是“乘二取整法”,那么要使得确保精度,小数部分的末位就应该是5的倍数。其他时候精度丢失就出现了。

然而精度的丢失发生在哪一个阶段呢,我们写几行代码看看。

double g = 0.05;
double h = 0.01;

System.out.println(h + g);
System.out.println("test: " + g);

我们可以发现输出的结果是:

0.060000000000000005
test: 0.05

通过代码来看,精度丢失似乎是发生在运算阶段,然而事实却并非如此。当不进行浮点运算时,可以直接使用定义时给的十进制表示方法,而运算后值发生了精确度上的变化,是因为本身参与运算的值就已经不够精确,在进行运算后,产生了不可预知的结果。

根据以上得出的结论就是:计算机中的浮点数都是以二进制进行的运算,对于部分由十进制转化为二进制的数由于在进制转化时无法除尽,对相应的二进制数只是一个近似值。所以当使用这种浮点数计算后,将其转换回十进制就会出现很小的误差。这种误差和原始数据是差了很多个数量级的,对数值大小的影响小,但是会影响数值比较时做出的判断。

所以其实浮点数只适合用于科学计算而不适用于精确计算。在《effective java》 中,也提到了这个原则 : float和double类型主要是为了科学计算和工程计算而设计的,它们执行二进制浮点运算,这是为了在广泛的数值范围上提供较为精确的快速近似计算,不应该被用于需要精确结果的场合。

Java中的解决方案

那么在java中如何进行精确的小数运算呢,bigdecimal。

使用BigDecimal并且一定要用String来构造。举个例子。

/** 
* 提供精确的加法运算。 
* @param v1 被加数 
* @param v2 加数 
* @return 两个参数的和 
*/  
//双精度或者浮点类型要转换成String类型再计算  
public static BigDecimal add(double v1, double v2) {  
	BigDecimal b1 = new BigDecimal(Double.toString(v1));  
	BigDecimal b2 = new BigDecimal(Double.toString(v2));  
	return b1.add(b2); 
}  
	
public static BigDecimal add(String v1, String v2) {  
	BigDecimal b1 = new BigDecimal(v1);  
	BigDecimal b2 = new BigDecimal(v2);  
	return b1.add(b2);  
} 

使用BigDecimal还有一些额外的好处,比如可以完全按照自己的想法控制舍入,BigDecimal本身提供了几种舍入模式,选择一种使用十分方便。此外在数字大小超过18位时,也必须使用BigDecimal

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值