一、引言
   借用《Effactive
Java》这本书中的话,float和double类型的主要设计目标是为了科学计算和工程计算。他们执行二进制浮点运算,这是为了在广域数值范围上提供
较为精确的快速近似计算而精心设计的。然而,它们没有提供完全精确的结果,所以不应该被用于要求精确结果的场合。但是,货币计算往往要求结果精确,这时候
可以使用int、long或BigDecimal。本文主要讲述BigDecimal使用过程中的一些陷阱、建议和技巧。

二、不可变性
BigDecimal是不可变类,每一个操作(加减乘除等)都会返回一个新的对象, 下面以加法操作为例。
   BigDecimal a =new BigDecimal("1.22");
   System.out.println("construct with a String value: " + a);
   BigDecimal b =new BigDecimal("2.22");
   a.add(b);
   System.out.println("a plus b is : " + a);

我们很容易会认为会输出:
  construct with a String value: 1.22
  a plus b is :3.44

但实际上a plus b is : 1.22

下面我们就来分析一下加法操作的源码

public BigDecimal add(BigDecimal augend) {
long xs =this.intCompact; //整型数字表示的BigDecimal,例a的intCompact值为122
long ys = augend.intCompact;//同上
//初始化BigInteger的值,intVal为BigDecimal的一个BigInteger类型的属性
BigInteger fst = (this.intCompact !=INFLATED) ?null :this.intVal;
BigInteger snd =(augend.intCompact !=INFLATED) ?null : augend.intVal;
int rscale =this.scale;//小数位数
long sdiff = (long)rscale - augend.scale;//小数位数之差
if (sdiff != 0) {//取小数位数多的为结果的小数位数
if (sdiff < 0) {
int raise =checkScale(-sdiff);
rscale =augend.scale;
if (xs ==INFLATED ||(xs = longMultiplyPowerTen(xs,raise)) ==INFLATED)
fst =bigMultiplyPowerTen(raise);
}else {
int raise =augend.checkScale(sdiff);
if (ys ==INFLATED ||(ys =longMultiplyPowerTen(ys,raise)) ==INFLATED)
snd = augend.bigMultiplyPowerTen(raise);
}
}
if (xs !=INFLATED && ys !=INFLATED) {
long sum = xs + ys;
if ( (((sum ^ xs) &(sum ^ ys))) >= 0L)//判断有无溢出
//返回使用BigDecimal的静态工厂方法得到的BigDecimal实例
return BigDecimal.valueOf(sum,rscale);
}
if (fst ==null)
fst =BigInteger.valueOf(xs);//BigInteger的静态工厂方法
if (snd ==null)
snd =BigInteger.valueOf(ys);
BigInteger sum =fst.add(snd);
//返回通过其他构造方法得到的BigDecimal对象
return (fst.signum == snd.signum) ?new BigDecimal(sum,INFLATED, rscale, 0) :
new BigDecimal(sum,compactValFor(sum),rscale, 0);
}

因为BigInteger与BigDecimal都是不可变的(immutable)的,在进行每一步运算时,都会产生一个新的对象,所以 a.add(b)虽然做了加法操作,但是a并没有保存加操作后的值,正确的用法应该是a=a.add(b); 减乘除操作也是一样的返回一个新的BigDecimal对象。

三、构造函数和valueOf方法
首先看如下一段代码:

// use constructor BigDecimal(double) 
 BigDecimal aDouble =new BigDecimal(1.22);
 System.out.println("construct with a double value: " + aDouble);

// use constructor BigDecimal(String)
 BigDecimal aString = new BigDecimal("1.22");
 System.out.println("construct with a String value: " + aString);

// use constructor BigDecimal.valueOf(double)
 BigDecimal aValue = BigDecimal.valueOf(1.22);
 System.out.println("use valueOf method: " + aValue);

你认为输出结果会是什么呢?如果你认为第一个会输出1.22,那么恭喜你答错了,输出结果如下:
construct with a double value: 1.2199999999999999733546474089962430298328399658203125
construct with a String value: 1.22
use valueOf method: 1.22

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

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

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

?? BigDecimal.valueOf(double) 使用由
Double.toString(double)方法提供的 double的标准化字符串表示形式( canonical string
representation) 将 double 转换成 BigDecimal 。这也是比较推荐的一种方式。
??
BigDecimal.valueOf(double)还有一个重载的方法
BigDecimal.valueOf(long),对于某些常用值(0到10) BigDecimal在内部做了缓存, 如果传递的参数值范围为[0,
10], 这个方法直接返回缓存中相应的BigDecimal对象。

