当前位置: 首页 > news >正文

Java 的金额计算用 long 还是 BigDecimal?资深程序员这样选

前言

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

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

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

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

一、double、float类型比较

double和float都是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的缺点

  1. 性能开销:对象创建和运算成本高
  2. 内存占用:每个对象需要20-30字节
  3. 使用复杂:容易用错构造方法和运算规则
  4. 代码冗长:简单的运算也需要多行代码

三、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?

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

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

如何选择?

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

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

http://www.jsqmd.com/news/645577/

相关文章:

  • 别再手动画了!用Python脚本5分钟搞定AutoCAD Plant 3D水平四通管件
  • 广东开窗器控制箱生产厂家哪家靠谱 - GrowthUME
  • 彩信接口文档怎么写?彩信开发教程
  • 3分钟搞定iPhone USB网络共享:Windows驱动终极解决方案 [特殊字符]
  • 【奇点大会独家剧透】:2026最硬核AI图像生成技术TOP3——仅限前200名开发者获取的SDK调用密钥已生成
  • 免费游戏光标增强工具:三步让你的鼠标在游戏中永不消失
  • 雀魂Mod Plus终极指南:免费解锁全角色皮肤的完整教程
  • 微电网(两台)主从控制孤岛-并网平滑切换的分析。 分析了: 1.孤岛下VF控制 2.并网下PQ...
  • 如何用罗技鼠标宏实现绝地求生自动压枪:3分钟快速上手终极指南
  • 基于人工势场算法实现单长机+多僚机的编队运动与避障Matlab仿真
  • 保姆级教程:用VMware和CentOS 7为你的SystemVerilog项目搭建VCS2018与Verdi调试环境
  • 2026年大连高端海鲜消费再升级:这家海景海鲜餐厅凭综合实力登上口碑榜 - GrowthUME
  • NVIDIA GB200 SuperPOD实战指南:如何快速部署你的首个AI智算中心(附避坑清单)
  • PKHeX自动合法性插件:宝可梦数据管理的终极解决方案
  • 竞赛规则已定,就不要放水了
  • 梳理头皮养护加盟推荐公司,哪个口碑好一目了然 - 工业推荐榜
  • 2026年超全整理:十大矢量图素材网站推荐与样机素材网站推荐 - 品牌2026
  • 英国金融监管机构紧急评估Anthropic AI模型安全风险
  • Linux系统Photoshop安装终极指南:如何在Linux上免费运行Photoshop CC 2022
  • 【架构实战】系统容量评估与压测工具对比
  • 搞定安卓7.0+抓包难题:雷电模拟器9.0搭配Charles证书安装到系统凭据的保姆级教程
  • 2026年韩国美容展 InterCharm Beauty Expo Korea - 中国组团单位- 新天国际会展 - 新天国际会展
  • 从自然奇观到优化利器:RIME(雾凇优化算法)核心原理与实现解析
  • JPL 公式由来
  • 避坑指南:处理TROPOMI哨兵5号NC数据时,为什么你的ArcGIS多维工具读不出来?
  • 森林火灾烟雾识别 人工智能AI图像识别 yolo工业安放智能化 森林建筑安全防火监控智能化 深度学习火焰图像识别第10322期
  • Audiveris:如何让纸质乐谱在几分钟内变成数字音乐?
  • 云计算服务模式
  • 2026河南成考机构实力排行榜:翼程蝉联榜首,Top5深度测评 - 商业科技观察
  • 5分钟快速掌握Illustrator批量替换脚本:ReplaceItems.jsx完整使用指南