关键词搜索

源码搜索 ×
×

Java 使用BigDecimal实现精确计算

发布2016-11-28浏览2350次

详情内容

前言:程序中很多业务都会涉及到双精度Double数的计算,在计算过程中有很多问题是潜在的陷阱,使得程序不会按照我们预想的那样能够精确输出。

1.错误示例

先来看一个测试例子:

  1. package com.boonya.program.lang;
  2. import java.math.BigDecimal;
  3. import org.junit.Test;
  4. public class BigDecimalTest {
  5. private static double a = 2.00;
  6. private static double b = 1.10;
  7. @Test
  8. public void testSubstract0(){
  9. System.out.println("method=testSubstract0>:"+(a - b));// 输出0.8999999999999999
  10. }
  11. @Test
  12. public void testSubstract1(){
  13. BigDecimal a1=new BigDecimal(a);
  14. BigDecimal b1=new BigDecimal(b);
  15. double result=a1.subtract(b1).doubleValue();
  16. System.out.println("method=testSubstract1>:"+result);// 输出0.8999999999999999
  17. }
  18. @Test
  19. public void testSubstract2(){
  20. BigDecimal a1=new BigDecimal(Double.valueOf(a));
  21. BigDecimal b1=new BigDecimal(Double.valueOf(b));
  22. double result=a1.subtract(b1).doubleValue();
  23. System.out.println("method=testSubstract2>:"+result);// 输出0.8999999999999999
  24. }
  25. @Test
  26. public void testSubstract3(){
  27. BigDecimal a1=new BigDecimal(Double.toString(a));
  28. BigDecimal b1=new BigDecimal(Double.toString(b));
  29. double result=a1.subtract(b1).doubleValue();
  30. System.out.println("method=testSubstract3>:"+result);// 输出0.9
  31. }
  32. }

从上面可以看出,testSubstract3 方法输出的结果才是我们所预期的。

2.Double计算原理

《Effactive Java》书中说,float和double类型的主要设计目标是为了科学计算和工程计算。他们执行二进制浮点运算,这是为了在广域数值范围上提供较为精确的快速近似计算而精心设计的。然而,它们没有提供完全精确的结果,所以不应该被用于要求精确结果的场合。但是,商业计算往往要求结果精确,这时候BigDecimal就派上大用场啦。

在JDK的描述中:

1、参数类型为double的构造方法的结果有一定的不可预知性。有人可能认为在Java中写入newBigDecimal(0.1)所创建的BigDecimal正好等于 0.1(非标度值 1,其标度为 1),但是它实际上等于0.1000000000000000055511151231257827021181583404541015625。这是因为0.1无法准确地表示为 double(或者说对于该情况,不能表示为任何有限长度的二进制小数)。这样,传入到构造方法的值不会正好等于 0.1(虽然表面上等于该值)。

2、另一方面,String 构造方法是完全可预知的:写入 newBigDecimal("0.1") 将创建一个 BigDecimal,它正好等于预期的 0.1。因此,比较而言,通常建议优先使用String构造方法

3、当double必须用作BigDecimal的源时,请注意,此构造方法提供了一个准确转换;它不提供与以下操作相同的结果:先使用Double.toString(double)方法,然后使用BigDecimal(String)构造方法,将double转换为String。要获取该结果,请使用static valueOf(double)方法。

3.BigDecimal计算工具

此工具类参考过网上的例子,但是其大部分都是错误的,其人使用的是:

BigDecimal b1 = new BigDecimal(Double.valueOf(value1));

