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

Java 用 double 算钱算出 0.30000000000000004:BigDecimal 正确姿势与坑

Java 用 double 算钱算出 0.30000000000000004:BigDecimal 正确姿势与坑

写电商、财务、账单系统时,几乎每个 Java 程序员都被这行代码坑过:

System.out.println(0.1+0.2);// 0.30000000000000004

明明是小学算术,结果多出一串诡异的小数。如果你拿double存过金额,迟早会在对账时收到「差了一分钱」的告警。这篇讲清楚为什么会这样、BigDecimal怎么用才对,以及它自己也藏着的几个坑。

为什么 double 算不准钱

double/float是 IEEE 754 二进制浮点数。计算机用二进制表示小数,而0.10.2这种十进制小数在二进制里是无限循环的,就像十进制里的1/3 = 0.333...永远写不完。存进 64 位double时只能截断,于是存的其实是一个非常接近但不等于0.1的值,累加起来误差就暴露了。

// 误差在比较时最致命doubletotal=0.1+0.2;if(total==0.3){System.out.println("相等");}else{System.out.println("不相等");// 实际走这里}

结论:只要涉及金额、利率、税费这类需要精确十进制的场景,永远不要用 double/float。BigDecimal,或者用「以分为单位的 long」(下面会讲)。

BigDecimal 的第一个坑:别用 double 构造

很多人知道要用BigDecimal,但第一步就错了:

BigDecimala=newBigDecimal(0.1);// 错!System.out.println(a);// 0.1000000000000000055511151231257827021181583404541015625

new BigDecimal(double)接收的是那个已经不精确的 double,BigDecimal 只是忠实地把这个二进制误差原样展开,反而更难看。

正确做法是用字符串构造,或者用valueOf:

BigDecimala=newBigDecimal("0.1");// 对:字符串精确BigDecimalb=BigDecimal.valueOf(0.1);// 对:valueOf 内部走 Double.toString,结果是 "0.1"System.out.println(a.add(newBigDecimal("0.2")));// 0.3

记住规则:构造 BigDecimal 用 String,别用 double。BigDecimal.valueOf(double)也安全,因为它内部先把 double 转成规整的字符串。

第二个坑:除法不指定精度会抛异常

BigDecimala=newBigDecimal("1");BigDecimalb=newBigDecimal("3");System.out.println(a.divide(b));// 抛 ArithmeticException: Non-terminating decimal expansion

1/3是无限小数,BigDecimal 不知道你要保留几位,直接摆烂抛异常。除法必须指定保留位数和舍入模式:

// 保留 2 位小数,四舍五入(HALF_UP)BigDecimalresult=a.divide(b,2,RoundingMode.HALF_UP);System.out.println(result);// 0.33

舍入模式常用这几个:

  • HALF_UP:四舍五入(日常最常用)。
  • HALF_EVEN:银行家舍入,0.5时向偶数靠,长期统计更无偏,金融系统常用。
  • DOWN:直接截断(算利息时对用户不利,慎用)。

第三个坑:equals 比较值相等会翻车

BigDecimalequals连精度(scale)一起比,这经常不是你想要的:

BigDecimalx=newBigDecimal("1.0");BigDecimaly=newBigDecimal("1.00");System.out.println(x.equals(y));// false!scale 不同(1 位 vs 2 位)System.out.println(x.compareTo(y)==0);// true:只比数值

要判断「数值是否相等」,一律用compareTo(...) == 0,不要用equals。这个坑在写单元测试断言时尤其容易踩。

第四个坑:BigDecimal 是不可变的

BigDecimalString一样不可变,所有运算都返回新对象,不改原值:

BigDecimalprice=newBigDecimal("100");price.add(newBigDecimal("50"));// 返回值被丢弃了!price 还是 100System.out.println(price);// 100price=price.add(newBigDecimal("50"));// 对:接住返回值System.out.println(price);// 150

漏接返回值是新手高频 bug,写的时候留个心眼:BigDecimal 的每个运算都要x = x.op(...)

一个完整的金额计算示例

算一个订单:单价 19.99,数量 3,打 8.5 折,保留 2 位:

importjava.math.BigDecimal;importjava.math.RoundingMode;publicclassOrderCalc{publicstaticvoidmain(String[]args){BigDecimalprice=newBigDecimal("19.99");BigDecimalqty=newBigDecimal("3");BigDecimaldiscount=newBigDecimal("0.85");BigDecimaltotal=price.multiply(qty)// 59.97.multiply(discount)// 50.9745.setScale(2,RoundingMode.HALF_UP);// 50.97System.out.println("应付:"+total);// 应付:50.97}}

setScale(2, RoundingMode.HALF_UP)是收尾定妆的关键:把中间过程的多余小数按业务规则规整到「分」。中间计算可以保留高精度,最后一步再 setScale,不要每一步都截断,否则误差会累积。

什么时候用 long 存分

如果金额永远是「元 + 分」两位精度、不涉及复杂的除法分摊,很多高性能系统干脆用long(或更小单位),彻底绕开浮点:

longpriceCents=1999;// 19.99 元longtotal=priceCents*3;// 5997 分,整数运算精确又快// 展示时再 total / 100 + "." + total % 100

long存分的优点是快、无精度问题、比较简单;缺点是遇到「按比例分摊」「多币种、多位小数」时不如BigDecimal灵活。经验法则:简单收付款用 long 存分,涉及税率、利率、分摊、汇率的用 BigDecimal。

小结

  • double/float是二进制浮点,存不下0.1这类十进制小数,算钱一律禁用
  • 构造BigDecimal用字符串(new BigDecimal("0.1"))或valueOf,绝不用new BigDecimal(0.1)
  • 除法必须给精度和舍入模式(divide(b, 2, RoundingMode.HALF_UP)),否则无限小数直接抛异常。
  • 比较数值用compareTo() == 0,别用equals(它连 scale 一起比)。
  • BigDecimal不可变,每步运算都要x = x.op(...)接住返回值。
  • 精度固定的简单收付款可以用long存分,更快更省心。

一句话记忆:算钱,构造用 String、除法给精度、比较用 compareTo、收尾用 setScale——这四条守住,对账再也不差一分钱。

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

相关文章:

  • 深入解析ADS5546:14位190MSPS高速ADC设计精髓与实战指南
  • Loss函数的地雷:针对类别不平衡,Focal Loss是如何碾压CrossEntropy的
  • 青岛卖黄金怎么选?从市场调研到易奢福一站变现(附门店与常见问答) - 遁地的c
  • 创业一年的技术领导力反思:从个人贡献者到组织构建者的转变
  • 汽车腰靠实力厂家怎么找?电话在此 - 城刊速递
  • Java本地部署Gemma 4:Maven一键集成方案解析
  • 2026八大满意度调查问卷工具横评:AI选型指南 - 企业数字化Rock
  • Open-Manus开源多智能体工具部署指南
  • 深入解析TI LM3561 LED驱动芯片:从同步升压原理到手机闪光灯实战设计
  • UAVStack源码解析:核心模块实现原理与设计模式
  • 影刀RPA场景全攻略:办公电商社媒自动化一网打尽
  • Unity新手必备:5款高效插件提升开发效率与项目质量
  • 未来已来:搭载AI算法的【动态检重】【分拣设备】与传统【常规检重秤】迭代优势及厂商巡礼 - 速递信息
  • Mechvibes键盘音效模拟器:3分钟打造专属打字体验
  • DoraCMS安全防护实战:纵深防御与NoSQL注入防范
  • 端侧AI推理7月趋势:模型压缩、推理框架与硬件的协同进化
  • Cat-Catch浏览器嗅探扩展完整指南:技术深度解析与实战应用
  • 公证一般需要多少钱?别被乱收费!快看这篇 - 信息快递
  • 大连闲置黄金怎么卖不亏钱?逸程回收 724 小时估价,当场转账 - 融媒生活
  • 天津黄金回收门店怎么甄别?正规资质筛查完整教程 - 日常比对手册
  • 告别公有云依赖!Helix私有AI Stack让企业数据安全与AI能力兼得
  • 算力液冷用雷达多普勒流量计选型,国产高性价比品牌推荐 - 仪表人叶工
  • 628. 三个数的最大乘积(206.07.26)
  • 杭州翡翠回收哪家强?易奢福30年老店获评“杭州测评第一”,鉴定师都是GIA持证上岗? - 奢侈品回收探店ing
  • RTL88x2BU无线网卡驱动:3个高级配置技巧与Linux环境完全指南
  • CNN-LSTM混合模型在时序预测中的优化与应用
  • 2026黄金回收店铺选择-沈阳和平区正规黄金回收门店一般具备哪些明显特征? - 融媒生活
  • C/C++图形化贪吃蛇实战:EasyX库应用与游戏循环解析
  • 2026北京朝阳区附近洒水车租赁服务商精选指南 - 谁都没有我好看
  • AI工具链7月选型总结:LangChain、向量数据库与LLMOps工具生态