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

如何用SoulChat2.0快速克隆你的心理咨询师?手把手教你搭建AI数字孪生体 本文详细介绍了如何利用SoulChat2.0框架快速构建心理咨询师的AI数字孪生体。通过其核心PsyDT技术,仅需少量真实案例即可合成风格数据并微调大模型,从而低成本地创建能高度模仿特定咨询师语言风格和专业技术的AI分身,适用于预约访谈、心理教育等辅助场景,实现高效的人机协作。 阅读详情

来源:juejin.cn/post/7087404273503305736

背景

我们在使用金额计算或者展示金额的时候经常会使用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);
}

执行结果如下:

4f67fc86f375c9ec7afa8a0bca7b14d3.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之所以会出问题,是因为小数点转二进制丢失精度。BigDecimal在处理的时候把十进制小数扩大N倍让它在整数上进行计算,并保留相应的精度信息

62c1dde6576ae19d5c0f1e6d6f850adf.png
  • 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);
    }
}
1.2 博图TIA的轨迹(Trace)功能介绍 在TIA中“轨迹” ( Traces ) 能来记录设备变量并对记录进行评估,对有些故障排查很有帮助,本文进行介绍 阅读详情

相关推荐

国科GK7202V300芯片开发避坑指南:从uboot编译到SDK配置的12个实战问题解决

本文针对国科GK7202V300芯片开发过程中的常见难题,提供从uboot编译、SDK配置到系统部署的实战解决方案。详细解析了开发环境搭建、内核编译、驱动模块加载、根文件系统构建及烧写部署等12个关键环节的避坑指南,帮助开发者高效解决编译错误、配置冲突等典型问题,提升开发效率。

sunny的博客 237

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

大家好,我是东哥!可能对于刚入门的新手不太理解P0的概念,下面简单解释一下。P0属于最高级别事故,比如崩溃,页面无法访问,主流程不通,主功能未实现,或者在影响面上影响很大(即使bug本身不严重)。目录背景事故分析总结工具分享背景我们在使用金额计算或者展示金额的时候经常会使用 BigDecimal,也是涉及金额时非常推荐的一个类型。BigDecimal 自身也提供了很多构造...

Java精选 1016

Layout常见错误汇总-不定时更

1、在做LVS时,出现中断错误 解决方法: 重新建立新的schematic和layout文件,对应好其中的元器件,同时两个文件的名字也要对应好“schematic”和“layout”,其他名字的话软件找不到如何对应。

weixin_38753095的博客 4万+

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

BigDecimal 创建出来的是对象,我们不能用传统的加减乘除对其进行运算,必须使用他的方法,在我们数据库存储里,如果我们使用的是 double 或者 float 类型,需要进行来回的转换后进行计算,非常不方便。float 和 double 类型,主要是为了科学计算和工程计算而设计的,之所以执行二进制浮点运算,是为了在广泛的数值范围上提供较为精确的快速近和计算。通过测试发现,当使用 double 或者 float 这些浮点数据类型时,会丢失精度,String、int 则不会,这是为什么呢?

程序员高级码农的博客 377

gnuradio+b210实现FM收音机

gnuradio+b210实现FM收音机环境介绍FM接收1 Flow Graph2 变量参数FM发射1 Flow Graph 环境介绍 Name Version gnuradio 3.7.14.0 uhd 3.15.0.0 ubuntu 18.04 usrp b210 参考 https://www.ijedr.org/papers/IJEDR1702038.pdf Audio File Transmission using GNURADIO and USRP FM接

u011745228的博客 5129

基于LangGraph的智能报告生成平台项目分析

不知道你是否知道或者了解OpenAI and Gemini Deep Research。他们是一种能够根据输入问题进行规划、结合网络搜索获取信息并最终呈现结果的研究工具或技术。那这样research是如何实现的呢?最近刚好看到一个实现类似功能的开源项目: open_deep_search。本文将基于该项目进行代码层面剖析。

u011160113的博客 925
上一篇: 快速搭建一个网关服务,动态路由、鉴权看完就会(含流程图)
下一篇: 7 种提升 Spring Boot 吞吐量神技!
Java思维导图
博客等级 码龄10年 306粉丝 71原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值