数值计算:注意精度、舍入和溢出问题

简介: 在《Effective Java》这本书中也提到这个原则,float和double只能用来做科学计算或者是工程计算,在商业计算中我们要用java.math.BigDecimal

数值计算:注意精度、舍入和溢出问题

在这里插入图片描述

在《Effective Java》这本书中也提到这个原则,float和double只能用来做科学计算或者是工程计算,在商业计算中我们要用java.math.BigDecimal


1. Double的坑

四则运算:

public static void main(String[] args) throws Exception {
        System.out.println(0.1+0.2);
        System.out.println(1.0-0.8);
        System.out.println(4.015*100);
        System.out.println(123.3/100);
        double amount1 = 2.15;
        double amount2 = 1.10;
        if (amount1 - amount2 == 1.05)
            System.out.println("OK");
    }

在这里插入图片描述
可以看到,输出结果和我们预期的很不一样。比如,0.1+0.2 输出的不是 0.3 而是0.30000000000000004;再比如,对 2.15-1.10 和 1.05 判等,结果判等不成立。


  • 出现这种问题的主要原因是,计算机是以二进制存储数值的,浮点数也不例外。Java 采用

了IEEE 754 标准实现浮点数的表达和运算,你可以通过这里查看数值转化为二进制的
结果。

  • 比如,0.1 的二进制表示为 0.0 0011 0011 0011… (0011 无限循环),再转换为十进制就

是 0.1000000000000000055511151231257827021181583404541015625。对于计算
机而言,0.1 无法精确表达,这是浮点数计算造成精度损失的根源。

在《Effective Java》这本书中也提到这个原则,float和double只能用来做科学计算或者是工程计算,在商业计算中我们要用java.math.BigDecimal


#### BigDecimal 四则运算:


public static void main(String[] args) throws Exception {

        System.out.println(new BigDecimal(0.1).add(new BigDecimal(0.2)));
        System.out.println(new BigDecimal(1.0).subtract(new BigDecimal(0.8)));
        System.out.println(new BigDecimal(4.015).multiply(new BigDecimal(100)));
        System.out.println(new BigDecimal(123.3).divide(new BigDecimal(100)));

    }

在这里插入图片描述

可以看到 ,运算还是不精确,只不过是精度高了,使用 BigDecimal 表示和计算浮点数,且务必使用字符串的构造方法来初始化


public static void main(String[] args) throws Exception {

        System.out.println(new BigDecimal("0.1").add(new BigDecimal("0.2")));
        System.out.println(new BigDecimal("1.0").subtract(new BigDecimal("0.8")));
        System.out.println(new BigDecimal("4.015").multiply(new BigDecimal("100")));
        System.out.println(new BigDecimal("123.3").divide(new BigDecimal("100")));

    }

在这里插入图片描述


2. 考虑浮点数舍入和格式化的方式

关于浮点类型的四舍五入 也是千奇百怪,让人莫不着头脑


 public static void main(String[] args) throws Exception {

        double num1 = 3.35;
        float num2 = 3.35f;
        System.out.println(String.format("%.1f", num1));//四舍五入
        System.out.println(String.format("%.1f", num2));

    }

在这里插入图片描述

如果我们希望使用其他舍入方式来格式化字符串的话,可以设置 DecimalFormat


 public static void main(String[] args) throws Exception {

        double num1 = 3.35; float num2 = 3.35f;
        DecimalFormat format = new DecimalFormat("#.##");
        format.setRoundingMode(RoundingMode.DOWN);
        System.out.println(format.format(num1));
        format.setRoundingMode(RoundingMode.DOWN);
        System.out.println(format.format(num2));

    }
  

在这里插入图片描述
因此,即使通过 DecimalFormat 来精确控制舍入方式,double 和 float 的问题也可能产
生意想不到的结果,所以浮点数避坑第二原则:浮点数的字符串格式化也要通过BigDecimal 进行。


