java中BigDecimal在金融行业中的使用
1.引言
在java语言中,double和float用于二进制浮点型计算,无法得到精确的结果。而BigDecimal则用于精确的计算。不超过16位有效数字(最好是不超过13位)的科学和工程计算,可以使用double和float,但要求精确计或者超过了16位有效数字(超过13位也建议如此)的商业运算则需要使用BigDecimal进行运行,比如金融行业。
《Effactive Java》第2版第48条中提到,“float和double类型尤其不适合用于货币计算,因为要让一个float或者double精确地标识0.1(或者10的任何其他负数次方值)是不可能的。” 如果不进行运算,使用String表示金额都比double和float强,当然最好的是BigDecimal。金融行业http通信接口可以使用String来表示金额,如果内部接口调用,建议使用BigDecimal。
2.BigDecimal简介
BigDecimal 由任意精度的整数非标度值 和32 位的整数标度 (scale) 组成。如果为零或正数,则标度是小数点后的位数。如果为负数,则将该数的非标度值乘以 10 的负scale 次幂。因此,BigDecimal表示的数值是(unscaledValue × 10-scale)。
3. BigDecimal的构造方法
分四类,一类是通过另一个BigDecimal对象构造,一类是使用double/long/int/BigInteger类型的数值构造,一类是通过String字符串构造,最后一类是使用静态方法valueOf()构造,静态方法要求参数是double/long/int/BigInteger等。
JDK API文档中,对于public BigDecimal(double val) 解释如下:
- 将 double 转换为 BigDecimal,后者是 double 的二进制浮点值准确的十进制表示形式。返回的 BigDecimal 的标度是使 (10scale × val) 为整数的最小值。
注:
- 此构造方法的结果有一定的不可预知性。有人可能认为在 Java 中写入 new BigDecimal(0.1) 所创建的 BigDecimal 正好等于 0.1(非标度值 1,其标度为 1),但是它实际上等于 0.1000000000000000055511151231257827021181583404541015625。这是因为 0.1 无法准确地表示为 double(或者说对于该情况,不能表示为任何有限长度的二进制小数)。这样,传入 到构造方法的值不会正好等于 0.1(虽然表面上等于该值)。
- 另一方面,String 构造方法是完全可预知的:写入 new BigDecimal("0.1") 将创建一个 BigDecimal,它正好 等于预期的 0.1。因此,比较而言,通常建议优先使用 String 构造方法。
- 当 double 必须用作 BigDecimal 的源时,请注意,此构造方法提供了一个准确转换;它不提供与以下操作相同的结果:先使用
Double.toString(double)
方法,然后使用BigDecimal(String)
构造方法,将 double 转换为 String。要获取该结果,请使用 staticvalueOf(double)
方法。
对于public static BigDecimal valueOf(long val)的解释如下:
- 将 long 值转换为具有零标度的 BigDecimal。提供的此“静态工厂方法”优先于 (long) 构造方法,因为前者允许重用经常使用的 BigDecimal 值。
故此,对于金融行业中的使用有以下结论:
1) 优先使用String参数的构造方法;
2)如果需要通过double构造BigDecimal,有限使用valueOf(double val)方法;这样得到的BigDecimal和String参数的构造方法标度一致(即标度可控);
3)valueOf()的静态工厂方法优先于long、int等类型参数的构造方法,
4)如果想获取指定标度的初始化数值,比如2位小数的0,可以如下使用:
BigDecimal zero1 = new BigDecimal("0.00");
String的构造方法,会根据字符串的小数位来设定标度,其他的构造方法还需要额外调用setScale()来设定标度。
4. BigDecimal的标度、舍入方式
BigDecimal类的标度使用scale属性表示,就是小数位数,精度使用precision属性表示,就是有效位数(亦即整数位+小数位).
舍入方式有多种,BigDecimal类本身有舍入方式的静态成员字段,但是已经废弃,建议使用RoundingMode枚举值。
RoundingMode枚举值如下:
枚举常量摘要 CEILING
向正无限大方向舍入的舍入模式。DOWN
向零方向舍入的舍入模式。FLOOR
向负无限大方向舍入的舍入模式。HALF_DOWN
向最接近数字方向舍入的舍入模式,如果与两个相邻数字的距离相等,则向下舍入。HALF_EVEN
向最接近数字方向舍入的舍入模式,如果与两个相邻数字的距离相等,则向相邻的偶数舍入。HALF_UP
向最接近数字方向舍入的舍入模式,如果与两个相邻数字的距离相等,则向上舍入。UNNECESSARY
用于断言请求的操作具有精确结果的舍入模式,因此不需要舍入。UP
远离零方向舍入的舍入模式。其中HALF_UP就是最常用的四舍五入,其他根据英文名字很好理解。
5.BigDecimal的四则运算
如果只是对货币金额值的存取,使用String也可以,但如果金额要参与四则运算,则必须使用BigDecimal进行精确的运算。BigDecimal与double、float的运算相比,BigDecimal运算比double、float运算精确,但速度没有double、float的快。
BigDecimal的四则运算使用以下方法:
-
public BigDecimal add(BigDecimal value); //加法
public BigDecimal subtract(BigDecimal value); //减法
public BigDecimal multiply(BigDecimal value); //乘法
public BigDecimal divide(BigDecimal value); //除法
JDK API中对四则运算的标度解释如下:
对于所有算术运算符,运算的执行方式是,首先计算准确的中间结果,然后,使用选择的舍入模式将其舍入为精度设置(如有必要)指定的位数。如果不返回准确结果,则将丢弃准确结果的某些数位。当舍入增加了返回结果的大小时,前导数字“9”的进位传播可能会创建新的数位。例如,将值 999.9 舍入为三位数字,则在数值上等于一千,表示为 100×101。在这种情况下,新的 "1" 是返回结果的前导数位。
除了逻辑的准确结果外,每种算术运算都有一个表示结果的首选标度。下表列出了每个运算的首选标度。
算术运算结果的首选标度
运算 结果的首选标度 加 max(addend.scale(), augend.scale()) 减 max(minuend.scale(), subtrahend.scale()) 乘 multiplier.scale() + multiplicand.scale() 除 dividend.scale() - divisor.scale() 这些标度是返回准确算术结果的方法使用的标度;准确相除可能必须使用较大的标度除外,因为准确的结果可能有较多的位数。例如,1/32 得到 0.03125。
舍入之前,逻辑的准确中间结果的标度是该运算的首选标度。
由上面可以看出来,如果调用上述四个方法的话,计算结果的标度可能不受控制,BigDecimal还为每个运算提供了几个重载方法,可以控制运算结果的标度和舍入方式。
以divide为例进行解释:
JDK API对public BigDecimal divide(BigDecimal divisor) 的解释如下:
- 返回一个 BigDecimal,其值为 (this / divisor),其首选标度为 (this.scale() - divisor.scale());如果无法表示准确的商值(因为它有无穷的十进制扩展),则抛出 ArithmeticException。
-
-
- 参数:
divisor
- 此 BigDecimal 要相除的值。- 返回:
- this / divisor
- 抛出:
ArithmeticException
- 如果准确的商值没有无穷的十进制扩展- 从以下版本开始:
- 1.5
可以看出,无法精确表示商值时就会抛异常,而除法很大可能无法精确表示商值。所以,必须指定标度和舍入方式。以下几个重载方法可以供调用者指定标度和舍入方式。
public BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode)public BigDecimal divide(BigDecimal divisor, RoundingMode roundingMode)
public BigDecimal divide(BigDecimal divisor, MathContext mc)
这些方法也可能抛出异常,但异常的情况是:divisor==0 或者 roundingMode==ROUND_UNNECESSARY 和 this.scale() 不足以准确地表示相除的结果。所以只要设置好标度和舍入方式,基本可以保证不会抛异常。
6. BigDecimal的不可变性
BigDecimal有一个类似String的特性,就是不可变性。调用setScale或者add等运算方法后,原对象的scale和数值都不会改变,所以需要将方法的返回值进行保存使用。
比如:a = a.setScale(2); a = a.add(b). 其中a,b为BigDecimal对象。
7.总结
1)金融行业的金额一定要使用BigDecimal来表示;
2)BigDecimal最好使用String的构造方法创建,如果要使用double等数值作为参数,也要使用valueOf来创建对象。
3)BigDecimal进行四则运算时,最好指定其标度和舍入方式,否则可能抛异常。
4)BigDecimal是不可变的;四则运算后要使用对象保存结果;
5)多阅读相关API文档,写测试代码来验证其相关方法的调动,才能更好的掌握。 参考资料:
Java BigDecimal详解 (http://blog.csdn.net/jackiehff/article/details/8582449)
《Effactive Java》使用BigDecimal进行精确运算(http://www.cnblogs.com/chenssy/archive/2012/09/09/2677279.html)
java中BigDecimal在金融行业中的使用的更多相关文章
- java中BigDecimal加减乘除基本用法
Java在java.math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运算.双精度浮点型变量double可以处理16位有效数. 在实际应用中,需要对更大或者更小的数进 ...
- Java中BigDecimal的8种舍入模式
java.math.BigDecimal 不可变的.任意精度的有符号十进制数.BigDecimal 由任意精度的整数非标度值和32位的整数标度(scale)组成. 如果为零或正数,则标度是小数点后的位 ...
- Java中BigDecimal的8种舍入模式是怎样的
Java中BigDecimal的8种舍入模式是怎样的?下面长沙欧柏泰克软件学院和大家一起来学习下吧: java.math.BigDecimal 不可变的.任意精度的有符号十进制数.BigDecima ...
- Java中BigDecimal的舍入模式
java.math.BigDecimal 不可变的.任意精度的有符号十进制数.BigDecimal 由任意精度的整数非标度值和32位的整数标度(scale)组成. 如果为零或正数,则标度是小数点后的位 ...
- Java中BigDecimal类介绍及用法
Java中提供了大数字(超过16位有效位)的操作类,即 java.math.BinInteger 类和 java.math.BigDecimal 类,用于高精度计算. 其中 BigInteger 类是 ...
- [转]Java中BigDecimal的使用
原文地址:https://blog.csdn.net/cen_s/article/details/76472834 在日常开发中我们经常会碰到小数计算,而小数直接计算的话会出现一些小小的错误,如下 S ...
- java中BigDecimal的学习
干着java的活,但是看的都是一些偏底层的东西(或者我根本就没有看),有点荒废了java的学习. 最近一直在用到一个类是BigDecimal,但都是模棱两可地在那儿用,并没有深入研究这个类的细节,感觉 ...
- 商业计算中Java高精度计算BigDecimal类
<Effective Java> 第48条:如果需要精确的答案,请避免使用float和double. 如果我们编译运行下面这个程序会看到什么?public class Test{ p ...
- Java基础扫盲系列(二)—— Java中BigDecimal和浮点类型
一直以来我几乎未使用过BigDecimal类型,只有在DB中涉及到金额字段时听说要用Decimal类型,但是今天再项目代码中看到使用BigDecimal表示贷款金额. 本篇文章不是介绍BigDecim ...
随机推荐
- Linux 下web开发环境搭建-jdk环境搭建
Centos 7 附:windows 下jdk环境变量 CLASSPATH .;%JAVA_HOME%\lib;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools ...
- flask验证登录学习过程(1)---实践flask_jwt
flask_jwt应用代码: from flask import Flask from flask_jwt import JWT,jwt_required,current_identity from ...
- HDU 2012 FZU 1756关于素数的一些水题
HDU 2012 素数判定 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) To ...
- timestamp 学习
该答案摘抄自CSDN. 哇,奇迹,跨度三年了,不知道楼主是否已经解决了此问题. 路过,简单说一下,timestamp 主要是记录该行的最后修改时间戳, 注意,这个时间戳是不可以转换为时间的,只能标注该 ...
- Storm事务Topology的接口介绍
ITransactionalSpout 基本事务Topology的Spout接口,内含两部分接口:协调Spout接口以及消息发送Blot接口. TransactionalSpoutBatchExe ...
- 团队项目-BUG排查-ADT工程 To Android Studio 一整天的排查日记
4-22 10:44至4-23 0:45 ①打开Eclipse从Github上Clone MathsApp到本机,报错'Unable to resolve target'android-19' ②尝试 ...
- 2nd 阅读构建之法有感
阅读构建之法有感 利用这一周的时间,我大致了解构建之法一书,这本书带我走进了一个全新的领域.它让我以一种新的视角去了解软件产业的发展和工作,领略软件工程的独特魅力,更给出了简单易懂的方式去理解何为软件 ...
- 【codeforces914G】Sum the Fibonacci FWT+FST(快速子集变换)
题目描述 给出一个长度为 $n$ 的序列 $\{s\}$ ,对于所有满足以下条件的五元组 $(a,b,c,d,e)$ : $1\le a,b,c,d,e\le n$ : $(s_a|s_b)\& ...
- 【Java并发编程】之三:线程挂起、恢复与终止的正确方法
挂起和恢复线程 Thread 的API中包含两个被淘汰的方法,它们用于临时挂起和重启某个线程,这些方法已经被淘汰,因为它们是不安全的,不稳定的.如果在不合适的时候挂起线程(比如,锁定共享资源时), ...
- Windows与VMware中的CentOS系统互通访问
[步骤01]设置 CentOS 虚拟机-网络适配器为“桥接模式(直接连接物理网络),复制物理网络连接状态” [步骤02]配置虚拟网卡 [步骤03]配置 CentOS 网络 [步骤04]测试 windo ...