Java浮点数存储格式
JAVA中浮点数有两个基础类型:floatdoublefloat占据4个字节,double
8个字节。下面将以float类型为例,介绍浮点数的存储方式。double类型和
float类型的存储方式雷同。
 
1.浮点数的存储方式
 
浮点数的存储格式比较特殊,下图是4字节的float变量的存储示意图:
 
根据IEEE754浮点数表示标准,一个float变量在存储中由三部分组成,分别是:
符号位:1(31),表示float的正负,0为正,1为负
幂指数:8(23-30),表示2进制权的幂次
有效位:23(0-22),表示有效数字
 
2.浮点数的取值范围
float的存储中,有4个特殊的存储值,分别是:
0x7f800000:正无穷大,Float.intBitsToFloat()打印显示为infinity
0xff800000:负无穷大,打印显示为-infinity
0x00000000:正零,打印显示为0.0
0x80000000:负零,打印显示为-0.0
注意,在Java,infinity!=-infinity,但是0.0==-0.0
以上4个特殊存储值将float的存储分为4个段
[0x00000001,0x7f7fffff]:正float数,共2^31-2^23-1
[0x7f800001,0x7fffffff]:非数字,打印显示NaN,共2^23-1
[0x80000001,0xff7fffff]:负float数,共2^31-2^23-1
[0xff800001,0xffffffff]:非数字,打印显示NaN,共float几个字节多少位2^23-1
3.浮点数的格式转换
bits表示一个整数,其存储空间为4字节,下面我们求出这4个字节表示的float
类型数字为多少。
int s = ((bits>>31) == 0)?1:-1;  //取出1bit符号位
int e = ((bits>>23) & 0xff); //取出8bit的幂指数
//取出23位有效位
int m = (e==0)?((bits & 0x7fffff) << 1):
               ((bits & 0x7fffff) | 0x800000);
则该存储空间表示的浮点数为 s*m*2^(e-150)
分析:
[0x00000001,0x007fffff]:相应实数范围为[(2^-149),(2^-126)-(2^-149)],即
大约为[1.4E-45,1.2E-38],离散间隔固定为(2^-149)即约为1.4E-45
实数个数为2^23个。
[0x00800000,0x7f7fffff]:相应实数范围为[(2^-126),(2^128 - 2^104)],即大约为
[1.2E-38,3.4E38],以后每增加2^23个实数,离散间隔增大一倍。

所以,浮点数设计完成了整个A=[0x00000000,0x7f7fffff]离散空间到B=[0.0,3.4E38]区间
部分值的一个映射,该映射具有以下属性:
<1>B中被映射实数的初始间隔为c=2^-149,并且每经过2^23个数间隔变为c=2*c
<2>该映射是单调递增的
评价:
浮点数的存储设计,从本质上来说是设计了一个优秀的数值映射,充分利用了2进制存储
的特点。
Java的double类型探索
 一.double类型的存储表示
Java的浮点类型表示完全按照IEEE754标准(Standards of IEEE 754 floating point numbers),有兴趣可以上IEEE标准网站()查阅.该标准的内容基本上描述了浮点类型的存储格式(Storage Layout),下面我从中总结几段,来概括该标准,详细信息请查阅标准原文.
1.什么是浮点数.
计算机上表达实数有两中方法:定点表示(fixed-point)和浮点表示(floating-point).定点表示法就是在现有的数字中间的某个位 置固定小数点,整数部分和小数部分的表示和一个普通整数的表示法没什么两样.例如,我们的数字长度为4,小数点位于中间,那么你可以表示10.28,也可 以表示00.01,与这种方法性质类似的定点表示还有使用分数的形式.定点数的固定窗口形式使得他既不能够表示非常大的数又不能表示非常小的数.并且当除 法发生时,大量 的精度丢失.
浮点数采用科学计数法的形式来表示实数.例如123.456可以表示成1.23456×102.相对于定点数的固定窗口(fixed Window)的限制,它采用的是浮动窗口(sliding window),因此可以表示较大精度范围的一个实数.
2.存储布局(Storage Layout)
所谓的存储布局就是一个浮点数在内存中如何表示.我们知道浮点数有float和double,前者是4个字节也就是32位,后者是8个字节也就是64位.布局分别为:
符号    指数        小数部分        偏移附加(bias)
单精度 1[31]    8[30-23]        23[22-00]        127
双精度 1[63]    11[62-52]    52[51-00]        1023
中括号内为位的编号范围,外面为该部分所占有的位的数量.偏移附加不属于位表示的内容,是一个常量,稍后解释.
符号只有一位:0-表示正数 1-表示负数
指数部分:用指数部分的值(8位/11位,unsigned)的值 减去 偏移附加 得到该数实际的指数 例如值为200,实际指数为73=200-127.对于双精度的double来说常量bias=1023
尾数:尾数是什么?对于一个科学计数法来讲,形式象这样的 L.M×BE,那么这个L.M就是所谓的尾数(mantisa).它由一个起始位和一个小数部分组成.举个例子,5可以用科学计数法表示成不同形式:
5*100
0.5*101
50*10-1
那么我们引进一个概念,规范化形式(normalized form)和非规范化形式(denormalized form).我们定义规范化形式为小数点位于第一个不为0的数字后面的表达形式为规范化形式,因此上面的第一种形式为规范化形式,其他的为非规范化形 式,Java中的浮点表示完全按照这个标准,只有两种形式规范化形式:1.f 和 非规范化形式 0.f .
那么,对于我们的位布局来说,选用底数为2的话,只有一个数字是非零的,那就是1.所以我们的隐含起始数字可以不用占用一个位,因为除了0就只能是1,具体的隐含规则,稍后展示.
3.表示的意义.
对应于上面的表格,每一个区域对应的值的范围所表示的浮点数的意义:
符号位s 指数位e 小数位f 所表示的意义v
0 00..00 00..00 +0
0 00..00 00..01
:
11..11 正的非规范实数,计算方法v=0.f × 2(-b+1)
0 00..01
:

