踩坑了,BigDecimal 使用不当,造成 P0 事故!

2023-11-07

大家好,我是东哥!可能对于刚入门的新手不太理解P0的概念,下面简单解释一下。

P0属于最高级别事故,比如崩溃,页面无法访问,主流程不通,主功能未实现,或者在影响面上影响很大(即使bug本身不严重

目录

背景

我们在使用金额计算或者展示金额的时候经常会使用 BigDecimal,也是涉及金额时非常推荐的一个类型。

BigDecimal 自身也提供了很多构造器方法,这些构造器方法使用不当可能会造成不必要的麻烦甚至是金额损失,从而引起事故资损。

事故

接下来我们看下收银台出的一起事故。

问题描述

收银台计算商品金额报错,导致订单无法支付。

事故级别

P0

事故过程

如下:

  • 13:44,接到报警,订单支付失败,支付可用率降至 60%

  • 13:50,迅速回滚上线代码,恢复正常

  • 14:20,review 代码,预发布验证发现问题点

  • 14:58,修改问题代码上线,线上恢复

故障原因

BigDecimal 在金额计算中丢失精度。

原因分析

首先我们先用一段代码复现问题根源,如下所示:

public static void main(String[] args) {  
    BigDecimal bigDecimal=new BigDecimal(88);  
    System.out.println(bigDecimal);  
    bigDecimal=new BigDecimal("8.8");  
    System.out.println(bigDecimal);  
    bigDecimal=new BigDecimal(8.8);  
    System.out.println(bigDecimal);  
}

执行结果如下:

2c1cfd8a4b2d44133268bcec3538dab7.png

通过测试发现,当使用 double 或者 float 这些浮点数据类型时,会丢失精度,String、int 则不会,这是为什么呢?

我们点开构造器方法看下源码:

public static long doubleToLongBits(double value) {  
    long result = doubleToRawLongBits(value);  
    // Check for NaN based on values of bit fields, maximum  
    // exponent and nonzero significand.  
    if ( ((result & DoubleConsts.EXP_BIT_MASK) ==  
          DoubleConsts.EXP_BIT_MASK) &&  
         (result & DoubleConsts.SIGNIF_BIT_MASK) != 0L)  
        result = 0x7ff8000000000000L;  
    return result;  
}

问题就处在 doubleToRawLongBits 这个方法上,在 jdk 中 double 类(float 与 int 对应)中提供了 double 与 long 转换,doubleToRawLongBits 就是将 double 转换为 long,这个方法是原始方法(底层不是 java 实现,是 c++ 实现的)。

double 之所以会出问题,是因为小数点转二进制丢失精度。

bc3aec2d7518a96ad2fe2d93cd17b6fa.png

BigDecimal 在处理的时候把十进制小数扩大 N 倍让它在整数上进行计算,并保留相应的精度信息。

float 和 double 类型,主要是为了科学计算和工程计算而设计的,之所以执行二进制浮点运算,是为了在广泛的数值范围上提供较为精确的快速近和计算。

并没有提供完全精确的结果,所以不应该被用于精确的结果的场合。

当浮点数达到一定大的数,就会自动使用科学计数法,这样的表示只是近似真实数而不等于真实数。

当十进制小数位转换二进制的时候也会出现无限循环或者超过浮点数尾数的长度。

总结

所以,在涉及到精度计算的过程中,我们尽量使用 String 类型来进行转换。

正确用法如下:

BigDecimal bigDecimal2=new BigDecimal("8.8");
BigDecimal bigDecimal3=new BigDecimal("8.812");
System.out.println( bigDecimal2.compareTo(bigDecimal3));
System.out.println( bigDecimal2.add(bigDecimal3));

BigDecimal创建出来的是对象,我们不能用传统的加减乘除对其进行运算,必须使用他的方法,在我们数据库存储里,如果我们使用的是double或者float类型,需要进行来回的转换后进行计算,非常不方便。

工具分享

所以在这里整理出一个util类供大家使用。

import java.math.BigDecimal;

/**
 * @Author shuaige
 * @Date 2022/4/17
 * @Version 1.0
 **/
public class BigDecimalUtils {
    public static BigDecimal doubleAdd(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.add(b2);
    }
    public static BigDecimal floatAdd(float v1, float v2) {
        BigDecimal b1 = new BigDecimal(Float.toString(v1));
        BigDecimal b2 = new BigDecimal(Float.toString(v2));
        return b1.add(b2);
    }
    public static BigDecimal doubleSub(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.subtract(b2);
    }
    public static BigDecimal floatSub(float v1, float v2) {
        BigDecimal b1 = new BigDecimal(Float.toString(v1));
        BigDecimal b2 = new BigDecimal(Float.toString(v2));
        return b1.subtract(b2);
    }

    public static BigDecimal doubleMul(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.multiply(b2);
    }
    public static BigDecimal floatMul(float v1, float v2) {
        BigDecimal b1 = new BigDecimal(Float.toString(v1));
        BigDecimal b2 = new BigDecimal(Float.toString(v2));
        return b1.multiply(b2);
    }

    public static BigDecimal doubleDiv(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        // 保留小数点后两位 ROUND_HALF_UP = 四舍五入
        return b1.divide(b2, 2, BigDecimal.ROUND_HALF_UP);
    }
    public static BigDecimal floatDiv(float v1, float v2) {
        BigDecimal b1 = new BigDecimal(Float.toString(v1));
        BigDecimal b2 = new BigDecimal(Float.toString(v2));
        // 保留小数点后两位 ROUND_HALF_UP = 四舍五入
        return b1.divide(b2, 2, BigDecimal.ROUND_HALF_UP);
    }
    /**
     * 比较v1 v2大小
     * @param v1
     * @param v2
     * @return v1>v2 return 1  v1=v2 return 0 v1<v2 return -1
     */
    public static int doubleCompareTo(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return  b1.compareTo(b2);
    }
    public static int floatCompareTo(float v1, float v2) {
        BigDecimal b1 = new BigDecimal(Float.toString(v1));
        BigDecimal b2 = new BigDecimal(Float.toString(v2));
        return  b1.compareTo(b2);
    }
}

作者:树洞君

https://juejin.cn/post/7087404273503305736

公众号“Java精选”所发表内容注明来源的,版权归原出处所有(无法查证版权的或者未注明出处的均来自网络,系转载,转载的目的在于传递更多信息,版权属于原作者。如有侵权,请联系,笔者会第一时间删除处理!

最近有很多人问,有没有读者交流群!加入方式很简单,公众号Java精选,回复“加群”,即可入群!

Java精选面试题(微信小程序):3000+道面试题,包含Java基础、并发、JVM、线程、MQ系列、Redis、Spring系列、Elasticsearch、Docker、K8s、Flink、Spark、架构设计等,在线随时刷题!

------ 特别推荐 ------

特别推荐:专注分享最前沿的技术与资讯,为弯道超车做好准备及各种开源项目与高效率软件的公众号,「大咖笔记」,专注挖掘好东西,非常值得大家关注。点击下方公众号卡片关注

文章有帮助的话,点在看,转发吧!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

踩坑了,BigDecimal 使用不当,造成 P0 事故! 的相关文章

随机推荐

  • C++PrimerPlus 第五章 循环和关系表达式 - 5.1 for循环

    C PrimerPlus 第五章 循环和关系表达式 5 1 for循环 5 1 for循环 5 1 1 for循环的组成部分 5 1 1 1 表达式和语句 5 1 1 2 非表达式和语句 5 1 1 3 修改规则 5 1 2 回到for循环
  • osgEarth的Rex引擎原理分析(五十二)CGCS2000与WGS84坐标系的比较

    目标 四十六 中的119 文章 2000中国大地坐标系及其与WGS84的比较 对此有详细的比较 https max book118 com html 2017 0614 114928909 shtm 结论是 实现上相容的 仅在扁率上有微小差
  • 数据挖掘导论课后习题答案-第三章

    最近在读 Introduction to Data Mining 这本书 发现课后答案只有英文版 于是打算结合自己的理解将答案翻译一下 其中难免有错误 欢迎大家指正和讨论 侵删 第三章 优点 第一 颜色可以很容易地区分不同的部分 第二 看起
  • Less 使用介绍

    1 相关网站 Less 中文网 http lesscss cn W3Cschool Less 教程 https www w3cschool cn less Less 在线编译 https lesstester com 2 Less是什么 L
  • Git remote 远程仓库链接管理

    SVN 使用单个集中仓库作为开发人员的通信枢纽 通过在开发人员的工作副本和中央仓库之间传递变更集来进行协作 这与 Git 的分布式协作模型不同 后者为每个开发人员提供了自己的仓库副本 并具有自己的本地历史记录和分支结构 用户通常需要共享一系
  • 计算机原理入门(非常详细)从零基础入门到精通,看完这一篇就够了

    目录 一 计算机组成概述 1 计算机系统简介 1 1 早期的冯 诺依曼机 1 2 现代计算机组结构 2 计算机的组成 2 1 存储器 2 2运算器 2 3控制器 3 计算机的执行过程 4 计算机的性能指标 二 系统总线 1 总线的结构 2
  • windows环境开发工具常见问题

    从MAC OS切换到windows操作环境 手感差了很多 但不影响干活哈 遇到一些问题 顺手收集下来 1 win7 系统安装 POSTMAN 缺插件 打开空白 解决 1 缺插件问题 单独下载 再安装postman 2 打开postman空白
  • Python实现PSO粒子群优化算法优化LightGBM回归模型(LGBMRegressor算法)项目实战

    说明 这是一个机器学习实战项目 附带数据 代码 文档 视频讲解 如需数据 代码 文档 视频讲解可以直接到文章最后获取 1 项目背景 PSO是粒子群优化算法 Particle Swarm Optimization 的英文缩写 是一种基于种群的
  • UNDERSTANDING THE ATTENTION ECONOMY

    Platforms profit by maximizing the amount of time users spend looking at and clicking on advertisements Our Attention Is
  • 记事本vx小程序(待完善)

    基础功能 页面 image pages image 111 jpg 背景 textAreaDes 输入的内容 revise 是不是修改 id btnDown 保存按钮 if this data textAreaDes length 0 re
  • Jmeter怎么实现接口关联?

    用于接口测试时 后一个接口经常需要用到前一次接口返回的结果 应该如何获取前一次请求的结果值 应用于后一个接口呢 拿一个登录的例子来说明如何获取 1 打开jmeter 新建一个测试计划 在测试计划里新建一个线程组 新建一个登录的http请求
  • 数据结构视频教程 -《数据结构C++ 复旦大学》

    整个视频打包下载地址 史上最全的数据结构视频教程系列分享之 数据结构C 复旦大学 转载请保留出处和链接 更多优秀资源请访问 我是码农 数据结构是计算机科学与技术专业 计算机信息管理与应用专业 电子商务等专业的基础课 是十分重要的核心课程 所
  • 如何在Android 应用程序中实现 Excel 自动化功能?只需Aspose就搞定

    在本文中 将学习如何在您的 Android 应用程序中实现 Excel 自动化功能 阅读本文后 将能够以编程方式在您的 Android 应用程序中从头开始创建 Excel XLSX 或 XLS 文件 此外 本文还将介绍如何更新现有 Exce
  • Git第七讲 Eclipse 安装Git插件以及使用

    1 1git插件下载卸载 git插件下载可以直接help Install new software 输入git http download eclipse org egit updates 下载列表的git插件 或者在eclipse mak
  • Rotary Position Embedding (RoPE, 旋转式位置编码)

    RoPE为苏剑林大佬之作 最早应用于他自研的RoFormer Rotary Transformer 属于相对位置编码 效果优于绝对位置编码和经典式相对位置编码 出自论文 RoFormer Enhanced Transformer with
  • Python中的len()函数

    函数 len 1 作用 返回字符串 列表 字典 元组等长度 2 语法 len str 3 参数 str 要计算的字符串 列表 字典 元组等 4 返回值 字符串 列表 字典 元组等元素的长度 5 实例 5 1 计算字符串的长度 gt gt g
  • python编程 报错解决:“AttributeError: ‘str‘ object has no attribute ‘decode‘”

    简介 在做django项目遇到了如题的报错 通过搜索分析是encode decode的问题 我的decode encode并没有出现在我编写的代码中 而是在D python Lib site packages django db backe
  • web项目打包到上线教程_web项目打包,发布以及部署

    如何将 web 工程打包 war 和解包 war 1 打包 war 进入 工程 应用的根目录 比如 webapps myjspweb 2 把整个 web 应用打包为 myjspwar war 文件 命令如下 jar cvfmyjspweb
  • jmeter常用线程组设置策略

    目录 一 前言 二 单场景基准测试 1 介绍 2 线程组设计 3 测试结果 三 单场景并发测试 1 介绍 2 线程组设计 3 测试结果 四 单场景容量 爬坡测试 1 介绍 2 线程组设计 3 测试结果 五 混合场景容量 并发测试 1 介绍
  • 踩坑了,BigDecimal 使用不当,造成 P0 事故!

    大家好 我是东哥 可能对于刚入门的新手不太理解P0的概念 下面简单解释一下 P0属于最高级别事故 比如崩溃 页面无法访问 主流程不通 主功能未实现 或者在影响面上影响很大 即使bug本身不严重 目录 背景 事故 分析 总结 工具分享 背景