更新时间: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. MySQL之mysql命令使用详解

    MySQL Name mysql - the MySQL command-line tool Synopsis mysql [options] db_name Description mysql is ...

  2. Kibana插件sentinl实现邮件报警

    为什么会突然想用到对日志的异常内容进行邮件报警,是因为在上周公司的线上业务多次出现锁表,开发在优化sql的同时,我也在想是不是可以对日志的异常内容进行检测并实现邮件预警. 在网上查询了一些资料后,决定 ...

  3. windows 7 做AP

    启动脚本: @echo off netsh wlan set hostednetwork mode=allow ssid=<ap-name> key=<password> ne ...

  4. ++i? i++? i+=1? i=i+1? 何必纠结?

    前言 今天在牛客上看面经,看到一个问题:num++; num+=1; num = num +1; 哪个效率最高? 自从学习C语言开始,我就在纠结for语言应该写i++,还是++i,其实这个问题,可以通 ...

  5. jquery连续滚动

    本文非常详细的讲解在jquery里实现图片或文字的连续循环滚动的方法. 连续循环滚动是我们在网页开发中经常要用到的特效,在jquery里,我们要实现文字或图片的连续循环滚动是非常简单的.出处:http ...

  6. flush table with read lock的轻量级解决方案

    为什么要使用FTWRL   MySQL dba在日常工作中,数据备份绝对是工作频度最高的工作内容之一.当你使用逻辑方式进行备份(mydumper,mysqldump)或物理方式进行备份(percona ...

  7. linux centOs中安装好数据库,客户端用plsql连接oracle

    原创作品,转载请在文章显眼位置注明出处:https://www.cnblogs.com/sunshine5683/p/10030375.html 首先,回顾上篇 CenOs7安装oracle图文详细过 ...

  8. 第一个Windows窗口应用程序

    学习目的 熟悉开发工具Visual C++ 6.0和MSDN 2001的使用. 应用Windows API函数, 手工编写具有最基本构成的Windows窗口应用程序(包含WinMain入口函数, 消息 ...

  9. Oracle数据库基本操作(一) —— Oracle数据库体系结构介绍、DDL、DCL、DML

    一.Oracle数据库介绍 1.基本介绍 Oracle数据库系统是美国ORACLE公司(甲骨文)提供的以分布式数据库为核心的一组软件产品,是目前最流行的客户/服务器(CLIENT/SERVER)或B/ ...

  10. LeetCode SQL: Second Highest Salary

    , NULL, salary) as `salary` from ( ,) tmp Write a SQL query to get the second highest salary from th ...