11..10 XX..XX 正的规范化实数,计算方法v=1.f × 2(e-b)
0 11..11 00..00 正的无穷大
0 11..11 00..01
:
01..11 无意义的非数字SNAN
0 11..11 10..00
:
11..11 无意义的非数字QNAN

其中b=127(float)/b=1023(double),SNAN表示无效运算结果,QNAN表示不确定的运算结果,都是无意义的.
如果把符号位s改成1,那么就全部都是负数的含义,其他意义和这相同.

另外我们看到,对于无意义的数字是指数部分为全1时,也就是说这里有很多种组合都是无意义
的非数字,而我们的Java中,判断一个数字是否是NAN的做法相当简单
static public boolean isNaN(double v) {
return (v != v);
}
从这里可以看出来,虚拟机对于double类型的数据比较时,肯定是先做了指数值的判定,发现不是全1时才作内存的逐位比较.当然这是我得推测,真像不知道是否如此.
再另外,我们'现在十分清楚,double类型所能表示的最小值就是它的值之间的距离,也就是我们所说的精度,数字按这种精度向整数"1阶梯式的累加时, 正好不能和1完全匹配,换句话说,1不是最小值(精度/距离)的整数倍.因此如果你设置变量 double d = 0.1;而结果不会是0.1,因为无法表示0.1;

二.怎么查看double类型的存储结构?
我们很清楚Java的Double类型提供一个函数叫做doubleToLongBits函数,这个函数的其实很简单,我们知道,long类型和 double类型都是64位的,他们的内存大小一样,这个函数的做法就是把double对应的内存结构复制到同样大小的long类型变量的内存结构中.返 回这个long值.因
为Java不支持对double类型做位运算,因此:
1.该函数不可能用Java语言完成,所以他是JNI实现
2.我们利用对long类型的位运算可以把该内存结构打印出来查看.
/**
* 测试
*/
public static void main(String[] args){
myTest t = new myTest();
double d = 0.1d;
long l = Double.doubleToLongBits(d);
System.out.LongBits(l));
}
/**
* 得到常整数的bit位表示字符串
* @param a
* @return
*/
public String getLongBits(long a){
//8个字节的字节数组,读出来
byte[] b = new byte[8];
for(int i=7;i>=0;i--){
b[i] = (byte)(a&0x000000FF);
a = a>>8;
}
return this.byte2hex(b); //调用下面一个函数
}
/**
* 字节数组转换成字符串
* @param b
* @return
*/
public static String byte2hex(byte[] b){
StringBuffer sb=new StringBuffer();
String stmp="";
for(int n=0;n<b.length;n++){
stmp=(HexString(b[n]&0XFF));
if(stmp.length()==1){
//不足两位的末尾补零
sb.append("0"+stmp);
} else{
sb.append(stmp);
}
if(n<b.length-1){
//":"作为分割符
sb.append(":");
}
}
String().toUpperCase();
}

0.1打印出来的内存结果是:
3F:B9:99:99:99:99:99:9A

我们恢复一下和第一节的表示意义对照表对照一下:
0 1010

有兴趣的话,可以那科学计算器按照第一节的规则计算一下它的值,哦,正好就是我们通过System.out.println(d)打印的结果.

好了.这就是全部,我不认为我把问题表达的很清楚,因为我的总觉得文字和我的想法还是有一点距离,大概这就是表达能力吧.如果你不至于糊涂,我将很高兴.
java学习之数值型别(int,float,double等)
1. 代码
2.Java中数字类型的转换法则
test1中看似除数中的所有的因子都被约掉了,只剩下了1000。但实际的输出却是5,而不是我们期望的1000。究其原因,是因为MICROS_PER_DAYint类型进行的计算,而计算的结果是86400000000,已经超出了int类型的最大值,即溢出了(int32位,2^31-1=2147483647),24*60*60*1000*1000最后的结果是500654080(见程序中的print1的输出)。
 
在产生了错误的计算结果后,该结果被付给了long型的MICROS_PER_DAYlong型为64位,故保持了这个错误的结果,最终导致了最终结果的错误。