专栏名称: Java知音
专注于Java,推送技术文章,热门开源项目等。致力打造一个有实用,有情怀的Java技术公众号!
目录
相关文章推荐
FM1007福建交通广播  ·  抖音、快手、微信同日宣布:下架! ·  16 小时前  
FM1007福建交通广播  ·  抖音、快手、微信同日宣布:下架! ·  16 小时前  
小众消息  ·  新年第一个暴击,感觉自己要失业了? ·  昨天  
安天集团  ·  安天AVL ... ·  2 天前  
网信湖北  ·  AI“洗稿”造谣,如何用魔法打败魔法 ·  2 天前  
网信湖北  ·  AI“洗稿”造谣,如何用魔法打败魔法 ·  2 天前  
北京青年报  ·  DeepSeek横空出世,因为中国做对了这件事 ·  3 天前  
北京青年报  ·  DeepSeek横空出世,因为中国做对了这件事 ·  3 天前  
51好读  ›  专栏  ›  Java知音

2.01变成了2.00,金额使用 double 被坑了!

Java知音  · 公众号  · 互联网安全 科技自媒体  · 2024-12-04 10:05

主要观点总结

文章主要讲述测试人员在测试过程中发现的一个bug,即订单详情中的价格展示不准确,经过排查发现是double类型的精度问题导致的。文章通过实例详细解释了double类型在进行金额转换时存在的精度问题,并提供了使用BigDecimal类进行金额转换的方法。同时,文章还进行了性能测试,发现使用BigDecimal类的性能影响并不大。

关键观点总结

关键观点1: 文章描述了一个bug,即订单详情展示金额不准确。

文章中提到,测试人员发现了一个bug,订单详情中的价格展示不正确。经过排查,确定是double类型的精度问题导致的。

关键观点2: 文章解释了double类型在进行金额转换时存在的精度问题。

文章通过实例详细解释了double类型在进行金额转换时,由于计算机中的二进制补码存储方式,存在除不尽、精度丢失的问题。例如,2.01在二进制补码存储时会变成000000010.009999999999999787。

关键观点3: 文章提供了使用BigDecimal类进行金额转换的方法。

文章介绍了Java中的BigDecimal类,专门用于处理更高精度的浮点数。文章提供了将元和分互相转换的方法,并进行了测试,证明其精度和性能都是可靠的。

关键观点4: 文章进行了性能测试,证明使用BigDecimal类的性能影响并不大。

文章中进行了性能测试,测试结果显示,使用BigDecimal类进行金额转换的性能影响并不大。虽然首次耗时略高,但整体性能良好。


正文

前些日子,测试提过来一个bug,说下单价格应该是 2.01,但是在订单详情中展示了2.00元。我头嗡的一下子,艹,不会是因为double 的精度问题吧~

果不其然,经过排查代码,最终定位原因订单详情展示金额时,使用double 进行了金额转换,导致金额不准。

我马上排查核心购买和售后链路,发现涉及资金交易的地方没有问题,只有这一处问题,要不然这一口大锅非得扣我身上。

为什么 2.01 变成了 2.0

2.01等小数 在计算机中按照2进制补码存储时,存在除不尽,精度丢失的问题。

例如  2.01的补码为 000000010.009999999999999787 。正如十进制场景存在 1/3等无限小数问题,二进制场景也存在无限小数,所以一定会存在精度问题。

什么场景小数转换存在问题

for (int money = 0; money 10000; money++) {
   String valueYuan = String.format("%.2f", money * 1.0 / 100);

   int value = (int) (Double.valueOf(valueYuan) * 100);
   if (value != money) {
      System.out.println(String.format("原值: %s, 现值:%s", money, value));
   }
}

如上代码中,先将数字 除以 100,转为元, 精度为2位,然后将double 乘以100,转为int。在除以、乘以两个操作后,精度出现丢失。

我把1-10000 的范围测试一遍,共有573个数字出现精度转换错误。这个概率已经相当大了。

如何转换金额更安全?

Java 提供了 BigDecimcal 专门处理精度更高的浮点数。简单封装一下代码,元使用String表示,分使用int表示。提供两个方法实现 元和分的 互转。

public static String change2Yuan(int money) {
   BigDecimal base = BigDecimal.valueOf(money);
   BigDecimal yuanBase = base.divide(new BigDecimal(100));
   return yuanBase.setScale(2, BigDecimal.ROUND_HALF_UP).toString();
}

public static int change2Fen(String money) {
   BigDecimal base = new BigDecimal(money);

   BigDecimal fenBase = base.multiply(new BigDecimal(100));
   return fenBase.setScale(0, BigDecimal.ROUND_HALF_UP).intValue();
}
unset unset 测试 unset unset

测试0-1 亿 的金额转换逻辑,均成功转换,不存在精度丢失。

int error = 0;
long time = System.currentTimeMillis();
for (int money = 0; money 100000000; money++) {
   String valueYuan = change2Yuan(money);

   int value = change2Fen(valueYuan);
   if (value != money) {
      error++;
   }
}
System.out.println(String.format("时间:%s", (System.currentTimeMillis() - time)));
System.out.println(error);
unset unset 性能测试 unset unset

网上很多人说使用 BigDecimcal 存在性能影响,但是我测试性能还是不错的。可能首次耗时略高,大约2ms

总结

涉及金额转换的 地方,一定要小心处理,防止出现精度丢失问题。可以使用代码审查工具,查看代码中是否存在使用double 进行金额转换的代码, 同时提供 金额转换工具类。

作者:五阳
来源: juejin.cn/post/7399985723673837577

1. Java面试题精选阶段汇总,已更新450期~







请到「今天看啥」查看全文