Java中数值的加减乘除结果都是对的么?丨【奔跑吧!JAVA】

举报
jackwangcumt 发表于 2021/06/03 22:09:32 2021/06/03
【摘要】 Java编程中,如果计算1.0-0.42得到的结果却不是0.58,其结果可和我们预期的是不一样的。本文将探讨这种特殊情况,并给出商业计算场景下如果用BigDecimal类型来化解这些问题。

     计算机程序内部都是二进制表示的,日常的编程实战中,除了处理字符串外,大部分情况下,都在处理数值的计算,如计算多个值的和或平均值。在Java语言体系中,关于数值的类型有int,float,double,long等。

     对于有小数的数值,开发人员首先想到的是可以用float或者double表示。比如求1.0和0.42两个值的差,那么心算可得0.58,但是如果用Java来实际计算一下,那么其结果可能和我们预期的是不一样的。

    下面结合特殊场景,来给出Java常见的数值加减乘除运算示例,看看结果都是对的么?具体的示例代码如下:

package com.jyd;
//Java 数值计算精度问题探讨示例
public class NumDemo {
    public static void main(String[] args) {
        int a = 10;
        int c0 = a / 3 ;
        System.out.println(c0);//3,只保留整数
        //float和double大部分情况精确的,但有时会计算错误
        // 在商业计算上(如金融、银行,财务),不建议使用。
        float b = 0.1f;
        float c2 = b / 3.0f;//0.033333335
        System.out.println(c2);
        float b2 = 20019999;
        System.out.println(b2); //2.002E7
        double d = 45.9D;
        double c3 = d / 3.0D ;
        System.out.println(c3);//15.299999999999999 (not 15.3)

        double e = 2001299.32D;
        double e2 = 0.11D;
        System.out.println(e + e2); //2001299.4300000002
        System.out.println(1.0 - 0.42);//0.5800000000000001
        System.out.println(4.015 * 100);//401.49999999999994
        System.out.println(0.01 + 0.05);//0.060000000000000005
    }
}

      上述示例代码中,int类型的数值10,/ 除以3,实际返回的是int类型,即3,只保留整数部分。这个计算结果只要我们知道,一般都可以理解。Java中的浮点类型(如float和double)采用 IEEE 754 标准。float类型的值0.1除以3.0时,返回的还是float类型的值,按照数学角度来说,应该返回0.3333...(循环),但是Java程序返回0.033333335。float类型长度为32位,其中 1 位表示数字的符号,用 8 位表示指数,用 23 位表示小数部分。感觉这个长度有的长,但是实际使用起来精度还是不够。即使是64位的double也是如此。

     示例中,20019999这个float类型的值,输出后却是2.002E7,即20020000,真是奇乎怪哉!,如果这个是银行账户的金额,1个账户多1元,那么几亿个户头则多出了几个亿的金额,足见这个计算精度的问题是在特殊场景下,是一个不可接受的方案。 其他的示例涉及到加法、减法、乘法,也同样会出现计算错误的问题。
      按照官网的说法,float和double一般用于工程计算,而不能应用商业计算,如银行,财务等。那么商业计算上,该如何解决此问题呢?此时需要用到一个特殊的类型,为BigDecimal。此类型是一个精确的类型,下面给出示例:

package com.jyd;

import java.math.BigDecimal;
import java.math.MathContext;

//Java 数值计算精度问题探讨示例
public class BigNumDemo {
    public static void main(String[] args) {

        BigDecimal b = new BigDecimal("0.1");
        BigDecimal b1 = new BigDecimal("3.0");
        BigDecimal c2 =  b.divide(b1,10,BigDecimal.ROUND_DOWN) ;//0.0333333333
        System.out.println(c2);
        BigDecimal b2 = new BigDecimal(""+20019999);
        System.out.println(b2); //20019999
        BigDecimal d = new BigDecimal("45.9");
        BigDecimal d2 = new BigDecimal("3.0");
        BigDecimal c3 = d.divide(d2,10,BigDecimal.ROUND_CEILING) ;
        System.out.println(c3);//15.3000000000

        BigDecimal e  = new BigDecimal("2001299.32");
        BigDecimal e2 = new BigDecimal("0.11");
        System.out.println(e.add(e2)); //2001299.43

        BigDecimal e3  = new BigDecimal("1.0");
        BigDecimal e4 = new BigDecimal("0.42");
        System.out.println(e3.subtract(e4));//0.58

        BigDecimal e5  = new BigDecimal("4.015");
        BigDecimal e6 = new BigDecimal("100");
        System.out.println(e5.multiply(e6, MathContext.UNLIMITED));//401.500

        BigDecimal e7  = new BigDecimal("0.01");
        BigDecimal e8 = new BigDecimal("0.05");
        System.out.println(e7.add(e8));//0.06

        BigDecimal e9  = new BigDecimal("999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999");
        System.out.println(e9.add(e3));//1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0
    }
}

       BigDecimal有多种构造函数,常用的有2种。建议使用String构造方式,不建议使用double构造方式,可能会出现精度丢失问题。通过上述示例可以看出,商业计算应该使用 BigDecimal类型来完成,虽然在加减乘除这块使用起来有点不够简便,但是仍然比较容易理解。且操作字符串,也更容易操作。在乘除计算中,强制需要制定额外的参数,比如明确小数个数,四舍五入的模式等。

     最后,Java编程的细节还有很多,比如不同精度的数值的赋值和转换问题,float和double为什么会导致数据精度的丢失等,可以参考官网文档或其他资料。这里不再赘述。

   【奔跑吧!JAVA】有奖征文火热进行中:https://bbs.huaweicloud.com/blogs/265241

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。