java源码如下:

/**
* Translates a {@code long} value into a {@code BigDecimal}
* with a scale of zero. This {@literal "static factory method"}
* is provided in preference to a ({@code long}) constructor
* because it allows for reuse of frequently used
* {@code BigDecimal} values.
*
* @param val value of the {@code BigDecimal}.
* @return a {@code BigDecimal} whose value is {@code val}.
*/
public static BigDecimal valueOf(long val) {
if (val >= 0 && val < zeroThroughTen.length)
return zeroThroughTen[(int)val];
else if (val != INFLATED)
return new BigDecimal(null, val, 0, 0);
return new BigDecimal(INFLATED_BIGINT, val, 0, 0);
}
// Cache of common small BigDecimal values.
private static final BigDecimal zeroThroughTen[] = {
new BigDecimal(BigInteger.ZERO, 0, 0, 1),
new BigDecimal(BigInteger.ONE, 1, 0, 1),
new BigDecimal(BigInteger.valueOf(2), 2, 0, 1),
new BigDecimal(BigInteger.valueOf(3), 3, 0, 1),
new BigDecimal(BigInteger.valueOf(4), 4, 0, 1),
new BigDecimal(BigInteger.valueOf(5), 5, 0, 1),
new BigDecimal(BigInteger.valueOf(6), 6, 0, 1),
new BigDecimal(BigInteger.valueOf(7), 7, 0, 1),
new BigDecimal(BigInteger.valueOf(8), 8, 0, 1),
new BigDecimal(BigInteger.valueOf(9), 9, 0, 1),
new BigDecimal(BigInteger.TEN, 10, 0, 2),
};

附上相应的测试代码:
 BigDecimal a1 = BigDecimal.valueOf(10);
  BigDecimal a2 = BigDecimal.valueOf(10);
  System.out.println(a1 == a2); // true

BigDecimal a3 = BigDecimal.valueOf(11);
  BigDecimal a4 = BigDecimal.valueOf(11);
  System.out.println(a3 == a4); // false

四、equals方法
  BigDecimal.equals方法是有问题的.仅当你确定比较的值有着相同的标度时才可使用. 因此,当你校验相等性时注意 - BigDecimal有一个标度,用于相等性比较. 而compareTo方法则会忽略这个标度(scale).

BigDecimal的equals方法源码如下:

@Override
public boolean equals(Object x) {
// 必须是BigDecimal实例
if (!(x instanceof BigDecimal))
return false;
BigDecimal xDec = (BigDecimal) x;
if (x == this)
return true;
// 标度必须相同
if (scale != xDec.scale)
return false;
long s = this.intCompact;
long xs = xDec.intCompact;
if (s != INFLATED) {
if (xs == INFLATED)
xs = compactValFor(xDec.intVal);
return xs == s;
} else if (xs != INFLATED)
return xs == compactValFor(this.intVal);
return this.inflated().equals(xDec.inflated());
}

参见以下测试代码:
// 打印false
 System.out.println(new BigDecimal("0.0").equals(new BigDecimal("0.00")));

// 打印false
System.out.println(new BigDecimal("0.0").hashCode() == (new BigDecimal("0.00")).hashCode());

// 打印0
System.out.println(new BigDecimal("0.0").compareTo(new BigDecimal("0.00")));

五、对除法使用标度
  BigDecimal对象的精度没有限制。如果结果不能终止,divide方法将会抛出ArithmeticException, 如1 / 3
= 0.33333...。所以强烈推荐使用重载方法divide(BigDecimal d, int scale, int
roundMode)指定标度和舍入模式来避免以上异常。
参见以下测试代码:

//java.lang.ArithmeticException: Non-terminating decimal expansion;
  //no exact representable decimal result.
  try {

   BigDecimal.valueOf(1).divide(BigDecimal.valueOf(3));

  } catch (ArithmeticException ex) {
    System.out.println(ex.getMessage());
  }

// always use a scale and the rounding mode of your choice
// 0.33
System.out.println(BigDecimal.valueOf(1).divide(BigDecimal.valueOf(3), 2, BigDecimal.ROUND_HALF_UP));

