在浮点运算中,浮点运算很少是精确的。虽然一些数字(譬如 0.5 )可以精确地表示为二进制(底数 2)小数(因为 0.5 等于 2 -1),但其它一些数字(譬如 0.1 )就不能精确的表示。因此,浮点运算可能导致舍入误差,产生的结果接近但不等于你可能希望的结果。比如:下面的代码运行结果的实际值为100958.34,而不是100000。
1
2
3
4
5
6
7
8
9public 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次所得到的结果。当将浮点数强制转换成整数时,产生的舍入误差甚至更严重,因为强制转换成整数类型会舍弃非整数部分,甚至对于那些“看上去似乎”应该得到整数值的计算,也存在此类问题。例如下面这段代码:
1
2
3
4
5
6public static void main(String[] args) { double d = 29.0 * 0.01; System.out.println(d); System.out.println((int) (d * 100)); }
运行结果:
1
2
30.29 28
看到结果是不是非常差异,所以建议大家不要用float、double等浮点值表示精确值一些非整数值(如几美元和几美分这样的小数)需要很精确。浮点数不是精确值,所以使用它们会导致舍入误差。因此,使用浮点数来试图表示象货币量这样的精确数量不是一个好的想法。使用浮点数来进行美元和美分计算会得到灾难性的后果。浮点数最好用来表示像测量值这类数值,这类值从一开始就不怎么精确。
解决方案:
JDK开发人员在很早就遇到了这个问题,并在JDK1.3起给我们提供了一种新的处理精确值的类BigDecimal,BigDecimal是标准的类,在编译器中不需要特殊支持,它可以表示任意精度的小数,并对它们进行计算。在内部,可以用任意精度任何范围的值和一个换算因子来表示 BigDecimal,换算因子表示左移小数点多少位,从而得到所期望范围内的值BigDecimal 给我们提供了加、减、乘和除等算术运算,由于BigDecimal是一个类,而且对象是不可变,不像float、double是基本变量,所以计算后的结果将放入一个新BigDecimal对象中,所以使用BigDecimal会占用很大的开销,不适合大规模的数学计算。设计它的目的是用来精确地表示小数,所以我们可以用它来表示货币和金额的计算。
BigDecimal 用法:
BigDecimal构造方法:
1
2
3
4
5
6
7
8
9
10BigDecimal 一共有4个构造方法 BigDecimal(int) 创建一个具有参数所指定整数值的对象。 BigDecimal(double) 创建一个具有参数所指定双精度值的对象。 BigDecimal(long) 创建一个具有参数所指定长整数值的对象。 BigDecimal(String) 创建一个具有参数所指定以字符串表示的数值的对象。
BigDecimal 的运算方式 不支持 + - * / 这类的运算 它有自己的运算方法:
1
2
3
4
5BigDecimal add(BigDecimal augend) 加法运算 BigDecimal subtract(BigDecimal subtrahend) 减法运算 BigDecimal multiply(BigDecimal multiplicand) 乘法运算 BigDecimal divide(BigDecimal divisor) 除法运算
以第一个float精度错误的例子,用BigDecimal计算:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public 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); }
结果:
1
2
3float sum=100958.34 BigDecimal total=10000.00
我们从结果中可以看出使用float计算结果是有误差的,而使用BigDecimal计算结果是正确的。我们再看以第二个double精度错误的例子,用BigDecimal计算:
1
2
3
4
5
6
7
8
9
10public 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() ); }
结果:
1
2
3
40.29 28.999999999999996 29.0
从上面两个例子可以看出BigDecimal能够很好处理浮点数计算精度问题,但是性能方面比float、double低很多,但是为了提高计算的精确度,尤其是像处理金额,建议大家还是使用BigDecimal进行计算,避免造成损失。
结束语:
结束语是引用IBM文档库里面的一段话:"在Java程序中使用浮点数和小数充满着陷阱。浮点数和小数不象整数一样“循规蹈矩”,不能假定浮点计算一定产生整型或精确的结果,虽然它们的确“应该”那样做。最好将浮点运算保留用作计算本来就不精确的数值,譬如测量。如果需要表示定点数(譬如,几美元和几美分),则使用 BigDecimal 。"。
转载于:https://my.oschina.net/u/3083178/blog/873783
最后
以上就是专注信封最近收集整理的关于金融、支付行业的开发者不得不知道的float、double计算误差问题解决方案:的全部内容,更多相关金融、支付行业内容请搜索靠谱客的其他文章。
发表评论 取消回复