一文浅析Java金额计算用long还是BigDecimal

 更新时间:2025年12月26日 09:30:52   作者:刘大华  
在Java后端开发中处理与钱有关的业务时,确保金额计算的准确性和避免错误非常重要,这篇文章主要给大家介绍了关于Java中处理金额计算之使用Long还是BigDecimal的相关资料,希望对大家有所帮助

前言

最近接触一个新项目,发现系统中所有金额相关字段都使用long类型来表示。

作为一个习惯使用BigDecimal处理金额的开发者,这让我产生了疑惑:这会不会有精度问题?为什么要这样设计?

“用double不行吗?它也能表示小数啊!”

经过一番研究和思考,把最终得出的结论来和大家详细分享一下。

一、double、float类型比较

doublefloat都是Java中的浮点数类型,它们采用IEEE 754标准来表示小数。

这种表示方法类似于科学计数法,但存在一个致命问题:不是所有小数都能精确表示

举一个简单的例子:

double a = 0.1;
double b = 0.2;
double result = a + b;
System.out.println(result); 
// 输出:0.30000000000000004

什么?简单的0.1 + 0.2居然不等于0.3?

是的,这是因为在二进制的计算中,有些十进制小数没有办法精确表示,就像1除以3一样,在十进制中也不能精确表示(0.33333...)。

二、BigDecimal 解决方案

BigDecimal的正确用法

BigDecimal可以解决精度问题,但必须正确使用。

错误的构造方式

BigDecimal wrong1 = new BigDecimal(0.1); 
BigDecimal wrong2 = new BigDecimal(0.2);

这种传入浮点型的构造方式还是会有精度问题。

正确的构造方式

BigDecimal correct1 = new BigDecimal("0.1");
BigDecimal correct2 = new BigDecimal("0.2");
System.out.println("正确方式: " + correct1.add(correct2)); // 0.3

通过传入字符串进行构造。

或者用valueOf(内部也是转字符串)

BigDecimal safe1 = BigDecimal.valueOf(0.1);
BigDecimal safe2 = BigDecimal.valueOf(0.2);
System.out.println("安全方式: " + safe1.add(safe2)); // 0.3

BigDecimal的运算规则

public class BigDecimalOperations {
    public static void main(String[] args) {
        BigDecimal price = new BigDecimal("19.99");
        BigDecimal quantity = new BigDecimal("3");
        BigDecimal taxRate = new BigDecimal("0.13");
        
        // 乘法
        BigDecimal subtotal = price.multiply(quantity);
        
        // 除法必须指定精度和舍入模式
        BigDecimal tax = subtotal.multiply(taxRate)
                               .setScale(2, RoundingMode.HALF_UP);
        
        // 加法
        BigDecimal total = subtotal.add(tax);
        
        System.out.println("小计: " + subtotal); // 59.97
        System.out.println("税金: " + tax);      // 7.80
        System.out.println("总计: " + total);    // 67.77
    }
}

BigDecimal的比较操作

public class BigDecimalComparison {
    public static void main(String[] args) {
        BigDecimal num1 = new BigDecimal("10.00");
        BigDecimal num2 = new BigDecimal("10.000");
        
        // 错误:比较引用
        System.out.println("== 比较: " + (num1 == num2)); // false
        
        // 错误:equals会比较精度
        System.out.println("equals比较: " + num1.equals(num2)); // false
        
        // 正确:compareTo只比较数值
        System.out.println("compareTo比较: " + (num1.compareTo(num2) == 0)); // true
    }
}

BigDecimal的缺点

性能开销:对象创建和运算成本高

内存占用:每个对象需要20-30字节

使用复杂:容易用错构造方法和运算规则

代码冗长:简单的运算也需要多行代码

三、long方案

核心思想:以分为单位存储

使用long表示金额的核心思路是:不以元为单位,而以最小货币单位(分)存储。

public class LongAmountDemo {
    // 工具方法:元转分
    public static long yuanToFen(double yuan) {
        return Math.round(yuan * 100);
    }
    
    // 工具方法:分转元(用于显示)
    public static String fenToYuanDisplay(long fen) {
        return String.format("¥%.2f", fen / 100.0);
    }
    
    // 工具方法:分转元(用于计算)
    public static double fenToYuan(long fen) {
        return fen / 100.0;
    }
    
