Skip to content

这两段代码的核心区别在于 计算精度数值表示方式,具体差异如下:


1. new BigDecimal(position.getAvgPx()).multiply(new BigDecimal("0.98")).toPlainString()

  • 精度:使用 BigDecimal 进行精确计算,避免浮点数舍入误差。
  • 输入处理:直接解析 position.getAvgPx() 的原始值(可能是字符串或数值),不经过 double 转换。
  • 乘法0.98 以字符串 "0.98" 传入,确保二进制精确表示。
  • 输出toPlainString() 保证结果始终是普通十进制格式(如 "98.00"),不会出现科学计数法。

适用场景
金融计算、价格调整等需要高精度的场景。


2. String.valueOf(Double.valueOf(position.getAvgPx()) * 0.98)

  • 精度:基于 double 的浮点运算,可能存在舍入误差(例如 0.98 在二进制中无法精确表示)。
  • 输入处理
    • Double.valueOf(position.getAvgPx()) 先将输入转为 double,可能引入精度损失(如果原始值是字符串或更高精度的数值)。
    • * 0.98 是标准的浮点乘法。
  • 输出String.valueOf() 的返回值可能是科学计数法(如 "9.8E+1"),取决于结果大小。

适用场景
对精度要求不高的快速计算,或已知输入值范围较小的情况。


关键区别总结

特性BigDecimal 版本Double 版本
精度高精度,无舍入误差可能有浮点误差(如 0.1 + 0.2 ≠ 0.3
输入处理直接解析原始值,无中间转换强制转为 double,可能丢失精度
0.98 的表示精确(字符串传入)二进制近似表示
输出格式固定为普通十进制(如 "98.00"可能含科学计数法(如 "9.8E+1"
性能较慢(适合精确计算)较快(适合非关键计算)

示例对比

假设 position.getAvgPx() 返回 "100.005"

  • BigDecimal 结果
    100.005 * 0.98 = 98.0049 → 输出 "98.0049"(精确)。
  • Double 结果
    100.005 转为 double 时可能已丢失精度,* 0.98 后可能输出 "98.00489999999999" 或科学计数法。

如何选择?

  • BigDecimal 如果:

    • 涉及金钱、交易、合约价格等敏感计算。
    • 需要严格避免浮点误差。
  • Double 如果:

    • 仅需快速估算,且误差可接受(如界面展示)。
    • 输入值范围有限(如 position.getAvgPx() 本身是 double 类型且无精度要求)。

    在 Java(或任何使用 IEEE 754 浮点数的语言)中,double 类型的浮点误差主要出现在以下场景中:


1. 十进制小数无法精确用二进制表示

double 底层是 二进制浮点数,而许多常见的十进制小数(如 0.1, 0.2, 0.3)在二进制中是无限循环小数,无法精确存储。
例如:

java
System.out.println(0.1 + 0.2); // 输出: 0.30000000000000004 (不是 0.3)
System.out.println(0.1 + 0.2); // 输出: 0.30000000000000004 (不是 0.3)

原因

  • 0.1 的二进制表示:0.0001100110011001100110011001100110011001100110011001101...(无限循环)
  • 0.2 的二进制表示:0.001100110011001100110011001100110011001100110011001101...
  • 相加时因截断误差导致结果偏离预期。

2. 大数与小数的运算

当数字的整数部分过大(或过小)时,浮点数的有效位数(double 为 52 位尾数)可能无法同时覆盖整数和小数部分,导致精度丢失。
例如:

java
double x = 1_000_000_000_000_000.1;
System.out.println(x); // 输出: 1000000000000000.0 (小数部分丢失)
double x = 1_000_000_000_000_000.1;
System.out.println(x); // 输出: 1000000000000000.0 (小数部分丢失)

3. 连续运算的误差累积

多次浮点运算会放大误差。例如:

java
double sum = 0.0;
for (int i = 0; i < 10; i++) {
    sum += 0.1;
}
System.out.println(sum); // 输出: 0.9999999999999999 (不是 1.0)
double sum = 0.0;
for (int i = 0; i < 10; i++) {
    sum += 0.1;
}
System.out.println(sum); // 输出: 0.9999999999999999 (不是 1.0)

4. 比较浮点数时的陷阱

直接比较 double 可能因微小误差导致错误:

java
double a = 0.1 + 0.2;
double b = 0.3;
System.out.println(a == b); // 输出: false
double a = 0.1 + 0.2;
double b = 0.3;
System.out.println(a == b); // 输出: false

正确做法:比较时允许一定误差范围(如 Math.abs(a - b) < 1e-10)。


5. 类型转换时的精度丢失

将高精度数(如 BigDecimallong)转为 double 时可能丢失精度:

java
long bigNumber = 9_223_372_036_854_775_807L;
double d = bigNumber;
System.out.println(d); // 输出: 9.223372036854776E18 (末尾精度丢失)
long bigNumber = 9_223_372_036_854_775_807L;
double d = bigNumber;
System.out.println(d); // 输出: 9.223372036854776E18 (末尾精度丢失)

如何避免浮点误差?

  1. BigDecimal
    适用于金融、价格等需要精确计算的场景。

    java
    BigDecimal result = new BigDecimal("0.1").add(new BigDecimal("0.2"));
    System.out.println(result); // 输出: 0.3
    BigDecimal result = new BigDecimal("0.1").add(new BigDecimal("0.2"));
    System.out.println(result); // 输出: 0.3
  2. 限制小数位数
    使用 DecimalFormatString.format 格式化输出,但注意这只是显示优化,不解决存储误差。

    java
    System.out.println(String.format("%.2f", 0.1 + 0.2)); // 输出: 0.30
    System.out.println(String.format("%.2f", 0.1 + 0.2)); // 输出: 0.30
  3. 避免直接比较
    改用误差范围比较:

    java
    boolean isEqual = Math.abs(a - b) < 1e-9;
    boolean isEqual = Math.abs(a - b) < 1e-9;

什么时候可以用 double

  • 科学计算、图形渲染等允许微小误差的场景。
  • 数值范围有限且无需精确小数的情况(如统计百分比、温度测量等)。

总结

double 的浮点误差本质是 二进制无法精确表示某些十进制小数,在涉及金钱、合约、累计计算等场景时务必使用 BigDecimal