BigDecimal 保住数据精度

来源:互联网 发布:微传播软件 编辑:程序博客网 时间:2024/04/28 09:49
双精度浮点型变量double可以处理16位有效数。在实际应用中,需要对更大或者更小的数进行运算和处理。Java在java.math包中提供的类BigDecimal,用来对超过16位有效位的数进行精确的运算。下面列出了BigDecimal类的主要构造器和方法。

序号

   

类型

   

1

public BigDecimal(double val)

构造

double表示形式转换

BigDecimal

2

public BigDecimal(int val)

构造

int表示形式转换为

BigDecimal

3

public BigDecimal(String val)

构造

将字符串表示

形式转换为BigDecimal

4

public BigDecimal add(BigDecimal augend)

普通

加法

5

public BigDecimal subtract(BigDecimal
subtrahend)

普通

减法

6

public BigDecimal multiply(BigDecimal
multiplicand)

普通

乘法

7

public BigDecimal divide(BigDecimal
divisor)

普通

除法

你肯会问,为什么要传一个String呢?
其实,如果你要表示一个小数点后有30位的数字,用DOUBLE?那么传进去之前已经丢失了精度,之后的操作就没有意义了,那么把数字以String的方式传进去能有效地保存精度。


BigDecimal API文档中对于BigDecimal(double)有这么一段话:

Note: the results of this constructor can be somewhat unpredictable. One might assume that new BigDecimal(.1) is exactly equal to .1, but it is actually equal to .10000000000000000555111512312578 27021181583404541015625. This is so because .1 cannot be represented exactly as a double (or, for that matter, as a binary fraction of any finite length). Thus, the long value that is being passed in to the constructor is not exactly equal to .1, appearances notwithstanding.

The (String) constructor, on the other hand, is perfectly predictable: new BigDecimal(".1") is exactly equal to .1, as one would expect. Therefore, it is generally recommended that the (String) constructor be used in preference to this one

下面对这段话做简单解释:

注意:这个构造器的结果可能会有不可预知的结果。有人可能设想new BigDecimal(.1)等于.1是正确的,但它实际上是等于.1000000000000000055511151231257827021181583404541015625,这就是为什么.1不能用一个double精确表示的原因,因此,这个被放进构造器中的长值并不精确的等于.1,尽管外观看起来是相等的。

然而(String)构造器,则完全可预知的,new BigDecimal(“.1”)如同期望的那样精确的等于.1,因此,(String)构造器是被优先推荐使用的。


看下面的结果:

      System.out.println(new BigDecimal(123456789.02).toString());

      System.out.println(new BigDecimal("123456789.02").toString());

输出为:

123456789.01999999582767486572265625

123456789.02

现在我们知道,如果需要精确计算,非要用String来够造BigDecimal不可!

      对BigDecimal所创建的是对象,我们不能使用传统的+、-、*、/等算术运算符直接对其对象进行数学运算,而必须调用其相对应的方法。方法中的参数也必须是BigDecimal的对象。
Code

      注意:使用方法doubleValue()将对象bigNumber中的值以双精度数值返回时,将丢失数据的准确性。使用其他方法,如xxxValue()时均存在这个问题,使用时必须慎重。
四舍五入得正确方法:

网上有代码如下,看似正确但结合上面的分析你会发现有很大的漏洞,传入的参数为double类型,精度已经丢失再请bigdecimal出来就没有意义了。

错误的代码:

public static Double round(Double doubleValue, int scale){   
        Double flag
=null;   
        String text
=doubleValue.toString();   
        BigDecimal bd
=new BigDecimal(text).setScale(scale, BigDecimal.ROUND_HALF_UP);   
        flag
=bd.doubleValue();   
        
return flag;   

正确的代码:

 

public static String round(String doubleValue, int scale) {

        BigDecimal bd 
= new BigDecimal(doubleValue).setScale(scale,BigDecimal.ROUND_HALF_UP);

        
return bd.toString();
}

 

 

当然你可能会说以下方法也行:

public double round(double value){
      return Math.round( value * 100 ) / 100.0;
}

 

但你试试给这个方法传入4.015它将返回4.01而不是4.02,问题就在于value*100了,

System.out.println(4.015 * 100);结果是401.49999999999994,这里误差就出现了。

 

附:Math.round()的工作原理:

返回最接近参数的 long。结果将舍入为整数:加上 1/2,对结果调用 floor 并将所得结果强制转换为 long 类型。换句话说,结果等于以下表达式的值: (long)Math.floor(a + 0.5d)

 

Java中浮点数(double、float)的计算是非精确计算,请看下面一个例子:

    System.out.println(0.05 + 0.01);

    System.out.println(1.0 - 0.42);

    System.out.println(4.015 * 100);

    System.out.println(123.3 / 100);

你的期望输出是什么?可实际的输出确实这样的:

0.060000000000000005

0.5800000000000001

401.49999999999994

1.2329999999999999

这个问题就非常严重了,如果你有123.3元要购买商品,而计算机却认为你只有123.29999999999999元,钱不够,计算机拒绝交易。

还有一种方式是使用java.text.DecimalFormat,但也存在问题,format采用的舍入模式是RoundingMode.HALF_EVEN(舍入模式在下面有介绍),比如说4.025保留两位小数会是4.02,因为.025距离” nearest neighbor”(.02和.03)长度是相等,向下舍入就是.02,如果是4.0251那么保留两位小数就是4.03。

System.out.println(new java.text.DecimalFormat("0.00").format(4.025));

System.out.println(new java.text.DecimalFormat("0.00").format(4.0251));

输出是

4.02

4.03

 

最后说说两个BigDecimal进行大小比较:

用“==”肯定是不行的,那么观看API就只有equals和compareTo了,用那个你懂的,呵呵!!

BigDecimal 舍入模式(Rounding mode)介绍:

 

BigDecimal定义了一下舍入模式,只有在作除法运算或四舍五入时才用到舍入模式,下面简单介绍,详细请查阅J2se API文档

static int   ROUND_CEILING       Rounding mode to round towards positive infinity.

向正无穷方向舍入
 
static int   ROUND_DOWN           Rounding mode to round towards zero.

向零方向舍入
 
static int    ROUND_FLOOR           Rounding mode to round towards negative infinity.

向负无穷方向舍入
 
static int    ROUND_HALF_DOWN          
Rounding mode to round towards "nearest neighbor" unless both neighbors are equidistant, in which case round down.

向(距离)最近的一边舍入,除非两边(的距离)是相等,如果是这样,向下舍入, 例如1.55 保留一位小数结果为1.5
 
static int    ROUND_HALF_EVEN

Rounding mode to round towards the "nearest neighbor" unless both neighbors are equidistant, in which case, round towards the even neighbor.

向(距离)最近的一边舍入,除非两边(的距离)是相等,如果是这样,如果保留位数是奇数,使用ROUND_HALF_UP ,如果是偶数,使用ROUND_HALF_DOWN
 
static int     ROUND_HALF_UP

Rounding mode to round towards "nearest neighbor" unless both neighbors are equidistant, in which case round up.

向(距离)最近的一边舍入,除非两边(的距离)是相等,如果是这样,向上舍入, 1.55保留一位小数结果为1.6
 
static int     ROUND_UNNECESSARY

 Rounding mode to assert that the requested operation has an exact result, hence no rounding is necessary.

计算结果是精确的,不需要舍入模式
 
static int     ROUND_UP

Rounding mode to round away from zero.

向远离0的方向舍入
0 0
原创粉丝点击