一、背景和原因分析
1、计算机无法使用二进制精确表示一个浮点数,CPU使用了指数和尾数的方式经过一定的计算得出浮点数从而导致了精度问题;面对精准计算的时候,浮点型数字的加减乘除就会存在精度问题,从而引致很大的差异
2、商业计算和金融货币服务计算都是不能有精度问题的,差之毫厘在商业计算和金融货币计算中都是非常忌讳的问题,因此我们对于商业计算和金融货币计算都得使用高精度的技术进行
二、Java中提供的解决方案
1、java.lang.Long 或者 基本类型 long
此方案主要是针对基于计算单位中的最小计量单位实施
2、java.math.BigInteger
此方案主要是针对基于计算单位中的最小计量单位实施,相对于java.lang.Long 或者 基本类型 long而言就是可以支持的数值范围更大了
3、java.math.BigDecimal
此方案是Java从JDK1.3版本开始提供的精准商业计算解决方案
4、java.lang.String
此方案主要是针对天文数字的加减乘除,一般都需要开发者额外基于运算规则进行具体的实现和开发,对于开发者能力要求比较高,在当前绝多大数的商业计算和金融货币计算中都不需要采取此方案
三、BigDecimal小结
1、BigDecimal构成
BigDecimal表示不可变的任意精度带符号十进制数。它由两部分组成:
intVal - 未校正精度的整数,类型为 BigInteger
Scale - 一个32位整数,表示小数点右边的位数 也称标度
如果scale为零或正值,则该值表示这个数字小数点右侧的位数。如果scale为负数,则该数字的真实值需要乘以10的该负数的绝对值的幂。例如,scale为-3,则这个数需要乘1000,即在末尾有3个0。
2、错误认知纠正
1、并非使用了BigDecimal就一定产生了精准计算结果,在BigDecimal中的构造函数中都有明确的说明,例如使用 BigDecimal(double val),BigDecimal(double val, MathContext mc) 是同样存在精度问题的
2、数值大小比较使用了equals()方法导致错误
3、除法没有设置标度和精度,遇到除不尽的除法运算的时候会抛出 ArithmeticException 异常
3、构建精准计算BigDecimal对象
- public static BigDecimal valueOf(T val) 其中T包含了double long两种情况
public BigDecimal(String val)
- public BigDecimal(String val, MathContext mc)
4、精准计算BigDecimal加减乘除用法
1、加法
public BigDecimal add(BigDecimal augend) public BigDecimal add(BigDecimal augend, MathContext mc)2、减法
public BigDecimal subtract(BigDecimal subtrahend) public BigDecimal subtract(BigDecimal subtrahend, MathContext mc)3、乘法
public BigDecimal multiply(BigDecimal multiplicand) public BigDecimal multiply(BigDecimal multiplicand, MathContext mc) public BigDecimal movePointRight(int n)当乘数是10,100,1000.....等10的N次方的时候优先使用小数点右移位的方法,比使用multiply的方式性能更高- 当同时存在乘法和除法的同级运算的时候,优先操作乘法再操作除法,可以在舍入操作的时候降低进度问题
- 当一个数值需要进行舍入操作,一定需要在最后结果中进行操作
4、除法
public BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode) 统一使用该方法是最安全的,建议只使用该方法 public BigDecimal divide(BigDecimal divisor, MathContext mc) 使用该方法的时候必须设置MathContext中的所有参数 public BigDecimal movePointLeft(int n)当除数是10,100,1000.....等10的N次方的时候优先使用小数点左移位的方法,比使用divide的方式性能更高
- 使用除法的时候必须判断除数不能为0
- 当同时存在乘法和除法的同级运算的时候,优先操作乘法再操作除法,可以在舍入操作的时候降低进度问题
- 当一个数值需要进行舍入操作,一定需要在最后结果中进行操作
5、绝对值
public BigDecimal abs() public BigDecimal abs(MathContext mc)6、取反
public BigDecimal negate() public BigDecimal negate(MathContext mc)
5、精准计算BigDecimal大小比较用法
1、比较两个BigDecimal实例中的数字是否一样,包含小数位最后边全是0的情况,例如 new BigDecimal("1.0") 和 new BigDecimal("1.00") 进行比较是否一样。
- 使用使用`equals`方法,包含标度进行比较
- 例子: new BigDecimal("1.0") 和 new BigDecimal("1.00") 是不一样的大小
2、单纯比较两个BigDecimal实例中的数值是否一样,包含小数位最后边全是0的情况,例如 new BigDecimal("1.0") 和 new BigDecimal("1.00") 进行比较是否一样
- 使用 public int compareTo(BigDecimal val),忽略标度的比较
- 例子:这种情况下 new BigDecimal("1.0") 和 new BigDecimal("1.00") 大小是一样的,比较结果返回0
6、精准计算BigDecimal输出字符串用法
1、public String toString()
- 有必要的时候会使用科学记数法
- scale为负数,一定会转换为科学计数的方式
- 需要先计算变动指数的值。公式为-scale+(unscaleValue.length-1),如果该值小于-6,那么则会使用科学计数的方式输出字符串
- 以10为底数的指数方式展示
- 某些场景下输出会存在精度丢失可能性(例如导出到表格,输出到前端)
2、public String toEngineeringString()
- 有必要的时候会使用工程记数法,类似科学记数法
- 以10为底数,指数必须是3的倍数的方式展示
- 某些场景下输出会存在精度丢失可能性(例如导出到表格,输出到前端)
3、public String toPlainString() -- 金融货币计算一般都是优先采取此模式
- 永不使用科学记数法、也不使用工程记数法(也就是永不使用指数方式)
- 不存在精度丢失
7、java.math.RoundingMode 舍入模式
1、ROUND_UP
- 向远离零的方向舍入
- 若舍入位为非零,则对舍入部分的前一位数字加1;若舍入位为零,则直接舍弃
- 向外取整模式
2、ROUND_DOWN
- 向接近零的方向舍入
- 不论舍入位是否为零,都直接舍弃
- 向内取整模式
3、ROUND_CEILING
- 向正无穷大的方向舍入
- 若 BigDecimal 为正,则舍入行为与 ROUND_UP 相同;若为负,则舍入行为与 ROUND_DOWN 相同
- 向上取整模式
4、ROUND_FLOOR
- 向负无穷大的方向舍入
- 若 BigDecimal 为正,则舍入行为与 ROUND_DOWN 相同;若为负,则舍入行为与 ROUND_UP 相同
- 向下取整模式
5、ROUND_HALF_UP
- 向“最接近的”整数舍入
- 若舍入位大于等于5,则对舍入部分的前一位数字加1;若舍入位小于5,则直接舍弃
- 四舍五入模式
6、ROUND_HALF_DOWN
- 向“最接近的”整数舍入
- 若舍入位大于5,则对舍入部分的前一位数字加1;若舍入位小于等于5,则直接舍弃
- 五舍六入模式
7、ROUND_HALF_EVEN
- 向“最接近的”整数舍入
- 若(舍入位大于5)或者(舍入位等于5且前一位为奇数),则对舍入部分的前一位数字加1;
- 若(舍入位小于5)或者(舍入位等于5且前一位为偶数),则直接舍弃
- 银行家舍入模式---美国金融领域使用居多
8、ROUND_UNNECESSARY
- 断言请求的操作具有精确的结果,因此不需要舍入。
- 如果对获得精确结果的操作指定此舍入模式,则抛出ArithmeticException。
8、常量类
public static final BigDecimal ZERO public static final BigDecimal ONE public static final BigDecimal TEN