前言
精度丢失的问题是在其他计算机语言中也都会出现,float和double类型的数据在执行二进制浮点运算的时候,并没有提供完全精确的结果。产生误差不在于数的大小,而是因为数的精度。
一、double进行运算时,经常出现精度丢失
0.1+0.2使用计算器计算是0.3,代码里却是0.30000000000000004
二、使用Java代码运行
为什么会这样呢?这就是精度丢失问题造成的。
因为计算机只能识别0和1,即二进制,无论哪种编程语言,都需要翻译成二进制才能被计算机识别。这种舍入误差的主要原因是浮点数值采用二进制系统表示, 而在二进制系统中无法精确地表示分数 1/10。这就好像十进制无法精确地表示分数 1/3—样。
针对十进制,1除以3是除不尽的。很好理解,因为我们一直接触的就是十进制,等于0.333333… 但是,二进制系统中无法精确地表示分数 1/10。
我们看下面的示例代码:
十进制转二进制(每次将小数部分乘2,取出整数部分,如果小数部分为0,就可以停止这个过程):十进制0.1计算如下:
public class Test5 {
public static void main(String[] args) {
//十进制 转二进制
double a = 0.1 * 2;//0.2
double b = 0.2 * 2;//0.4
double c = 0.4 * 2;//0.8
double d = 0.8 * 2;//1.6
double e = 0.6 * 2;//1.2
double f = 0.2 * 2;//0.4
double g = 0.4 * 2;//0.8
double h = 0.8 * 2;//1.6
System.out.println(a+","+b+","+c+","+d);
System.out.println(e+","+f+","+g+","+h);
//我们发现,上面的过程已经开始循环,小数部分永远不能为0
}
}
当某个业务场景对double数据的精度要求非常高时,就必须采取某种手段来处理这个问题,这也是BigDecimal为什么会被广泛应用于金额支付场景中的原因。
BigDecimal类位于java.math包下,用于对超过16位有效位的数进行精确的运算。一般来说,double类型的变量可以处理16位有效数,但实际应用中,如果超过16位,就需要BigDecimal类来操作。
三、BigDecimal类常用的有参构造器
new BigDecimal(String val)
/* @param val String representation of {@code BigDecimal}.
*
* @throws NumberFormatException if {@code val} is not a valid
* representation of a {@code BigDecimal}.
*/
public BigDecimal(String val) {
this(val.toCharArray(), 0, val.length());
}
new BigDecimal(double val)
/* @param val {@code double} value to be converted to
* {@code BigDecimal}.
* @throws NumberFormatException if {@code val} is infinite or NaN.
*/
public BigDecimal(double val) {
this(val,MathContext.UNLIMITED);
}
BigDecimal.valueOf(double val)
/* @param val {@code double} to convert to a {@code BigDecimal}.
* @return a {@code BigDecimal} whose value is equal to or approximately
* equal to the value of {@code val}.
* @throws NumberFormatException if {@code val} is infinite or NaN.
* @since 1.5
*/
public static BigDecimal valueOf(double val) {
// Reminder: a zero double returns '0.0', so we cannot fastpath
// to use the constant ZERO. This might be important enough to
// justify a factory approach, a cache, or a few private
// constants, later.
return new BigDecimal(Double.toString(val));
}
四、将double转为BigDecimal的时候,需要先把double转换为字符串,然后再作为BigDecimal(String val)构造函数的参数,这样才能避免出现精度问题。
double d1 = 0.1;
double d2 = 0.2;
double d3 = d1 + d2; //可能精度丢失问题
double d4 = d1 * d2; //可能精度丢失问题
System.out.println("d1 + d2 = "+d3);
System.out.println("d1 * d2 = "+d4);
/**
* BigDecimal类位于java.math包下,用于对超过16位有效位的数进行精确的运算。
* 一般来说,double类型的变量可以处理16位有效数,
* 但实际应用中,如果超过16位,就需要BigDecimal类来操作
*/
BigDecimal bigDecimal = BigDecimal.valueOf(d1);//也可以使用
BigDecimal p1 = new BigDecimal(Double.toString(d1));//推荐使用
BigDecimal p2 = new BigDecimal(Double.toString(d2));
// BigDecimal add = p1.add(p2);
double v = p1.add(p2).doubleValue();
double v1 = p1.multiply(p2).doubleValue();
System.out.println("-------"+v);//0.3
System.out.println("-------"+v1);//0.02
总结如下:
Java中的double类型确实存在精度丢失的问题,这主要源于其内部表示和运算规则。以下是导致double类型精度丢失的主要原因:
- 范围限制:double类型有其能表示的最大和最小值范围。当数值超出这个范围时,转换会导致精度丢失或发生溢出。
- 小数位数限制:double类型有限的位数可能无法完全表示非常长的小数部分,导致舍入错误或精度丢失。
- 十进制数的表示问题:由于double是基于二进制的浮点数表示,某些十进制数可能无法准确表示,这也会导致精度丢失。例如,0.1这个十进制数在二进制浮点表示中是一个无限循环小数,转换为double类型时会有精度损失。
- 运算过程中的精度丢失:在进行算术运算时,如果参与运算的数的精度高于double类型的精度,则运算过程中可能会出现舍入错误,导致最终结果的精度丢失。
为了避免这些问题,特别是在金融或需要高精度计算的领域,建议使用BigDecimal类进行精确运算。BigDecimal类位于java.math包下,用于对超过16位有效位的数进行精确的运算。将double转换为BigDecimal时,需要先把double转换为字符串,然后再作为BigDecimal构造函数的参数,这样可以避免出现精度问题。此外,连续的浮点数运算会累积精度误差,因此在进行大量计算或对精度有严格要求的情况下,使用BigDecimal类进行计算是更为合适的选择。