1.概述
精度丢失,由于现代计算机中采用了浮点数来表示小数,这种表示法会存在精度丢失的问题。想要了解精度丢失的原因,可以去看博主另一篇文章,里面详细解释了其中的原因:
下面举一个精度丢失的例子:
// 商品价格 double price1 = 19.99; double price2 = 9.99; double price3 = 4.99; // 商品数量 int quantity1 = 2; int quantity2 = 3; int quantity3 = 1; // 计算每种商品的小计 double subtotal1 = price1 * quantity1; double subtotal2 = price2 * quantity2; double subtotal3 = price3 * quantity3; // 计算总价 double total = subtotal1 + subtotal2 + subtotal3; // 输出结果 System.out.println("商品1小计:" + subtotal1); System.out.println("商品2小计:" + subtotal2); System.out.println("商品3小计:" + subtotal3); System.out.println("购物车总价:" + total);
摸出手机算一下就知道,本来总价应该等于74.94,但是实际的运行结果等于:
这种精度丢失在诸如金融、航天等需要高精度运算的场景下是无法接受的,为了保证精度,JDK中推出了BigDecimal类型,用来进行浮点数的精确计算。将上面的例子改为BigDecimal后:
// 商品价格 BigDecimal price1 = new BigDecimal("19.99"); BigDecimal price2 = new BigDecimal("9.99"); BigDecimal price3 = new BigDecimal("4.99"); // 商品数量 int quantity1 = 2; int quantity2 = 3; int quantity3 = 1; // 计算每种商品的小计 BigDecimal subtotal1 = price1.multiply(new BigDecimal(quantity1)); BigDecimal subtotal2 = price2.multiply(new BigDecimal(quantity2)); BigDecimal subtotal3 = price3.multiply(new BigDecimal(quantity3)); // 计算总价 BigDecimal total = subtotal1.add(subtotal2).add(subtotal3); // 输出结果 System.out.println("商品1小计:" + subtotal1); System.out.println("商品2小计:" + subtotal2); System.out.println("商品3小计:" + subtotal3); System.out.println("购物车总价:" + total);
结果:
2.基本API
2.1.创建 BigDecimal 对象:
- BigDecimal(String val):使用字符串表示创建一个 BigDecimal对象。
- BigDecimal(double val):使用双精度浮点数创建一个 BigDecimal对象。
- BigDecimal(BigInteger val):使用 BigInteger对象创建一个BigDecimal 对象。
BigDecimal bigDecimal1=new BigDecimal("0.8"); BigDecimal bigDecimal2=new BigDecimal(0.8); BigDecimal bigDecimal3=new BigDecimal(8);
2.3.基本运算方法:
add(BigDecimal augend):加法操作,将当前 BigDecimal 对象与参数相加。
subtract(BigDecimal subtrahend):减法操作,将当前 BigDecimal 对象减去参数。
multiply(BigDecimal multiplicand):乘法操作,将当前 BigDecimal 对象与参数相乘。
divide(BigDecimal divisor):除法操作,将当前 BigDecimal 对象除以参数。
- pow(int exponent):指数操作,将当前 BigDecimal 对象的幂次方计算。
BigDecimal bigDecimal1=new BigDecimal("0.8"); BigDecimal bigDecimal2=new BigDecimal("0.2"); System.out.println(bigDecimal1.add(bigDecimal2)); System.out.println(bigDecimal1.multiply(bigDecimal2)); System.out.println(bigDecimal1.divide(bigDecimal2)); System.out.println(bigDecimal1.pow(2));
2.4.精度控制方法:
setScale(int newScale):设置 BigDecimal对象的精度(小数点后的位数)。
setScale(int newScale, RoundingMode roundingMode):设置 BigDecimal 对象的精度,并指定舍入模式。
- round(MathContext mc):使用指定的舍入规则舍入当前 BigDecimal对象。
BigDecimal number = new BigDecimal("123.456789"); BigDecimal roundedNumber = number.setScale(2, BigDecimal.ROUND_HALF_UP);
2.5.比较
- compareTo(BigDecimal val):比较当前 BigDecimal对象与参数的大小关系。
- equals(Object obj):比较当前 BigDecimal 对象与参数是否相等。
BigDecimal bigDecimal1 = new BigDecimal("123.456789"); BigDecimal bigDecimal2 = new BigDecimal("123.456789"); System.out.println(bigDecimal1.compareTo(bigDecimal2)); System.out.println(bigDecimal1.equals(bigDecimal2));
2.6.转换
intValue()、longValue()、floatValue()、doubleValue():将 BigDecimal 对象转换为对应的基本数据类型。
toBigInteger()、toBigIntegerExact():将 BigDecimal对象转换为 BigInteger对象。
- toBigInteger()、toBigIntegerExact():将 BigDecimal对象转换为 BigInteger对象。
BigDecimal bigDecimal = new BigDecimal("123.456789"); int i = bigDecimal.intValue(); float v = bigDecimal.floatValue(); double v1 = bigDecimal.doubleValue(); long l = bigDecimal.longValue(); BigInteger bigInteger = bigDecimal.toBigInteger(); BigInteger bigInteger1 = bigDecimal.toBigIntegerExact();
3.注意事项
BigDecimal虽然能杜绝运算过程中的精度丢失,但无法杜绝初始化过程中的精度丢失,例如:
BigDecimal bigDecimal1=new BigDecimal(1.2); BigDecimal bigDecimal2=new BigDecimal(1.234); System.out.println(bigDecimal1.add(bigDecimal2));
上面代码就会存在精度丢失,因为我们输入1.2和1.234后,输入的数据会转浮点数存在计算机的内存中,这种转换过程中,精度就已经丢失了,BigDecimal去内存中读这两个数时,读出来的就是精度丢失的数。
为了确保精度,尽量用字符串来初始化:
BigDecimal bigDecimal1=new BigDecimal("1.2"); BigDecimal bigDecimal2=new BigDecimal("1.234"); System.out.println(bigDecimal1.add(bigDecimal2));
4.底层实现原理
BigDecimal底层的代码可读性不是很好,这里直接给出底层实现原理的总结:
在BigDecimal内部,它使用一个整数数组来存储数值的每一位。通常情况下,数组的每个元素表示一组十进制数的位数,例如,数组的第一个元素表示最低位,第二个元素表示十位,以此类推。每个数组元素都是一个32位整数,即可以存储0到2^32-1之间的数值。
为了表示一个数值,BigDecimal还需要维护一些其他的信息,包括符号(正数、负数或零)、小数点的位置以及数值的精度。这些信息通过额外的变量来保存。
在进行数值的运算时,BigDecimal会根据操作的类型和需要的精度,对两个数值进行相应的运算,例如加法、减法、乘法和除法。运算的过程中,它会对两个数值的符号进行处理,并按照数学规则进行运算。对于除法运算,BigDecimal会通过精确的算法进行计算,避免了浮点数除法可能产生的精度损失。