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等于[2的8次方](256.0)
正常Double尾数位[0101100100110000011010100110011010100110001001110100]
小数点前面[01011001]的数值为: 89
小数点后面[00110000011010100110011010100110001001110100]的数值为: 0.1891235499999766
最终结果是指数位的值256.0+小数点前面的值89+小数点后面的值0.1891235499999766=345.18912355
保留4位小数的结构345.1891
结论
- 小数点前面的数值有边界值(4503599627370496)*2,超过边界值将导致数据丢失(右位移的原因!)
- 如果存储纯小数位的值,则可认为存储无限小的值(左位移的原因)
- 为啥有效位是16位,就是因为52位二进制能够存储的最大值是16位