六、总结
(1)商业计算使用BigDecimal。

(2)使用参数类型为String的构造函数,将double转换成BigDecimal时用BigDecimal.valueOf(double),做
除法运算时使用重载的方法divide(BigDecimal d, int scale, int roundMode)。

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

(4)尽量使用compareTo方法比较两个BigDecimal对象的大小。

再来看看数字格式化输出的概念:

有时候我需要将数字按照本地的风格习惯进行数字的显示,可以用NumberFormat,

此类的定义如下:

public abstract class NumberFormat extends Format

MessageFormat 、DateFormat 、NumberFormat 是 Format 三个常用的子类,如果要想进一步完成一个好的国际化程序,则肯定需要同时使用这样三个类完成,根据不同的国家显示贷币的形式。

此类还是在java.text 包中,所以直接导入此包即可。

import java.text.* ;
public class NumberFormatDemo01{
public static void main(String args[]){
NumberFormat nf = null ; // 声明一个NumberFormat对象
nf = NumberFormat.getInstance() ; // 得到默认的数字格式化显示
System.out.println("格式化之后的数字:" + nf.format(10000000)) ;
System.out.println("格式化之后的数字:" + nf.format(1000.345)) ;
}
};

在美国,"."是小数点,但在其它地方就不一定了。如何处理这个呢? java.text 包中的一些包可以处理这类问题。下面的简单范例使用那些类解决上面提出的问题:

import java.text.NumberFormat;

import java.util.Locale;

public class DecimalFormat1 {

  public static void main(String args[]) {

     // 得到本地的缺省格式

       NumberFormat nf1 = NumberFormat.getInstance();

      System.out.println(nf1.format(1234.56));

     // 得到德国的格式

       NumberFormat nf2 =  NumberFormat.getInstance(Locale.GERMAN);

       System.out.println(nf2.format(1234.56));

   }     }

如果你在美国,运行程序后输出:     1,234.56     1.234,56

换句话说,在不同的地方使用不同的习惯表示数字。 NumberFormat.getInstance() 方法返回NumberFormat的一个实例(实际上是NumberFormat具体的一个子类,例如DecimalFormat), 这适合根据本地设置格式化一个数字。你也可以使用非缺省的地区设置,例如德国。然后格式化方法根据特定的地区规则格式化数字。这个程序也可以使用一个简单 的形式:     NumberFormat.getInstance().format(1234.56) 但是保存一个格式然后重用更加有效。国际化是格式化数字时的一个大问题。 另一个是对格式的有效控制,例如指定小数部分的位数,下面是解决这个问题的一个简单例子:

import java.text.DecimalFormat;

import java.util.Locale;

    public class DecimalFormat2 {

      public static void main(String args[]) {

         // 得到本地的缺省格式

        DecimalFormat df1 = new DecimalFormat("####.000");

        System.out.println(df1.format(1234.56));

        // 得到德国的格式

        Locale.setDefault(Locale.GERMAN);

        DecimalFormat df2 = new DecimalFormat("####.000");

        System.out.println(df2.format(1234.56));

  }

}

在这个例子中设置了数字的格式,使用像"####.000"的符号。这个模式意味着在小数点前有四个数字,如果不够就空着,小数点后有三位数字,不足用0 补齐。

程序的输出:     1234.560     1234,560

相似的,也可以控制指数形式的格式,例如:

import java.text.DecimalFormat;

  public class DecimalFormat3 {

    public static void main(String args[]) {

      DecimalFormat df = new DecimalFormat("0.000E0000");

      System.out.println(df.format(1234.56));

  }

}

输出:     1.235E0003

对于百分数:

import java.text.NumberFormat;

  public class DecimalFormat4 {

    public static void main(String args[]) {

      NumberFormat nf = NumberFormat.getPercentInstance();

      System.out.println(nf.format(0.47));

  }

}

输出:     47%

至此,你已经看到了格式化数字的几个不同的技术。

