BigDecimal 在转换 to/from 浮点数时不保留舍入值

BigDecimal not retaining rounded value when converting to/from float

我有一个函数可以使用 BigDecimal.setScale

将浮点数舍入到 n 位数
private float roundPrice(float price, int numDigits) {
    BigDecimal bd = BigDecimal.valueOf(price);
    bd = bd.setScale(numDigits, RoundingMode.HALF_UP);
    float roundedFloat = bd.floatValue();
    return roundedFloat;
}

public void testRoundPrice() {
    float numberToRound = 0.2658f;
    System.out.println(numberToRound);
    float roundedNumber = roundPrice(numberToRound, 5);
    System.out.println(roundedNumber);
    BigDecimal bd = BigDecimal.valueOf(roundedNumber);
    System.out.println(bd);
}

输出:

0.2658
0.2658
0.26579999923706055

如何防止 BigDecimal 在舍入值末尾添加所有这些额外数字?

注意:我无法执行以下操作,因为我无权访问 api 调用函数中的位数。

System.out.println(bd.setScale(5, RoundingMode.CEILING));

正好相反。 BigDecimal 说的是实话。 0.26579999923706055 更接近您的 float 在四舍五入前后一直获得的值。 float 是二进制而不是十进制数不能精确地包含 0.2658。实际上 0.265799999237060546875 已经很接近了。

当你打印浮点数时,你没有得到完整的值。发生了一些舍入,因此尽管 float 具有上述值,但您只能看到 0.2658.

当您从 float 创建 BigDecimal 时,您实际上首先转换为 double(因为这是 BigDecimal.valueOf() 接受的)。 doublefloat 具有相同的值,但将打印为 0.26579999923706055,这也是您的 BigDecimal 获得的值。

如果您想要 BigDecimal 具有 float 印刷 值,而不是其中的确切值或接近的值,以下方法可能有效:

    BigDecimal bd = new BigDecimal(String.valueOf(roundedNumber));
    System.out.println(bd);

输出:

0.2658

不过,您可能会对其他值感到惊讶,因为 float 没有那么精确。

编辑:您有效地转换了 float -> double -> String -> BigDecimal.

Dawood ibn Kareem 的这些有见地的评论让我进行了一些研究:

Actually 0.265799999237060546875.

Well, 0.26579999923706055 is the value returned by calling toString on the double value. That's not the same as the number actually represented by that double. That's why BigDecimal.valueOf(double) doesn't in general return the same value as new BigDecimal(double). It's really important to understand the difference if you're going to be working with floating point values and with BigDecimal.

那么到底发生了什么:

  1. 您的 float 在四舍五入前后的内部值为 0.265799999237060546875。
  2. 当您将 float 传递给 BigDecimal.valueOf(double) 时,您实际上是在转换 float -> double -> String -> BigDecimal.
    • doublefloat 具有相同的值,0.265799999237060546875.
    • String 的转换稍微舍入到 "0.26579999923706055"
    • 因此您的 BigDecimal 得到的值为 0.26579999923706055,即您看到并询问的值。

来自 BigDecimal.valueOf(double) 的文档:

Translates a double into a BigDecimal, using the double's canonical string representation provided by the Double.toString(double) method.

链接

  • 堆栈溢出问题:Is floating point math broken?
  • 文档:BigDecimal.valueOf(double)
  • 堆栈溢出问题:BigDecimal - to use new or valueOf

我决定修改我的程序,使用 BigDecimal 作为对象中 属性 价格的基本类型,而不是 float 类型。虽然一开始很棘手,但在 运行.

中绝对是更清洁的解决方案
public class Order {
    // float price; // old type
    BigDecimal price; // new type
}