在大多数行业涉及到浮点数的计算的场景比较少,但是在金融、支付行业就比较多了,而且在这两个行业一个小小的错误
可能将会给公司带来极大的损失。
以前我们公司就出现了这样的一个问题,当时使用的是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 的运算方式 不支持 + - * / 这类的运算 它有自己的运算方法:BigDecimal 一共有4个构造方法 BigDecimal(int) 创建一个具有参数所指定整数值的对象。 BigDecimal(double) 创建一个具有参数所指定双精度值的对象。 BigDecimal(long) 创建一个具有参数所指定长整数值的对象。 BigDecimal(String) 创建一个具有参数所指定以字符串表示的数值的对象。
我们现在来看以第一个float精度错误的例子,用BigDecimal计算:BigDecimal add(BigDecimal augend) 加法运算 BigDecimal subtract(BigDecimal subtrahend) 减法运算 BigDecimal multiply(BigDecimal multiplicand) 乘法运算 BigDecimal divide(BigDecimal divisor) 除法运算
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计算:
计算的精确度,尤其是像处理金额,建议大家还是使用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低很多,但是为了提高
不能假定浮点计算一定产生整型或精确的结果,虽然它们的确“应该”那样做。最好将浮点运算保留用作计算本来就不精确的数值,譬如结束语:
结束语是引用IBM文档库里面的一段话:"在Java程序中使用浮点数和小数充满着陷阱。浮点数和小数不象整数一样“循规蹈矩”,
测量。如果需要表示定点数(譬如,几美元和几美分),则使用 BigDecimal 。"。
注:本文部分内容参考 IBM developerWorks中国的文档库