java代码解析Double 浮点的存储结构

本文详细探讨了Java中double类型的存储结构,包括指数位和尾数位的表示,以及如何通过二进制转换理解double数值的计算。强调了指数超过52次方时可能导致数据丢失的问题,并通过实例展示了如何从二进制还原double数值的过程,帮助开发者更好地理解和避免潜在的计算误差。
摘要由CSDN通过智能技术生成

Java Double 存储结构解析

  • 阅读适合人群-> 有二进制基础,有代码基础的开发人员
  • 目的-> 弄清楚double的存储结构能够让你清楚了解double的数据边界,以及如何使用double进行计算,而不是动不动就拿Bigdecimal来开刷(非常不方便)
  • 目的-> 弄清楚double的存储结构助于你理解计算机结构和原理,以及帮助你避开代码的漏洞
  • 64字节存储结构
    在这里插入图片描述
/**
     * 分析double数据结构
     * ** 如果指数超过52(4503599627370496)次方,则尾数所有字段将被用作[小数前]的整数使用,此时实际数据将不会存储小数点后的数据
     * ** 不要存储大于4503599627370496 *2倍的数值,因为超过2倍,将使得尾数位无法表示2的52次方的值(4503599627370496)
     * 小于1无法使用,暂时未理解
     * **/
    public static Double doubleTest(String stringValue){
        Double normalDouble = new Double(stringValue);
        // 将double转成64位二进制字符串
        String normalString = Long.toBinaryString(Double.doubleToLongBits(normalDouble));
        // 前面有符号位,所以如果少于64位则用0补全
        int pad = 64-normalString.length();
        normalString = String.join("",Collections.nCopies(pad, "0")) + normalString;

        // 截取指数11位
        String powerString = normalString.substring(1, 12);
        // 指数位的数值
        Integer doublePower = Integer.valueOf(powerString, 2);
        // 减去1023,如果是负数则为小于1的值(2的-N次方)
        Integer power = doublePower-1023;

        // 截断获取尾数位52位
        String suffixString = normalString.substring(12, 64);
        String afterPoint = "0";
        String beforePoint = "0";
        // 判断尾数位中小数点前面的二进制和小数点后面的二进制

        if (power>=52){
            /** 如果大于52 则超出了截断尾数长度,则取52位全部位再补N次方的0 **/
            beforePoint = suffixString.substring(0, 52)  + String.join("",Collections.nCopies(power-52, "0"));
        }
        else if(power<0){
            /** 如果指数为负次方,则需要将小数点后方的小数位的二进制前面补N个0 **/
            afterPoint = String.join("",Collections.nCopies(Math.abs(power), "0")) + suffixString.substring(0, 52);
        }else{
            afterPoint = suffixString.substring(power, 52);
            beforePoint = suffixString.substring(0, power);
        }
        // 循环将小数点后方的二进制转换成double,按位为1的 1/2 1/4 1/8 1/16 依次相加
        Double afterValue = 0.0;
        for (int i =0; i< afterPoint.length();i++){
            char c = afterPoint.charAt(i);
            if (c == '1'){
                afterValue = afterValue + (double)1/Math.pow(2, (i+1));
            }
        }

        double v = (Math.pow(2, power)+afterValue+Long.valueOf(beforePoint,2));
        System.out.println("正常Double转二进制的值: " + Long.toBinaryString(Double.doubleToLongBits(normalDouble)));
        System.out.printf("正常Double调用toString转成后的值%s(%d)%n", normalDouble.toString(), normalDouble.longValue());
        System.out.printf("正常Double指数位[%s],值是:%d,减去1023等于[2的%d次方](%s)%n", powerString, doublePower, power, Math.pow(2, power));
        System.out.printf("正常Double尾数位[%s]%n", suffixString);
        System.out.printf("小数点前面[%s]的数值为: %d%n",beforePoint, Long.valueOf(beforePoint, 2));
        System.out.printf("小数点后面[%s]的数值为: %s%n",afterPoint, afterValue);
        System.out.printf("最终结果是指数位的值%s+小数点前面的值%s+小数点后面的值%s=%s%n",Math.pow(2, power), Long.valueOf(beforePoint, 2),afterValue, v);
        return v;
    }

    public static void main(String[] args) {
        Double v = doubleTest("345.18912355");
        System.out.println("保留4位小数的结构" + String.format("%.4f%n",v));
    }

结果:

正常Double转二进制的值: 100000001110101100100110000011010100110011010100110001001110100
正常Double调用toString转成后的值345.18912355(345)
正常Double指数位[10000000111],值是:1031,减去1023等于[28次方](256.0)
正常Double尾数位[0101100100110000011010100110011010100110001001110100]
小数点前面[01011001]的数值为: 89
小数点后面[00110000011010100110011010100110001001110100]的数值为: 0.1891235499999766
最终结果是指数位的值256.0+小数点前面的值89+小数点后面的值0.1891235499999766=345.18912355
保留4位小数的结构345.1891

结论

  • 小数点前面的数值有边界值(4503599627370496)*2,超过边界值将导致数据丢失(右位移的原因!)
  • 如果存储纯小数位的值,则可认为存储无限小的值(左位移的原因)
  • 为啥有效位是16位,就是因为52位二进制能够存储的最大值是16位
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值