0Pricing
Java Academy · 课时

使用 BigDecimal 进行财务计算

在金额和税费计算中使用 BigDecimal,避免浮点数精度误差

使用 BigDecimal 进行财务计算 是 CoddyKit 上的免费 Java Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Java Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Java Academy 课程共包含 4 节课。

使用 BigDecimal 进行财务计算

由于二进制表示的原因,浮点类型(double、float)无法精确表示所有十进制数值。对于金钱和税费,请使用 BigDecimal。

浮点数问题

二进制浮点运算会引入极小的舍入误差。在财务应用中,这些误差会不断累积并导致实际错误。

double price = 0.1 + 0.2;
System.out.println(price);          // 0.30000000000000004 (WRONG!)
System.out.println(price == 0.3);   // false

// Real bug: total never equals expected
double total = 0;
for (int i = 0; i < 10; i++) total += 0.1;
System.out.println(total); // 0.9999999999999999

安全创建 BigDecimal

请始终从 String 创建 BigDecimal,不要从 double 创建。使用 double 构造方法会继承浮点数的不精确性。

import java.math.BigDecimal;

// WRONG: double constructor
BigDecimal wrong = new BigDecimal(0.1);
System.out.println(wrong); // 0.1000000000000000055511...

// CORRECT: String constructor
BigDecimal correct = new BigDecimal("0.1");
System.out.println(correct); // 0.1

// Or use valueOf (also safe)
BigDecimal safe = BigDecimal.valueOf(0.1);
System.out.println(safe); // 0.1

使用 BigDecimal 进行基本运算

BigDecimal 使用方法调用进行算术运算:add()、subtract()、multiply()、divide()。这些方法会返回新的 BigDecimal 实例(不可变)。

import java.math.BigDecimal;

BigDecimal price = new BigDecimal("19.99");
BigDecimal tax   = new BigDecimal("0.08");
BigDecimal qty   = new BigDecimal("3");

BigDecimal subtotal = price.multiply(qty);
BigDecimal taxAmount = subtotal.multiply(tax);
BigDecimal total = subtotal.add(taxAmount);

System.out.println(subtotal);  // 59.97
System.out.println(taxAmount); // 4.7976
System.out.println(total);     // 64.7676

舍入模式

RoundingMode 控制 BigDecimal 的舍入方式。主要模式包括:

  • HALF_UP — 标准舍入(0.5 向上舍入)
  • HALF_EVEN — 银行家舍入(0.5 舍入到最近的偶数)
  • DOWN — 向零截断
  • CEILING — 向正无穷方向舍入
import java.math.*;

BigDecimal val = new BigDecimal("2.565");

System.out.println(val.setScale(2, RoundingMode.HALF_UP));   // 2.57
System.out.println(val.setScale(2, RoundingMode.HALF_EVEN)); // 2.56 (banker's)
System.out.println(val.setScale(2, RoundingMode.DOWN));      // 2.56
System.out.println(val.setScale(2, RoundingMode.CEILING));   // 2.57

除法与小数位数

使用 BigDecimal 进行除法时,必须指定小数位数和舍入模式,否则对于无限小数会抛出 ArithmeticException。

import java.math.*;

BigDecimal revenue = new BigDecimal("1000.00");
BigDecimal months  = new BigDecimal("3");

// Without scale → ArithmeticException (non-terminating decimal)
try {
    revenue.divide(months);
} catch (ArithmeticException e) {
    System.out.println("Need to specify scale!");
}

// Correct: specify scale and rounding
BigDecimal monthly = revenue.divide(months, 2, RoundingMode.HALF_UP);
System.out.println(monthly); // 333.33

比较 BigDecimal 值

请使用 compareTo() 比较值,而不是使用 equals()。equals() 会考虑小数位数:2.0 和 2.00 使用 equals() 比较时 NOT 相等,但使用 compareTo() 比较时 ARE 相等。

import java.math.BigDecimal;

BigDecimal a = new BigDecimal("2.0");
BigDecimal b = new BigDecimal("2.00");

System.out.println(a.equals(b));        // false (different scale)
System.out.println(a.compareTo(b));     // 0 (same value)
System.out.println(a.compareTo(b) == 0); // true

// For sorting in collections
import java.util.*;
List<BigDecimal> prices = Arrays.asList(
    new BigDecimal("3.50"), new BigDecimal("1.00"), new BigDecimal("2.75")
);
Collections.sort(prices);
System.out.println(prices); // [1.00, 2.75, 3.50]

