Java踩坑记系列之BigDecimal

在java.math包中提供了对大数字的操作类,用于进行高精确计算,如BigInteger,BigDecimal类。而平常我们开发中使用最多的float和double只能适用于一般的科学和工程计算,如果要在比较精确的计算方面如货币,那么使用float和double会相应的丢失精度,因此用于精密计算大数字的类BigDecimal就必不可少了。所以BigDecimal适合商业计算场景,用来对超过16位有效位的数进行精确的运算。但是BigDecimal的使用并不像float和double那样,使用不当造成的后果更严重,下面就来看下我们项目中踩过BigDecimal的坑:

一. BigDecimal的初始化精度丢失问题

先来看下面代码的运行结果:

BigDecimal bd1 = new BigDecimal(0.1);
System.out.println("bd1="+bd1);
BigDecimal bd2 = new BigDecimal("0.1");
System.out.println("bd2="+bd2);
BigDecimal bd3 = BigDecimal.valueOf(0.1);
System.out.println("bd3="+bd3);

输出结果:

bd1=0.1000000000000000055511151231257827021181583404541015625
bd2=0.1
bd3=0.1

如果是float或double类型转Bigdecimal,不要使用new BigDecimal()转, 使用valueOf()方法 或 new BigDecimal("")转成string,否则有可能出现精度问题。

《Effective Java》这本书里说过:

如果需要精确的答案,请避免使用float和double

因为float和double执行的是二进制浮点运算,二进制有些情况下不能准确的表示一个小数,就像十进制不能准确的表示1/3(1/3=0.3333…)也就是说二进制表示小数的时候只能够表示能够用1/(2^n)的和的任意组合,例如:

  • 0.5能够表示,因为它可以表示成为1/2
  • 0.75也能够表示,因为它可以表示成为1/2+1/(2^2)
  • 0.875也能够表示,因为它可以表示成为1/2+1/(22)+1/(23)
  • 但是0.1不能够精确表示,因为它不能够表示成为1/(2^n)的和的形式
System.out.println(0.5*3);
System.out.println(0.1*3);

大家可以本地执行下这两行代码,看下输出结果就知道为什么二进制不能表示0.1却可以表示0.5了。所以其实不是BigDecimal的问题,BigDecimal就是为了满足精确运算存在的,问题出在0.1它本身就一个不准确的值,这其实跟BigDecimal无关,但在使用的时候需要注意用法。

二. BigDecimal在进行除法运算时需设置精度,否则对于除不尽的情况会抛出异常

继续看下面的代码执行结果:

BigDecimal bd4 = new BigDecimal("10");
BigDecimal bd5 = new BigDecimal("3");
System.out.println(bd4.divide(bd5));

输出结果:

Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
at java.math.BigDecimal.divide(BigDecimal.java:1690)
at BigDecimalTest.main(BigDecimalTest.java:38)

应该向下面这样设置小数点后的位数,以及超出后是四舍五入和向上/向下取整或者直接舍弃:

System.out.println(bd4.divide(bd5,2,BigDecimal.ROUND_DOWN));

第二个参数表示小数位数,第三个参数表示超出的位数直接舍弃(当然也可以设置四舍五入,向上取整等)

三. 不要使用BigDecimal的equals方法比较大小, 否则可能会因为精度问题导致比较结果和预期的不一致

BigDecimal bd1 = new BigDecimal("0");
BigDecimal bd2 = new BigDecimal("0.0");
System.out.println(bd1.equals(bd2));
System.out.println(bd1.compareTo(bd2) == 0)

输出结果:

equals:false
compareTo:true

如果你无法确定你的BigDecimal值有小数情况,最好用compareTo!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MACD 是一种技术分析指标,用于衡量股票价格趋势的强弱和转折点。在 Java 中使用 BigDecimal 类来实现 MACD 指标的计算可以提高精度,避免浮点数计算带来的误差。 以下是一个使用 BigDecimal 实现 MACD 指标计算的示例代码: ```java import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; public class MACD { private List<BigDecimal> closePriceList; private int shortPeriod; private int longPeriod; private int signalPeriod; public MACD(List<BigDecimal> closePriceList, int shortPeriod, int longPeriod, int signalPeriod) { this.closePriceList = closePriceList; this.shortPeriod = shortPeriod; this.longPeriod = longPeriod; this.signalPeriod = signalPeriod; } public BigDecimal[] calculate() { BigDecimal[] result = new BigDecimal[3]; List<BigDecimal> shortEMAList = calculateEMA(closePriceList, shortPeriod); List<BigDecimal> longEMAList = calculateEMA(closePriceList, longPeriod); BigDecimal shortEMA = shortEMAList.get(shortEMAList.size() - 1); BigDecimal longEMA = longEMAList.get(longEMAList.size() - 1); BigDecimal macd = shortEMA.subtract(longEMA); List<BigDecimal> signalEMAList = calculateEMA(getSubList(shortEMAList, signalPeriod), signalPeriod); BigDecimal signalEMA = signalEMAList.get(signalEMAList.size() - 1); BigDecimal histogram = macd.subtract(signalEMA); result[0] = macd; result[1] = signalEMA; result[2] = histogram; return result; } private List<BigDecimal> calculateEMA(List<BigDecimal> priceList, int period) { List<BigDecimal> emaList = new ArrayList<>(); BigDecimal multiplier = BigDecimal.valueOf(2).divide(BigDecimal.valueOf(period + 1), 10, BigDecimal.ROUND_HALF_UP); BigDecimal ema = priceList.get(0); emaList.add(ema); for (int i = 1; i < priceList.size(); i++) { ema = priceList.get(i).subtract(ema).multiply(multiplier).add(ema); emaList.add(ema); } return emaList; } private List<BigDecimal> getSubList(List<BigDecimal> list, int size) { int start = list.size() - size; if (start < 0) { start = 0; } return list.subList(start, list.size()); } } ``` 在这个示例代码中,MACD 类接收一个由 BigDecimal 类型的收盘价组成的列表,以及三个整数类型的参数:shortPeriod,longPeriod 和 signalPeriod。计算过程分为三个步骤: 1. 计算短期和长期指数平均线(EMA); 2. 计算 MACD 值和信号线; 3. 计算直方图。 在计算 EMA 时,使用 BigDecimal 类型的 multiplier 变量来避免浮点数计算误差。在计算 MACD 和直方图时,用 subtract() 方法减去 BigDecimal 类型的数值,避免浮点数计算误差。 为了避免计算过程中产生的不必要的精度误差,建议在计算过程中使用尽可能多的 BigDecimal 对象。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值