使用BigDecimal进行精确运算以及格式化输出数字的更多相关文章

  1. BigDecimal - Java精确运算

    (1).浮点数精确计算 项目中一直存在一个问题,就是每次报表统计的物资金额和实际的金额要差那么几分钱,和实际金额不一致,让客户觉得总是不那么舒服,原因是因为我们使用java的浮点类型double来定义 ...

  2. BigDecimal进行精确运算demo工具类

    package com.js.ai.modules.pointwall.interfac; import java.math.BigDecimal; public class TestDigDecim ...

  3. 使用BigDecimal进行精确运算

    首先我们先来看如下代码示例: 1 public class Test_1 { 2 public static void main(String[] args) { 3 System.out.print ...

  4. 使用Java BigDecimal进行精确运算

    首先我们先来看如下代码示例: public class Test_1 {     public static void main(String[] args) {         System.out ...

  5. (转)使用BigDecimal进行精确运算

    场景:在进行支付业务的金额计算时,通常采用BigDecimal类型的数据,并没有看到常见的int double类型,所以有必要好好学习下BigDecimal的常用用法. 1 误区 首先我们先来看如下代 ...

  6. 电商网站中价格的精确计算(使用BigDecimal进行精确运算(实现加减乘除运算))

    使用BigDecimal的String的构造器.商业计算中,使用bigdecimal的String构造器,一定要用. 重要的事情说三遍: 商业计算中,使用bigdecimal的String构造器! 商 ...

  7. BigDecimal进行精确运算

    public class Test_1 { public static void main(String[] args) { System.out.println(0.06+0.01); System ...

  8. Shell 格式化输出数字、字符串(printf)

    1.语法 printf打印格式字符串,解释'%'指令和'\'转义. 1.1.转义 printf使用时需要指定输出格式,输出后不换行. printf FORMAT [ARGUMENT] printf O ...

  9. while 循环,格式化输出和运算编码

    今日内容 1.while循环 while Ture:             content = input ("请输入你要喷的内容",输入Q退出)             if ...

随机推荐

  1. Tomcat SSL的安装及配置中遇到问题

    配置tomcat服务器利用SSL进行加密. 一.生成密钥库 具体生成方式就不讲了,tomcat支持的keystore的格式有JKS,PKCS11和PKCS12 JKS是jdk /bin目录下keyto ...

  2. 微信5.4安卓版重回ios风格 导航菜单都放底栏位置

    微信5.4安卓版发布更新了,由于本人的手机设置软件自动更新,中午的时候才发现微信换成了5.4版本,启动微信后是一个大大的“转账,就是发消息”,进入微信界面有点小惊喜,导航菜单都改为底部tab方式,顶部 ...

  3. java基本知识小记(1)

    1.Java中的值传递 值传递意味着对于传给方法的每个参数都会制作一份副本然后将副本而不是原始值传递给方法并且通过参数的名进行引用. 注意:虽然传值机制对于所有参数类型都适用,但是他对对象类型的作用与 ...

  4. 在Linux上挂载Windows共享文件夹,如何开机自动挂载(mount)?

    按照一般的思路,我们先将文件夹挂载上去,命令如下: mkdir /mnt/share_software mount //192.9.206.43/share_software /mnt/share_s ...

  5. java基础知识(二)字符串处理

    字符串是程序开发中使用最为频繁,因此为了工作的高效和作为一名想进阶的程序员,了解并掌握字符串的处理显得尤为重要.java为我们提供了String.StringBuffer.StringBuilde三个 ...

  6. padding

    padding-top:20px;上内边距 padding-right:30px;右内边距 padding-bottom:30px;下内边距 padding-left:20px;左内边距 paddin ...

  7. ubuntu下建立NFS共享,并用开发板挂载

    安装NFS服务 apt-get install nfs-kernel-server nfs-common apt-get install portmap 在/etc/exports里加入 /home/ ...

  8. Python转码问题的解决方法:UnicodeDecodeError:‘gbk' codec can't decode bytes in position

    在开发过程中遇到了错误:UnicodeDecodeError: ‘gbk' codec can't decode bytes in position 678-679...这是因为遇到了非法字符, 解决 ...

  9. JDK source 之 ArrayList 需要注意事项

    线程安全 ArrayList内部没有实现原子性操作,所以是非线程安全的.如果需要在线程安全的环境下使用List的话,需要使用Vector 或者CopyOnWriteArrayList,具体场景,自行深 ...

  10. [BZOJ4408][Fjoi 2016]神秘数

    [BZOJ4408][Fjoi 2016]神秘数 试题描述 一个可重复数字集合S的神秘数定义为最小的不能被S的子集的和表示的正整数.例如S={1,1,1,4,13},1 = 12 = 1+13 = 1 ...