使用 MathContext 处理有效数字

MathContext 指定精度(有效数字位数)和舍入模式。当您需要统一的有效数字精度,而不是固定的小数位数时,请使用它。

import java.math.*;

MathContext mc = new MathContext(4, RoundingMode.HALF_UP);

BigDecimal a = new BigDecimal("1234567");
BigDecimal b = new BigDecimal("3");

BigDecimal result = a.divide(b, mc);
System.out.println(result); // 4.115E+5 (4 significant digits)

// DECIMAL32, DECIMAL64, DECIMAL128 are standard MathContexts
BigDecimal precise = new BigDecimal("355").divide(
    new BigDecimal("113"), MathContext.DECIMAL64);
System.out.println(precise); // 3.141592920353982...

购物车示例

一个完整的购物车计算示例,使用 BigDecimal 处理订单项、税费、折扣和最终总额。

import java.math.*;

record LineItem(String name, BigDecimal price, int qty) {
    BigDecimal subtotal() {
        return price.multiply(new BigDecimal(qty));
    }
}

var items = List.of(
    new LineItem("Laptop",  new BigDecimal("999.00"), 1),
    new LineItem("Case",    new BigDecimal("29.99"),  2),
    new LineItem("Charger", new BigDecimal("49.99"),  1)
);

BigDecimal subtotal = items.stream()
    .map(LineItem::subtotal)
    .reduce(BigDecimal.ZERO, BigDecimal::add);

BigDecimal taxRate = new BigDecimal("0.09");
BigDecimal tax = subtotal.multiply(taxRate).setScale(2, RoundingMode.HALF_UP);
BigDecimal total = subtotal.add(tax);

System.out.println("Subtotal: " + subtotal);
System.out.println("Tax:      " + tax);
System.out.println("Total:    " + total);

Stripe:以分为单位的货币

Stripe 等支付处理服务会将金额存储为整数分,从根本上避免小数问题。在分与显示金额之间进行转换是一种常见模式。

import java.math.*;

// Store as cents (long) for database / API
long amountCents = 999_99L; // $999.99

// Convert to BigDecimal for display
BigDecimal amount = BigDecimal
    .valueOf(amountCents)
    .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);

System.out.println("Amount: $" + amount); // Amount: $999.99

// Convert user input back to cents
BigDecimal input = new BigDecimal("49.95");
long cents = input.multiply(new BigDecimal("100"))
    .setScale(0, RoundingMode.HALF_UP).longValueExact();
System.out.println("Cents: " + cents); // Cents: 4995

何时使用 BigDecimal,何时使用 double

请根据任务选择合适的类型:

  • BigDecimal:财务金额、税费和发票等需要精确十进制表示的场景
  • double:科学计算、图形和机器学习等速度重要且可以接受小误差的场景
  • int/long 分:支付接口以及数据库中的货币存储

快速检查

如果要检查两个 BigDecimal 值在数值上是否相等,而不考虑小数位数,应使用哪种 BigDecimal 比较方法?

总结:使用 BigDecimal 进行财务计算

要点:

  • double/浮点类型会产生舍入误差,绝不能用于金钱计算
  • 请从 String 或 BigDecimal.valueOf() 创建 BigDecimal,不要从 double 创建
  • 使用 add()、subtract()、multiply()、divide() 进行算术运算
  • 进行除法时始终指定小数位数和 RoundingMode
  • 使用 compareTo() 检查相等性,而不是 equals()
  • 对于数据库和应用程序接口相关工作,可以考虑将金钱存储为整数分

常见问题解答

「使用 BigDecimal 进行财务计算」课时是免费的吗?

是的 — 「使用 BigDecimal 进行财务计算」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Java Academy 课程的其余内容,请升级到 CoddyKit PRO。 Java Academy 课程共包含 4 节课。

「使用 BigDecimal 进行财务计算」这节课中我会学到什么?

在金额和税费计算中使用 BigDecimal,避免浮点数精度误差 你通过在浏览器中直接运行的动手代码来练习 Java Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Java Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Java Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。

「使用 BigDecimal 进行财务计算」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Java Academy 课中编写并运行代码吗?

能。每节 Java Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. Math 类基础
  2. 整数运算与溢出
  3. 使用 BigDecimal 进行财务计算
  4. NumberFormat 与 printf
← 返回 Java Academy