ZetCode

Java BigDecimal

上次修改时间:2024 年 7 月 4 日

在本文中,我们将展示如何在 Java 中使用 BigDecimal 执行高精度计算。

BigDecimal 表示一个不可变的、任意精度的有符号十进制数。它用于高精度算术。BigDecimal 提供了算术、标度操作、舍入、比较、哈希和格式转换的操作。

BigDecimal 由两部分组成

例如,BigDecimal 2.18 的未缩放值为 218,标度为 2。

BigDecimal 用于需要高精度的领域;例如,财务或货币计算。 BigDecimal 值的算术运算通过诸如 addsubtract 之类的方法完成; +-*\ 运算符未重载。

BigDecimal 基本算术运算

以下示例显示了使用 BigDecimal 的基本算术运算。

Main.java
import java.math.BigDecimal;
import java.math.RoundingMode;

void main() {

    var val1 = new BigDecimal("3.44");
    var val2 = new BigDecimal("2.74");

    BigDecimal res1 = val1.add(val2);
    System.out.println(res1);

    BigDecimal res2 = val1.subtract(val2);
    System.out.println(res2);

    BigDecimal res3 = val1.multiply(val2);
    System.out.println(res3);

    BigDecimal res4 = val1.divide(BigDecimal.TEN, RoundingMode.DOWN);
    System.out.println(res4);

    BigDecimal res5 = val1.divide(val2, 15, RoundingMode.HALF_UP);
    System.out.println(res5);
}

在此示例中,我们有加法、减法、乘法和除法运算。

var val1 = new BigDecimal("3.44");
var val2 = new BigDecimal("2.74");

我们创建两个 BigDecimal 值。 这些数字作为字符串传递。

BigDecimal res1 = val1.add(val2);
System.out.println(res1);

在这里,我们将两个 BigDecimal 数字相加。 加法运算通过 add 方法执行。

BigDecimal res4 = val1.divide(BigDecimal.TEN, RoundingMode.DOWN);
System.out.println(res4);

在进行除法运算时,我们还需要指定舍入模式。

$ java Main.java
6.18
0.70
9.4256
0.34
1.255474452554745

BigDecimal 精度

BigDecimal 用于高精度算术。

Main.java
import java.math.BigDecimal;

    void main() {

        double a = 0.1 + 0.1 + 0.1;
        double b = 0.3;

        System.out.println(a);
        System.out.println(b);
        System.out.println(a == b);

        var c = new BigDecimal("0.1").add(new BigDecimal("0.1"))
                .add(new BigDecimal("0.1"));
        var d = new BigDecimal("0.3");

        System.out.println(c);
        System.out.println(d);
        System.out.println(c.equals(d));
    }

在此示例中,我们将 double 类型的精度与 BigDecimal 进行比较。 我们添加三个浮点值,并将其与预期输出进行比较。

$ java Main.java
0.30000000000000004
0.3
false
0.3
0.3
true

double 存在很小的边际误差; 因此,该操作不精确。 BigDecimal 给出预期的输出。

BigDecimal 舍入模式

BigDecimal 类允许其用户完全控制舍入行为。 如果未指定舍入模式并且无法表示确切的结果,则会抛出异常。

Main.java
import java.math.BigDecimal;
import java.math.RoundingMode;

void main() {

    var x = new BigDecimal("5.54");
    BigDecimal x2 = x.setScale(1, RoundingMode.FLOOR);
    System.out.println(x2);

    var y = new BigDecimal("5.94");
    BigDecimal y2 = y.setScale(1, RoundingMode.CEILING);
    System.out.println(y2);
}

在此示例中,我们在两种不同的舍入模式下舍入两个值。

var x = new BigDecimal("5.54");
BigDecimal x2 = x.setScale(1, RoundingMode.FLOOR);

使用 setScale 方法,我们提供标度和舍入模式。 在我们的例子中,我们将值四舍五入到一位小数,并使用舍入模式 RoundingMode.FLOOR,它向负无穷大舍入。

$ java Main.java
5.5
6.0

Java BigDecimal 比较

使用 compareTo 方法,值相等但标度不同的两个 BigDecimal 对象(如 4.0 和 4.00)被此方法视为相等。

Main.java
import java.math.BigDecimal;

void main() {

    var x = new BigDecimal("1.6");
    var y = new BigDecimal("1.60");

    System.out.println(x.equals(y));
    System.out.println(x.compareTo(y));

}

该示例使用 equalscompareTo 比较值 1.6 和 1.60。

$ java Main.java
false
0

对于相等的值,compareTo 返回 0。

BigDecimal 实际示例

以下示例按类别对产品进行分组,并计算每个类别中所有产品的总价。

Main.java
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

record Product(String name, String category, BigDecimal price) {}

void main() {

    Map<String, Map<BigDecimal, List<Product>>> productsByCategories =
            products().stream().collect(
                    Collectors.groupingBy(Product::category,
                            Collectors.groupingBy(Product::price)));

    productsByCategories.forEach((k, v) -> {

        System.out.printf("%s: ", k);

        var sum = new BigDecimal("0");

        var prices = v.keySet();
        for (var price: prices) {

            sum = sum.add(price);
        }

        System.out.println(sum);
    });
}

List<Product> products() {

    return List.of(
        new Product("apple", "fruit", new BigDecimal("4.50")),
        new Product("banana", "fruit", new BigDecimal("3.76")),
        new Product("carrot", "vegetables", new BigDecimal("2.98")),
        new Product("potato", "vegetables", new BigDecimal("0.92")),
        new Product("garlic", "vegetables", new BigDecimal("1.32")),
        new Product("ginger", "vegetables", new BigDecimal("2.45")),
        new Product("white bread", "bakery", new BigDecimal("1.50")),
        new Product("roll", "bakery", new BigDecimal("0.08")),
        new Product("bagel", "bakery", new BigDecimal("0.15"))
    );
}

我们有三种类别的各种杂货:水果、蔬菜和烘焙食品。 我们的目标是按类别对产品进行分组,并计算各个类别中所有价格的总和。

Map<String, Map<BigDecimal, List<Product>>> productsByCategories =
products().stream().collect(
        Collectors.groupingBy(Product::category,
                Collectors.groupingBy(Product::price)));

使用 Java 流,我们按类别和价格对产品进行分组。

productsByCategories.forEach((k, v) -> {

    System.out.printf("%s: ", k);

    var sum = new BigDecimal("0");

    var prices = v.keySet();
    for (var price: prices) {

        sum = sum.add(price);
    }

    System.out.println(sum);
});

forEach 循环中,我们计算每个类别中所有价格的总和。

$ java Main.java
bakery: 1.73
fruit: 8.26
vegetables: 7.67

来源

Java BigDecimal - 语言参考

在本文中,我们展示了如何在 Java 中使用 BigDecimal 进行高精度算术运算。

作者

我叫 Jan Bodnar,是一位充满热情的程序员,拥有丰富的编程经验。 自 2007 年以来,我一直在撰写编程文章。 迄今为止,我已经撰写了 1,400 多篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有Java教程