金融、支付行业的开发者不得不知道的float、double计算误差问题

在大多数行业涉及到浮点数的计算的场景比较少,但是在金融、支付行业就比较多了,而且在这两个行业一个小小的错误

可能将会给公司带来极大的损失。


以前我们公司就出现了这样的一个问题,当时使用的是double类型进行计算,导致计算出来的结果与实际的结果少了几十

元钱。虽然数额不大,但是引起产品、技术的重视。通过查阅相关资料,终于知道了是因为float、double在对含有小数的数值进行

计算过程中的舍入产生了误差。


在浮点运算中,浮点运算很少是精确的。虽然一些数字(譬如 0.5 )可以精确地表示为二进制(底数 2)小数(因为 0.5 

等于 2 -1),但其它一些数字(譬如 0.1 )就不能精确的表示。因此,浮点运算可能导致舍入误差,产生的结果接近但不等于你

可能希望的结果。比如:下面的代码运行结果的实际值为100958.34,而不是100000。大家可以运行试一下。

public static void main(String[] args) {
		float f = 0.1f;
		float sum = 0;
		for( int i=0; i<1000000; i++)
		{
		    sum += f;
		}
		System.out.println(sum);
	}
类似的,.1*26相乘所产生的结果不等于.1自身加26次所得到的结果。当将浮点数强制转换成整数时,产生的舍入误差甚至

更严重,因为强制转换成整数类型会舍弃非整数部分,甚至对于那些“看上去似乎”应该得到整数值的计算,也存在此类问题。例如

下面这段代码:

public static void main(String[] args) {
		double d = 29.0 * 0.01;
		System.out.println(d);
		System.out.println((int) (d * 100));
	}

运行结果:

0.29
28

看到结果是不是非常差异,所以建议大家不要用float、double等浮点值表示精确值一些非整数值(如几美元和几美分这样的

小数)需要很精确。浮点数不是精确值,所以使用它们会导致舍入误差。因此,使用浮点数来试图表示象货币量这样的精确数量不是

一个好的想法。使用浮点数来进行美元和美分计算会得到灾难性的后果。浮点数最好用来表示像测量值这类数值,这类值从一开始就

不怎么精确。


那我们怎样来解决这样的问题呢?


JDK开发人员在很早就遇到了这个问题,并在JDK1.3起给我们提供了一种新的处理精确值的类BigDecimal,BigDecimal是标

准的类,在编译器中不需要特殊支持,它可以表示任意精度的小数,并对它们进行计算。在内部,可以用任意精度任何范围的值和一个

换算因子来表示 BigDecimal,换算因子表示左移小数点多少位,从而得到所期望范围内的值BigDecimal 给我们提供了加、减、乘和除

等算术运算,由于BigDecimal是一个类,而且对象是不可变,不像float、double是基本变量,所以计算后的结果将放入一个新BigDec

imal对象中,所以使用BigDecimal会占用很大的开销,不适合大规模的数学计算。设计它的目的是用来精确地表示小数,所以我们可以

用它来表示货币和金额的计算。


BigDecimal 用法:

BigDecimal构造方法:


BigDecimal     一共有4个构造方法
BigDecimal(int)                 创建一个具有参数所指定整数值的对象。
BigDecimal(double)        创建一个具有参数所指定双精度值的对象。
BigDecimal(long)             创建一个具有参数所指定长整数值的对象。
BigDecimal(String)          创建一个具有参数所指定以字符串表示的数值的对象。

BigDecimal 的运算方式 不支持 + - * / 这类的运算 它有自己的运算方法:

BigDecimal add(BigDecimal augend) 加法运算
	BigDecimal subtract(BigDecimal subtrahend) 减法运算
	BigDecimal multiply(BigDecimal multiplicand) 乘法运算
	BigDecimal divide(BigDecimal divisor) 除法运算


 我们现在来看以第一个float精度错误的例子,用BigDecimal计算:

public static void main(String[] args) {
		float f = 0.1f;
		float sum = 0;
		for( int i=0; i<1000000; i++)
		{
		    sum += f;
		}
		System.out.println("float sum="+sum);
		BigDecimal b1 = new BigDecimal(Double.toString(0.01)); 
		BigDecimal total = new BigDecimal(Double.toString(0)); 
		for( int i=0; i<1000000; i++)
		{
			total=total.add(b1);
		}
		System.out.println("BigDecimal total="+total);
	}

结果:

float sum=100958.34
BigDecimal total=10000.00
我们从结果中可以看出使用float计算结果是有误差的,而使用BigDecimal计算结果是正确的。我们再看以第二个double精度

错误的例子,用BigDecimal计算:

public static void main(String[] args) {
		double d = 29.0 * 0.01;
		System.out.println(d);
		/***double计算**/
		System.out.println(d * 100);
		/***BigDecimal计算**/
		BigDecimal b1 = new BigDecimal(Double.toString(d));  
		BigDecimal b2 = new BigDecimal(Double.toString(100)); 
		System.out.println( b1.multiply(b2).doubleValue() );
	}

结果:

0.29
28.999999999999996
29.0

我们从结果中可以看出使用double计算结果是有误差的,而使用BigDecimal计算结果是正确的


从上面两个例子可以看出BigDecimal能够很好处理浮点数计算精度问题,但是性能方面比float、double低很多,但是为了提高

计算的精确度,尤其是像处理金额,建议大家还是使用BigDecimal进行计算,避免造成损失。

结束语:

结束语是引用IBM文档库里面的一段话:"在Java程序中使用浮点数和小数充满着陷阱。浮点数和小数不象整数一样“循规蹈矩”,

不能假定浮点计算一定产生整型或精确的结果,虽然它们的确“应该”那样做。最好将浮点运算保留用作计算本来就不精确的数值,譬如

测量。如果需要表示定点数(譬如,几美元和几美分),则使用 BigDecimal 。"。


注:本文部分内容参考 IBM developerWorks中国的文档库


  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值