更新时间: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. 【Guava】Optional接口来避免空指针错误

    null会带来很多问题,从开始有null开始有无数程序栽在null的手里,null的含义是不清晰的,检查null在大多数情况下是不得不做的,而我们又在很多时候忘记了对null做检查,在我们的产品真正投 ...

  2. System.Web.Caching.Cache类 缓存 各种缓存依赖(转)

    转自:http://www.cnblogs.com/kissdodog/archive/2013/05/07/3064895.html Cache类,是一个用于缓存常用信息的类.HttpRuntime ...

  3. NSSM - the Non-Sucking Service Manager

    nssm is a service helper which doesn't suck. srvany and other service helper programs suck because t ...

  4. spark2.4+elasticsearch6.1.1搭建一个推荐系统

    目录 整体框架 安装相关的组件 elasticsearch安装 spark安装 下载es-hadoop中间件 安装计算向量相似度的elasticsearch插件 运行 结果展示 参考博客 本博文详细记 ...

  5. Codeforces F. Cowmpany Cowmpensation

    Description 有一棵树,现在要给每个节点赋一个在1到D之间的权值,问有多少种方案满足任意一个节点的权值都不大于其父亲的权值. n<=3000,D<=1e9 题面 Solution ...

  6. 第8章 scrapy进阶开发(1)

    8-1 selenium动态网页请求与模拟登录知乎 Ⅰ.介绍selenium 1.什么是selenium:selenium百度百科 2.selenium的构架图: 如果要操作浏览器,还需要一个driv ...

  7. SSIS教程:创建简单的ETL包 -- 5. 添加包部署模型的包配置(Adding Package Configurations for the Package Deployment Model)

    包配置允许您从开发环境的外部设置运行时属性和变量. 配置允许您开发灵活且易于部署和分发的包.Microsoft Integration Services 提供了以下配置类型: XML 配置文件 环境变 ...

  8. Visual studio 2017 c++ wcout 无法输出中文

    wcout.imbue(locale(locale(), "", LC_CTYPE));

  9. 撩课-Java每天5道面试题第14天

    101.请解释下 ORM? 对象关系映射(Object Relational Mapping)模式 是一种为了解决面向对象与关系数据库 存在的互不匹配的现象的技术. 简单来说, ORM是通过使用描述对 ...

  10. 基于SSM框架配置多数据源

    项目基于ssm + maven,通过注解可以实现自动切换数据源. 一.pom.xml <?xml version="1.0" encoding="UTF-8&quo ...