public static void main(String[] args) throws Exception {

        BigDecimal num1 = new BigDecimal("3.35");
        BigDecimal num2 = num1.setScale(1, BigDecimal.ROUND_DOWN);
        System.out.println(num2);
        BigDecimal num3 = num1.setScale(1, BigDecimal.ROUND_HALF_UP);
        System.out.println(num3);

    }

在这里插入图片描述

3. BigDecimal不能使用equals

案例 :

   public static void main(String[] args) throws Exception {

        BigDecimal a = new BigDecimal(0.00);
        BigDecimal b = new BigDecimal(0);

        boolean result = a.equals(b);
        System.out.println("a equals b -->" + result);

        BigDecimal c = new BigDecimal("0.00");
        BigDecimal d = new BigDecimal("0");

        boolean result1 = c.equals(d);
        System.out.println("c equals d -->" + result1);

    }

结果:

a equals b -->true
c equals d -->false

我们来看下 BigDecimal 的 equals 方法 源码,equals 比较的是 BigDecimal 的 value 和 scale,1.0 的 scale 是 1,1 的scale 是 0,所以结果一定是 false:

/**
     * Compares this {@code BigDecimal} with the specified
     * {@code Object} for equality.  Unlike {@link
     * #compareTo(BigDecimal) compareTo}, this method considers two
     * {@code BigDecimal} objects equal only if they are equal in
     * value and scale (thus 2.0 is not equal to 2.00 when compared by
     * this method).
     *
     * @param  x {@code Object} to which this {@code BigDecimal} is
     *         to be compared.
     * @return {@code true} if and only if the specified {@code Object} is a
     *         {@code BigDecimal} whose value and scale are equal to this
     *         {@code BigDecimal}'s.
     * @see    #compareTo(java.math.BigDecimal)
     * @see    #hashCode
     */
    @Override
    public boolean equals(Object x) {
        if (!(x instanceof BigDecimal))
            return false;
        BigDecimal xDec = (BigDecimal) x;
        if (x == this)
            return true;
        if (scale != xDec.scale)
            return false;
        long s = this.intCompact;
        long xs = xDec.intCompact;
        if (s != INFLATED) {
            if (xs == INFLATED)
                xs = compactValFor(xDec.intVal);
            return xs == s;
        } else if (xs != INFLATED)
            return xs == compactValFor(this.intVal);

        return this.inflated().equals(xDec.inflated());
    }

如果我们希望只比较 BigDecimal 的 value,可以使用 compareTo 方法

 public static void main(String[] args) throws Exception {
        
        BigDecimal c = new BigDecimal("0.00");
        BigDecimal d = new BigDecimal("0");

        boolean result2 = c.compareTo(d) == 0;
        System.out.println("c compareTo d -->" + result2);

    }

结果:

c compareTo d -->true

4. 数值溢出问题

数值计算还有一个要小心的点是溢出,不管是 int 还是 long,所有的基本数值类型都有超
出表达范围的可能性。

对Long 的最大值 进行 + 1

   public static void main(String[] args) throws Exception {
        long l = Long.MAX_VALUE;
        System.out.println(l + 1);
        System.out.println(l + 1 == Long.MIN_VALUE);

    }

结果: 输出结果是一个负数,因为 Long 的最大值 +1 变为了 Long 的最小值:

-9223372036854775808
true

改进:

 public static void main(String[] args) throws Exception {
        BigInteger i = new BigInteger(String.valueOf(Long.MAX_VALUE));
        System.out.println(i.add(BigInteger.ONE).toString());
 }

结果 :

9223372036854775808
  • 通过 BigInteger 对 Long 的最大值加 1 一点问题都没有