    public static void main(String[] args) {
        // 以分为单位存储所有金额
        long price = yuanToFen(19.99);  // 1999分 = 19.99元
        long quantity = 3;
        long taxRate = 13;  // 13% = 0.13,这里用整数表示百分比
        
        // 计算过程全部使用整数运算
        long subtotal = price * quantity;                    // 5997分
        long tax = (subtotal * taxRate) / 100;               // 780分
        long total = subtotal + tax;                         // 6777分
        
        System.out.println("小计: " + fenToYuanDisplay(subtotal)); // ¥59.97
        System.out.println("税金: " + fenToYuanDisplay(tax));      // ¥7.80
        System.out.println("总计: " + fenToYuanDisplay(total));    // ¥67.77
    }
}

long方案的优势

1.绝对精确 整数运算在计算机中是精确的,不会出现浮点数的精度问题。

2.性能卓越

// long运算 - 机器指令级别,极快
long a = 1000L, b = 2000L;
long result = a + b;

// BigDecimal运算 - 方法调用,对象创建,较慢
BigDecimal c = new BigDecimal("10.00");
BigDecimal d = new BigDecimal("20.00");
BigDecimal result2 = c.add(d);

3.存储高效

  • long:固定8字节
  • BigDecimal:对象头+数值,通常20-30字节

4.序列化简单 在数据库、JSON、网络传输中处理更简单。

实际业务应用

public class OrderService {
    // 订单金额(分)
    private long orderAmount;
    // 优惠金额(分)
    private long discountAmount;
    // 实付金额(分)
    private long actualAmount;
    
    public void calculateOrder(long unitPrice, int quantity, long discountRate) {
        // 计算订单金额
        orderAmount = unitPrice * quantity;
        
        // 计算优惠金额
        discountAmount = (orderAmount * discountRate) / 100;
        
        // 计算实付金额
        actualAmount = orderAmount - discountAmount;
    }
    
    // 显示方法
    public String getDisplayAmount() {
        return String.format("订单金额: %s, 优惠: %s, 实付: %s", 
            formatFen(orderAmount),
            formatFen(discountAmount),
            formatFen(actualAmount));
    }
    
    private String formatFen(long fen) {
        return String.format("¥%.2f", fen / 100.0);
    }
}

四、各种方案的适用场景

double/float:绝对不要用于金额

  • 科学计算、图形处理
  • 统计分析(允许误差)
  • 物理模拟
  • 不适用于任何金额计算

BigDecimal:复杂金融计算

  • 高精度利率、汇率计算
  • 税 务计算(需要复杂小数运算)
  • 银行核心系统
  • 需要任意精度的场景

long:大多数业务系统

  • 电商订单系统
  • 支付系统
  • 账户余额管理
  • 积分、优惠券系统

五、BigDecimal的最佳实践

如果确实需要使用BigDecimal,请遵循以下规则:

1. 构造方法

// 推荐
BigDecimal a = new BigDecimal("0.1");
BigDecimal b = BigDecimal.valueOf(0.1);  // 内部转字符串

// 避免
BigDecimal c = new BigDecimal(0.1);      // 精度问题

2. 运算控制

BigDecimal num1 = new BigDecimal("10.00");
BigDecimal num2 = new BigDecimal("3.00");

// 除法必须指定精度
BigDecimal result = num1.divide(num2, 4, RoundingMode.HALF_UP);

// 乘法建议控制精度
BigDecimal product = num1.multiply(num2).setScale(2, RoundingMode.HALF_UP);

3. 数值比较

BigDecimal amount1 = new BigDecimal("100.00");
BigDecimal amount2 = new BigDecimal("100.000");

// 正确
if (amount1.compareTo(amount2) == 0) {
    // 数值相等
}

// 错误
if (amount1.equals(amount2)) {
    // 不会执行,因为精度不同
}

六、long方案的实施建议

1. 建立工具类

public class MoneyUtils {
    private MoneyUtils() {} // 工具类,防止实例化
    
    // 元转分
    public static long yuanToFen(double yuan) {
        return Math.round(yuan * 100);
    }
    
    // 分转元(显示用)
    public static String fenToDisplayYuan(long fen) {
        return String.format("¥%.2f", fen / 100.0);
    }
    
    // 分转元(计算用)
    public static double fenToYuan(long fen) {
        return fen / 100.0;
    }
    
    // 金额加法(防止溢出)
    public static long add(long amount1, long amount2) {
        return Math.addExact(amount1, amount2);
    }
    
    // 金额乘法
    public static long multiply(long amount, int multiplier) {
        return Math.multiplyExact(amount, multiplier);
    }
}

2. 数据库设计

-- 使用bigint存储金额(分)
CREATE TABLE orders (
    id BIGINT PRIMARY KEY,
    order_amount BIGINT COMMENT '订单金额(分)',
    discount_amount BIGINT COMMENT '优惠金额(分)',
    actual_amount BIGINT COMMENT '实付金额(分)'
);

