关于Java中的double类型数据

作者: Jadyn | 来源:发表于2016-02-27 13:19 被阅读843次

    在初学Java的时候,一般我们都会从基本的数据类型开始学习,而在基本数据类型中,我认为double类型是比较难理解的,并且在以后的学习或工作中,在double类型数据这遇到的坑也是极多的。例如下面的这样一个程序

    public static void main(String[] args) {
        System.out.println(2.0-1.1);
    }
    

    很多人会认为上面的程序会打印出0.9,但实际上,它打印的却是0.8999999999999999,这是为什么呢?
      首先介绍一下,十进制小数是怎么转换为二进制数的,举个例子3.75
      首先取出3.75的小数部分0.75,将其乘以要转换的进制的进制数,在这里也就是2,乘以2后得到结果1.5,取1.5的整数部分作为二进制小数的小数部分的第一位,再取1.5的小数部分0.5,乘以2后得到1.0,将1.0的整数部分1作为小数部分的第二位。以此类推,直到最后得到值0或形成无限循环。3.75转换为二进制数就是11.11
      但是我们知道在Java中double类型的数据占8个字节,所以对于无限循环的二进制小数我们只能取到它的近似值,就比如说1.1
      下面自己写了一个将double类型的数转换为二进制的程序

    public static void main(String[] args) {
        doubleToBinary(3.75000000);
        doubleToBinary(173.8125);
        doubleToBinary(1.10);
    }
    /**
     * 10任何次负幂都不能精确地被表示为一个长度有限的二进制数
     * @param d
     */
    public static void doubleToBinary(double d) {
        // 获得double类型的整数部分
        int intPart = (int) d;
        String tempStr = d + "";
        // 获得double类型数的字符串形式的小数部分
        String decimalPartStr = tempStr.substring(tempStr.indexOf("."));
        BigDecimal decimal = new BigDecimal(decimalPartStr);
        // 获得小数点后面的位数
        int precision = decimal.precision();
        // 最终的小数部分二进制字符串
        String decimalPartBinary = decimalPartToBinary(Double.parseDouble(decimalPartStr), precision);
        System.out.println(Integer.toBinaryString(intPart) + "." + decimalPartBinary);
    }
    
    /**
    * 将小数部分转换为二进制字符串
    * @param decimalPart 小数部分
    * @param precision   原始数的小数部分位数
    * @return
    */
    public static String decimalPartToBinary(double decimalPart, int precision) {
        // 转换为整型
        long decimalPartLong = (long) (Math.pow(10, precision) * decimalPart);
        String temp = "";
        int i = 0;
        while (precision > 0 && i < 64) {
            decimalPartLong = decimalPartLong * 2;
            temp += (int) (decimalPartLong / Math.pow(10, precision));
            //取得除第一位之后的数,并转换为字符串
            String str = (long) (decimalPartLong % Math.pow(10, precision)) + "";
            if (str.charAt(str.length() - 1) == '0' && str.length() != 1) {
                //去掉数最后面的0
                decimalPartLong = Long.parseLong(str.substring(0, str.length() - 1));
            } else {
                decimalPartLong = Long.parseLong(str);
            }
            if (decimalPartLong == 0) {
                break;
            }
            ++i;
            precision = (decimalPartLong + "").length();
            }
        return temp;
    }
    

    上面程序中的main方法输出的值为

    11.11
    10101101.1101
    1.0001100110011001100110011001100110011001100110011001100110011001
    

    通过上面的程序,我们很容易看到测试数据1.10在小数部分是无限循环的,1.10并不能精确地表示为一个double,因此它在Java中被表示为最接近它的double值。既然Java中是对double不能精确表示的数以近似值去存储的,那么在一些需要精确计算的地方就有可能出现错误,甚至产生意想不到的结果。比如说在业务中经常碰到的货币计算。那么如何解决这个问题呢?
      1.使用执行精确小数运算的BigDecimalAPI,但这里要说明一点,最好(一定)要用BigDecimal(String val)构造方法,而不要使用BigDecimal(double val),因为BigDecimal(double val)构造方法将会使用它的参数val的精确值返回一个BigDecimal,比如new BigDecimal(1.1)将会返回一个表示1.100000000000000088817841970012523233890533447265625BigDecimal
      2.使用整数类型进行计算,比如说intlong。将2.0元转换为200分去计算,这样System.out.println((200-110)+ "分")将会打印出正确的结果90分。
      总而言之呢,在需要精确计算的地方要避免使用floatdouble的数据。

    如果上面的程序有错的地方,请指出,共同学习,谢谢。

    相关文章

      网友评论

      • 13kcN4:Java中浮点类型遵循IEEE 754标准,为什么浮点类型不能表示精准值可以看一下标准,另外作者关于将浮点值转换为二进制表示的代码也有问题,你并不是将浮点数在内存中真实的二进制打印出来,可以看一下浮点数的Api,我记得有将浮点数转换为二进制表示的方法
        bcf4ae61a80d:@despair 。。。
        13kcN4: @bcf4ae61a80d 我是五楼😁😁😁
        bcf4ae61a80d:@despair 自古一楼是大神?
      • 又音心:我是女生,也学Java,关注了你,希望多写关于Java的东西,涨姿势 😄
        Jadyn:@5d637c6700b9 共同学习
      • Henio:有用
        Jadyn:@Henio 多谢肯定

      本文标题:关于Java中的double类型数据

      本文链接:https://www.haomeiwen.com/subject/wfpwkttx.html