Java BigDecimal初探
更新时间:2016-03-17
一、引言
《Effactive Java》中有这样的描述:float和double类型的主要设计目标是为了科学计算和工程计算。他们执行二进制浮点运算,这是为了在广域数值范围上提供较为精确的快速近似计算而精心设计的。然而,它们没有提供完全精确的结果,所以不应该被用于要求精确结果的场合。但是,货币计算往往要求结果精确,这时候可以使用int、long或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
因为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初探的更多相关文章
- java bigDecimal and double
Java BigDecimal和double BigDecimal是Java中用来表示任意精确浮点数运算的类,在BigDecimal中,使用unscaledValue × 10-scale来表示一 ...
- Java BigDecimal类的使用和注意事项
1.对于金额相关运算,若是精度较高,基本上用BigDecimal进行运算,精度要求低的话用Long.Double即可 2.web后台接受金额用String接受,展示到前端一般也转成 String 3. ...
- java并发初探ConcurrentSkipListMap
java并发初探ConcurrentSkipListMap ConcurrentSkipListMap以调表这种数据结构以空间换时间获得效率,通过volatile和CAS操作保证线程安全,而且它保证了 ...
- java并发初探ConcurrentHashMap
java并发初探ConcurrentHashMap Doug Lea在java并发上创造了不可磨灭的功劳,ConcurrentHashMap体现这位大师的非凡能力. 1.8中ConcurrentHas ...
- java并发初探ThreadPoolExecutor拒绝策略
java并发初探ThreadPoolExecutor拒绝策略 ThreadPoolExecuter构造器 corePoolSize是核心线程池,就是常驻线程池数量: maximumPoolSize是最 ...
- java并发初探CyclicBarrier
java并发初探CyclicBarrier CyclicBarrier的作用 CyclicBarrier,"循环屏障"的作用就是一系列的线程等待直至达到屏障的"瓶颈点&q ...
- java并发初探CountDownLatch
java并发初探CountDownLatch CountDownLatch是同步工具类能够允许一个或者多个线程等待直到其他线程完成操作. 当前前程A调用CountDownLatch的await方法进入 ...
- java并发初探ReentrantWriteReadLock
java并发初探ReentrantWriteReadLock ReenWriteReadLock类的优秀博客 ReentrantReadWriteLock读写锁详解 Java多线程系列--" ...
- Java内部类初探
Java内部类初探 之前对内部类的概念不太清晰,在此对内部类与外部类之间的关系以及它们之间的调用方式进行一个总结. Java内部类一般可以分为以下三种: 成员内部类 静态内部类 匿名内部类 一.成员内 ...
随机推荐
- Springboot用官方建议访问Html页面并接传值
Springboot用官方建议访问Html页面并接传值 我们以前通常习惯用webapp来防止jsp页面,但是到了Springboot中,官方建议用Static文件夹来存放及静态的资源, 用templa ...
- javac之向前引用
可以参考JLS7:https://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.3.2.3 public class Test5 ...
- InnoDB的分区表
分区功能并不是在存储引擎层完成的,因此不只有InnoDB存储引擎支持分区,常见的存储引擎MyISAM.NDB等都支持.但也并不是所有的存储引擎都支持,如CSV.FEDERATED.MERGE等就不支持 ...
- mysql 索引数据结构及原理
原文:http://www.uml.org.cn/sjjm/201107145.asp 1 索引的本质 MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构.提取句子 ...
- 【转】WinForm窗体显示和窗体间传值
以前对WinForm窗体显示和窗体间传值了解不是很清楚 最近做了一些WinForm项目,把用到的相关知识整理如下 A.WinForm中窗体显示 显示窗体可以有以下2种方法: Form.ShowDial ...
- JQuery ajax-向服务器发送请求的方法
如需将请求发送到服务器,我们使用 XMLHttpRequest 对象的 open() 和 send() 方法: xmlhttp.open("GET","ajax_info ...
- Rsyslog+ELK日志分析系统
转自:https://www.cnblogs.com/itworks/p/7272740.html Rsyslog+ELK日志分析系统搭建总结1.0(测试环境) 因为工作需求,最近在搭建日志分析系统, ...
- [C语言] 数据结构概述
1.数据结构概述 定义 我们如何把现实中大量而复杂的问题以特定的数据类型和特定的存储结构保存到主存储器(内存)中, 以及在此基础上为实现某个功能(比如查找.删除.查找.某个元素,对所有元素进行排序)而 ...
- Java 并发:线程中断-interrupt
一直以为执行了interrupt方法就可以让线程结束,并抛出InterruptedException. 今天看了Java并发编程实战的第七章发现并不是这么回事,在这章的开头就提到 要使任务和线程能安全 ...
- Luogu4433:[COCI2009-2010#1] ALADIN(类欧几里德算法)
先套用一个线段树维护离散化之后的区间的每一段的答案 那么只要考虑怎么下面的东西即可 \[\sum_{i=1}^{n}(A\times i \ mod \ B)\] 拆开就是 \[\sum_{i=1}^ ...