3. API设计

// 请求和响应中使用Long类型表示金额(分)
public class OrderRequest {
    private Long productId;
    private Integer quantity;
    private Long unitPrice;  // 单价(分)
}

public class OrderResponse {
    private String orderNo;
    private Long totalAmount;  // 总金额(分)
    private String displayAmount; // 显示金额 "¥99.99"
}

总结与选择

为什么那个项目选择long?

现在我可以理解那个项目的设计思路了:

  • 业务特性:主要是简单的加减乘运算,没有复杂的小数除法
  • 性能要求:高并发场景,需要最优性能
  • 团队协作:统一规范,避免BigDecimal的误用
  • 系统复杂度:降低系统复杂度和维护成本

如何选择

场景推荐方案理由
简单业务系统long性能好,简单可靠
银行金融系统BigDecimal精度要求极高
高并发交易long性能至关重要
复杂税 务计算BigDecimal需要复杂小数运算
新项目启动long技术债务少

技术选型从来都不是单一的,理解业务的需求,能选择出最适合的技术方案就是最好的方案。

以上就是一文浅析Java金额计算用long还是BigDecimal的详细内容,更多关于Java计算金额的资料请关注脚本之家其它相关文章!

相关文章

  • Java中的synchronized锁膨胀详解

    Java中的synchronized锁膨胀详解

    这篇文章主要介绍了Java中的synchronized锁膨胀详解,正常创建的对象,状态为无锁,对象头的Mark Word 中主要记录了 对象的年龄,也就是经历了多少次GC还存活下来,需要的朋友可以参考下
    2024-01-01
  • SpringBoot打印Banner的实现示例

    SpringBoot打印Banner的实现示例

    本文主要介绍了SpringBoot启动Banner的实现原理和打印流程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-01-01
  • Java中最基本的存储单元之变量基础教程

    Java中最基本的存储单元之变量基础教程

    变量相当于内存中一个数据存储空间的表示,类似与门牌号与房间的关系—变量看作门牌号,通过门牌号可以找到房间,而通过变量名可以访问到变量,这篇文章主要介绍了Java中最基本的存储单元之变量基础教程的相关资料,需要的朋友可以参考下
    2025-12-12
  • java常用的定时任务框架及相关代码应用示例

    java常用的定时任务框架及相关代码应用示例

    在Java开发中,定时任务是一个常见的需求,比如定时备份数据、定时清理日志、定时发送邮件等,这篇文章主要介绍了java常用的定时任务框架及相关代码应用的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2025-11-11
  • Java图片与Base64互转工具类实现过程

    Java图片与Base64互转工具类实现过程

    本文介绍了Java中实现图片与Base64编码转换的工具类,包括本地图片转Base64编码、网络图片转Base64编码、Base64编码转图片文件等功能,工具类使用了Java标准库和Apache Commons库,通过Java NIO实现高效的文件操作,感兴趣的朋友跟随小编一起看看吧
    2026-01-01
  • Java实现Markdown图片批量本地化处理工具

    Java实现Markdown图片批量本地化处理工具

    在日常使用Markdown时,我们常通过远程URL引用图片,所以本文小编就为大家详细介绍一下如何使用Java实现Markdown图片批量本地化处理吧
    2025-07-07
  • SpringBoot整合Shiro的方法详解

    SpringBoot整合Shiro的方法详解

    Apache Shiro是一个java安全(权限)框架,Shiro可以非常容易的开发出足够好的应用,其不仅可以用在javase环境,也可以用在javaee环境。本文介绍了SpringBoot整合Shiro的方法,需要的可以参考一下
    2022-05-05
  • java面向对象的三大特性之一继承用法实例分析

    java面向对象的三大特性之一继承用法实例分析

    这篇文章主要介绍了java面向对象的三大特性之一继承用法,结合实例形式分析了java面向对象程序设计中继承的基本原理与具体使用方法,需要的朋友可以参考下
    2019-11-11
  • Java 实现分布式服务的调用链跟踪

    Java 实现分布式服务的调用链跟踪

    分布式服务中完成某一个业务动作,需要服务之间的相互协作才能完成,在这一次动作引起的多服务的联动我们需要用1个唯一标识关联起来,关联起来就是调用链的跟踪。本文介绍了Java 实现分布式服务的调用链跟踪的步骤
    2021-06-06
  • MyBatis Plus插件机制与执行流程原理分析详解

    MyBatis Plus插件机制与执行流程原理分析详解

    这篇文章主要介绍了MyBatis Plus插件机制与执行流程原理分析,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-09-09

最新评论