更新时间:2016-03-17

一、引言

《Effactive Java》中有这样的描述:floatdouble类型的主要设计目标是为了科学计算和工程计算。他们执行二进制浮点运算,这是为了在广域数值范围上提供较为精确的快速近似计算而精心设计的。然而,它们没有提供完全精确的结果,所以不应该被用于要求精确结果的场合。但是,货币计算往往要求结果精确,这时候可以使用intlongBigDecimal

二、不可变性

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

因为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对象。

四、equals方法

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

参见以下测试代码:

// 打印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)指定标度和舍入模式来避免以上异常。

关于舍入模式常用的有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对象的大小。

Java BigDecimal初探的更多相关文章

  1. java bigDecimal and double

    Java BigDecimal和double   BigDecimal是Java中用来表示任意精确浮点数运算的类,在BigDecimal中,使用unscaledValue × 10-scale来表示一 ...

  2. Java BigDecimal类的使用和注意事项

    1.对于金额相关运算,若是精度较高,基本上用BigDecimal进行运算,精度要求低的话用Long.Double即可 2.web后台接受金额用String接受,展示到前端一般也转成 String 3. ...

  3. java并发初探ConcurrentSkipListMap

    java并发初探ConcurrentSkipListMap ConcurrentSkipListMap以调表这种数据结构以空间换时间获得效率,通过volatile和CAS操作保证线程安全,而且它保证了 ...

  4. java并发初探ConcurrentHashMap

    java并发初探ConcurrentHashMap Doug Lea在java并发上创造了不可磨灭的功劳,ConcurrentHashMap体现这位大师的非凡能力. 1.8中ConcurrentHas ...

  5. java并发初探ThreadPoolExecutor拒绝策略

    java并发初探ThreadPoolExecutor拒绝策略 ThreadPoolExecuter构造器 corePoolSize是核心线程池,就是常驻线程池数量: maximumPoolSize是最 ...

  6. java并发初探CyclicBarrier

    java并发初探CyclicBarrier CyclicBarrier的作用 CyclicBarrier,"循环屏障"的作用就是一系列的线程等待直至达到屏障的"瓶颈点&q ...

  7. java并发初探CountDownLatch

    java并发初探CountDownLatch CountDownLatch是同步工具类能够允许一个或者多个线程等待直到其他线程完成操作. 当前前程A调用CountDownLatch的await方法进入 ...

  8. java并发初探ReentrantWriteReadLock

    java并发初探ReentrantWriteReadLock ReenWriteReadLock类的优秀博客 ReentrantReadWriteLock读写锁详解 Java多线程系列--" ...

  9. Java内部类初探

    Java内部类初探 之前对内部类的概念不太清晰,在此对内部类与外部类之间的关系以及它们之间的调用方式进行一个总结. Java内部类一般可以分为以下三种: 成员内部类 静态内部类 匿名内部类 一.成员内 ...

随机推荐

  1. oauth2.0的授权流程详解

    授权模式 1)oauth2.0 提供了四种授权模式,开发者可以根据自己的业务情况自由选择. 授权码授权模式(Authorization Code Grant) 隐式授权模式(简化模式)(Implici ...

  2. Windows里安装wireshark或者ethereal工具(包括汉化破解)(图文详解)

    不多说,直接上干货! https://www.wireshark.org/download.html 我这里,读取的是,来自于https://www.ll.mit.edu/ideval/data/19 ...

  3. javac的访问者模式

    这一篇重点来总结下javac的访问者模式,其定义的访问者接口为JCTree.Visitor,具体如下: /** A generic visitor class for trees. */ public ...

  4. Java 实现两个整数变量的交换

    class OperatorTest { public static void main(String[] args) { int a = 10; int b = 20; System.out.pri ...

  5. Maven Oracle JDBC

    Oracle的JDBC驱动程序无法从公共仓库下载,你只能手动部署Oracle JDBC驱动程序到本地库.命令如下: mvn install:install-file -Dfile={ORACLE_HO ...

  6. Mongo db 与mysql 语法比较

    mongodb与mysql命令对比 传统的关系数据库一般由数据库(database).表(table).记录(record)三个层次概念组成,MongoDB是由数据库(database).集合(col ...

  7. 使用 Python 编写脚本并发布

    使用 Python 编写脚本并发布 P1: 脚本 通常在 Linux 服务器上会遇到在命令行中输入命令的操作,而有些操作包含的命令数目较多或者其中的命令包含的参数较多,如果一个一个的敲命令的话就太麻烦 ...

  8. java并发编程(6)显式锁

    显式锁 一.Lock与ReentrantLock Lock提供了一种无条件的.可轮询的.定时的以及可中断的锁获取操作,所有的加锁和解锁方法都是显式的 ReentrantLock实现了Lock:并提供了 ...

  9. iOS语音播报文字

    记得大学的时候学微软Window Phone时,有语音识别类似苹果的嘿,Siri.今天无聊百度搜了一下,搜到苹果语音播报文字.自己试了下还挺好玩. 1.引入框架#import <AVFounda ...

  10. SearchRequestBuilder常用方法说明

    SearchRequestBuilder常用方法说明 (1) setIndices(String... indices):上文中描述过,参数可为一个或多个字符串,表示要进行检索的index: (2) ...