相关文章
|
算法 计算机视觉
YOLOv8改进 | 损失函数篇 | 最新ShapeIoU、InnerShapeIoU损失助力细节涨点
YOLOv8改进 | 损失函数篇 | 最新ShapeIoU、InnerShapeIoU损失助力细节涨点
798 2
|
存储 JSON 监控
grafana/promtail 作用
Grafana/ Promtail 是一个日志的收集、存储和可视化工具。它主要用于监控和分析分布式系统的日志数据。Grafana是一个开源的数据可视化工具,而Promtail是Grafana的一个组件,用于收集和发送日志数据。Grafana/ Promtail具有以下作用: 1. 日志收集:Promtail可以从不同的源收集日志数据,如系统日志、应用日志等,并将其发送到中央存储库,如Elasticsearch、Loki等。 2. 自动发现和标记:Promtail可以自动发现并标记正在运行的容器和主机,以便在收集日志时进行标识和过滤。 3. 丰富的日志格式支持:Promtail支持多种常见
597 0
|
8月前
|
机器学习/深度学习 人工智能 自然语言处理
《探秘DeepSeek优化器:解锁模型训练的高效密码》
DeepSeek作为备受瞩目的大语言模型,在自然语言处理任务中表现出色,其优化器功不可没。该优化器具备自适应学习率调节机制,能灵活应对训练动态,确保快速收敛与稳定;采用高效梯度处理技术,防止梯度爆炸或消失,支持稀疏梯度更新,减少计算开销;完美适配分布式训练环境,降低通信开销,加速多节点协同工作;并与模型架构深度适配,充分发挥潜力。这些特点共同推动DeepSeek在复杂任务中取得优异表现。
178 1
|
11月前
|
机器学习/深度学习 人工智能 芯片
【AI系统】超异构计算
本文探讨了计算机架构发展的黄金十年,重点介绍了异构计算和超异构计算的概念及其在AI芯片发展中的应用。文章首先回顾了AI芯片发展的三个阶段,随后详细阐述了异构计算的优势和应用场景,如性能飞跃、灵活定制、降低成本和降低功耗。接着,文章分析了超异构计算的出现背景、基本特征及其面临的挑战,包括软件层的复杂性和硬件定义软件与软件定义硬件之间的权衡。最后,展望了超异构计算的未来,强调了跨平台统一计算架构的重要性,以及构建开放生态系统的必要性。
526 5
|
监控 安全 Cloud Native
【云原生之Docker实战】使用Docker部署Ward服务器监控工具
【5月更文挑战第11天】使用Docker部署Ward服务器监控工具
368 4
|
人工智能 自然语言处理 vr&ar
ControlNet作者重磅新作LayerDiffusion,AI绘画能分图层了
【2月更文挑战第13天】ControlNet作者重磅新作LayerDiffusion,AI绘画能分图层了
323 2
ControlNet作者重磅新作LayerDiffusion,AI绘画能分图层了
|
搜索推荐 Windows
让你的电脑准时“打个盹”:Win10定时休眠
木头左教你设置Windows 10任务计划程序,让电脑定时休眠,节约能源又呵护健康。首先确保休眠功能开启,然后在任务计划程序创建新任务,命名如“定时休眠”,设置触发时间和操作(cmd.exe /c shutdown -h)。可高级定制,如条件触发或异常处理。跟着步骤实践,解决常见问题,打造个性化自动休眠计划。记得谨慎操作哦!
|
安全 Java 数据库
Spring Boot中集成 Shiro
本节主要介绍了 Shiro 安全框架与 Spring Boot 的整合。先介绍了 Shiro 的三大核心组件已经它们的作用;然后介绍了 Shiro 的身份认证、角色认证和权限认证;最后结合代码,详细介绍了 Spring Boot 中是如何整合 Shiro 的,并设计了一套测试流程,逐步分析 Shiro 的工作流程和原理,让读者更直观地体会出 Shiro 的整套工作流程。Shiro 使用的很广泛,希望读者将其掌握,并能运用到实际项目中。
|
机器学习/深度学习 人工智能 自然语言处理
大语言模型的主流应用领域
大语言模型在多个领域都发挥着重要作用,从新闻报道到金融分析,从智能家居到在线教育、自然语言处理、智能客服、情感分析,它们都在推动技术进步并改善人们的生活质量。
820 1
|
缓存 NoSQL Java
基于SpringBoot+Redis的前后端分离外卖项目-苍穹外卖微信小程序端(十一)
基于SpringBoot+Redis的前后端分离外卖项目-苍穹外卖微信小程序端(十一)