在实际的计算中是错误的。下面是工具类的代码:

  1. package com.boonya.program.lang;
  2. import java.math.BigDecimal;
  3. import java.math.BigInteger;
  4. /**
  5. * BigDecimal精确计算:参考http://docs.oracle.com/javase/7/docs/api/java/math/BigDecimal.html
  6. *
  7. * <li>在大多数的商业计算中,一般采用java.math.BigDecimal类来进行精确计算。</li>
  8. * @package com.boonya.program.lang.BBigDecimal
  9. * @date 2016年11月28日 上午11:33:44
  10. * @author pengjunlin
  11. * @comment
  12. * @update
  13. */
  14. public class BBigDecimal extends BigDecimal{
  15. /**
  16. *
  17. */
  18. private static final long serialVersionUID = -8187328246570289879L;
  19. public BBigDecimal(BigInteger val) {
  20. super(val);
  21. }
  22. /**
  23. * 提供精确加法计算的add方法
  24. *
  25. * @param value1
  26. * 被加数
  27. * @param value2
  28. * 加数
  29. * @return 两个参数的和
  30. */
  31. public static double add(double value1, double value2) {
  32. BigDecimal a = new BigDecimal(Double.toString(value1));
  33. BigDecimal b = new BigDecimal(Double.toString(value2));
  34. return a.add(b).doubleValue();
  35. }
  36. /**
  37. * 提供精确加法计算的add方法
  38. *
  39. * @param value1
  40. * 被加数
  41. * @param value2
  42. * 加数
  43. * @param scale
  44. * 精确范围
  45. * @return 两个参数的和
  46. * @throws IllegalAccessException
  47. */
  48. public static double add(double value1, double value2,int scale) throws IllegalAccessException {
  49. BigDecimal a = new BigDecimal(Double.toString(value1));
  50. BigDecimal b = new BigDecimal(Double.toString(value2));
  51. // 如果精确范围小于0,抛出异常信息
  52. if(scale<0){
  53. throw new IllegalAccessException("精确度不能小于0");
  54. }
  55. a=a.setScale(scale);//必须重新赋值
  56. b=b.setScale(scale);//必须重新赋值
  57. return a.add(b).setScale(scale).doubleValue();
  58. }
  59. /**
  60. * 提供精确减法运算的subtract方法
  61. *
  62. * @param value1
  63. * 被减数
  64. * @param value2
  65. * 减数
  66. * @return 两个参数的差
  67. */
  68. public static double subtract(double value1, double value2) {
  69. BigDecimal a = new BigDecimal(Double.toString(value1));
  70. BigDecimal b = new BigDecimal(Double.toString(value2));
  71. return a.subtract(b).doubleValue();
  72. }
  73. /**
  74. * 提供精确减法运算的subtract方法
  75. *
  76. * @param value1
  77. * 被减数
  78. * @param value2
  79. * 减数
  80. * @param scale
  81. * 精确范围
  82. * @return 两个参数的差
  83. * @throws IllegalAccessException
  84. */
  85. public static double subtract(double value1, double value2,int scale) throws IllegalAccessException {
  86. BigDecimal a = new BigDecimal(Double.toString(value1));
  87. BigDecimal b = new BigDecimal(Double.toString(value2));
  88. // 如果精确范围小于0,抛出异常信息
  89. if(scale<0){
  90. throw new IllegalAccessException("精确度不能小于0");
  91. }
  92. a=a.setScale(scale);//必须重新赋值
  93. b=b.setScale(scale);//必须重新赋值
  94. return a.subtract(b).setScale(scale).doubleValue();
  95. }
  96. /**
  97. * 提供精确乘法运算的multiply方法
  98. *
  99. * @param value1
  100. * 被乘数
  101. * @param value2
  102. * 乘数
  103. * @return 两个参数的积
  104. */
  105. public static double multiply(double value1, double value2) {
  106. BigDecimal a = new BigDecimal(Double.toString(value1));
  107. BigDecimal b = new BigDecimal(Double.toString(value2));
  108. return a.multiply(b).doubleValue();
  109. }
  110. /**
  111. * 提供精确乘法运算的multiply方法
  112. *
  113. * @param value1
  114. * 被乘数
  115. * @param value2
  116. * 乘数
  117. * @param scale
  118. * 精确范围
  119. * @return 两个参数的积
  120. * @throws IllegalAccessException
  121. */
  122. public static double multiply(double value1, double value2,int scale) throws IllegalAccessException {
  123. BigDecimal a = new BigDecimal(Double.toString(value1));
  124. BigDecimal b = new BigDecimal(Double.toString(value2));
  125. // 如果精确范围小于0,抛出异常信息
  126. if(scale<0){
  127. throw new IllegalAccessException("精确度不能小于0");
  128. }
  129. a=a.setScale(scale);//必须重新赋值
  130. b=b.setScale(scale);//必须重新赋值
  131. return a.multiply(b).setScale(scale).doubleValue();
  132. }
  133. /**
  134. * 提供精确的除法运算方法divide
  135. *
  136. * @param value1
  137. * 被除数
  138. * @param value2
  139. * 除数
  140. * @param scale
  141. * 精确范围
  142. * @return 两个参数的商
  143. * @throws IllegalAccessException
  144. */
  145. public static double divide(double value1, double value2, int scale)
  146. throws IllegalAccessException {
  147. BigDecimal a = new BigDecimal(Double.toString(value1));
  148. BigDecimal b = new BigDecimal(Double.toString(value2));
  149. // 如果精确范围小于0,抛出异常信息
  150. if (scale < 0) {
  151. throw new IllegalAccessException("精确度不能小于0");
  152. }
  153. a=a.setScale(scale);//必须重新赋值
  154. b=b.setScale(scale);//必须重新赋值
  155. return a.divide(b,scale).doubleValue();
  156. }
  157. }

4.总结

        (1)商业数据计算使用BigDecimal。

        (2)尽量使用参数类型为String的构造函数。

        (3) BigDecimal都是不可变的(immutable)的,在进行每一步运算时,都会产生一个新的对象,所以在做加减乘除运算时千万要保存操作后的值。

        (4)我们往往容易忽略JDK底层的一些实现细节,导致出现错误,需要多加注意。

参考博客地址: http://blog.csdn.net/jackiehff/article/details/8582449

相关技术文章

点击QQ咨询
开通会员
返回顶部
×
微信扫码支付
微信扫码支付
确定支付下载
请使用微信描二维码支付
×

提示信息

×

选择支付方式

  • 微信支付
  • 支付宝付款
确定支付下载