1.浮点数是什么

在计算机中表达实数的方式有定点数和浮点数。定点数的小数点位置固定,不能移动,浮点数的小数点位置可以移动。

所以一个浮点数可以有多种表达方式,如101.101可以表示成1.01101*2^2、1011.01*2^-1。

Java的float和double采用了IEEE 754标准中所定义的单精度 32 位浮点数和双精度 64 位浮点数的格式,用二进制的科学计数法来表示浮点数。

2.浮点数的表示

Java语言的浮点数有两种表示形式

十进制表示:123.0,23.45

科学计数法表示:8.3e2,9.2E-3

   1> e是exponent的首字母,指数的意思,表示10的几次方,大小写均可

   2> e或E之前必须有数,注意指数有正有负

        3> 只有浮点类型的数值才可以使用科学计数法形式表示。例如,51200是一个int类型的值,但512E2则是浮点类型的值

3.浮点数的内存

float在内存中占4个字节32位,内存区域分为三个部分。 

尾数位:0-22位,共23位

指数位(阶码位):23-30位,共8位

符号位:最高位,0表示正数1表示负数

double在内存中占8个字节64位。最高位为符号位,接下来11位为指数位,最后52位为尾数位

根据IEEE754标准规定

对于阶码e ,如果不是0或者255, 就需要减去偏差值,对于float 是127 ,double是1023。减去偏差值的数值才是真正的指数
对于尾数M ,如果阶码不是0或者255,尾数的小数点左侧有一个默认的 1

4.浮点数的进制转换

十进制小数转二进制小数,整数部分和小数部分需要分开处理

  • 整数部分:除以2直到商为0,反序取余

  • 小数部分:乘以2,取结果的整数部分,再用结果的小数部分乘以2,如此循环下去,直到小数部分为0。然后将整数顺序排列。

        如果小数部分永远不为0,则按规定进行取舍。

(因为可能出现永远不为0的情况,所以就注定了有些十进制小数无法用二进制小数精确表示)

来看一个具体例子


float f= 10.8125f;

10.8125整数部分转换为二进制是1010,小数部分转换成二进制是1101

所以10.8125对应的二进制小数位1010.1101,规范写法为1.0101101*10^3

底数去掉1和小数点为0101101,指数为3+127=130,二进制为10000010,符号位为0

即10.8125的二进制为

0100 0001 0010 1101 0000 0000 0000 0000

用程序看一下10.8125的二进制表示

System.out.println(Integer.toBinaryString(Float.floatToIntBits(10.8125f)));

结果为

1000001001011010000000000000000

前面补0为

0100 0001 0010 1101 0000 0000 0000 0000

跟我们计算的一样

5.浮点数不精确

浮点数在计算机中用以近似表示任意某个实数。近似,是什么意思呢?就是用一个近似值去表示一个数。

浮点数并不一定等于小数。 

如float f = 0.1f ;计算机底层是无法用二进制去精确表示0.1的,它只会有一个无限接近于0.1的二进制小数

0.1f 在计算机中存储的二进制是  0011 1101 1100 1100 1100 1100 1100 1101,相当于10进制的0.100000001490116119384765

在计算机中,用0.100000001490116119384765这个近似值去表示小数0.1

打印出来也是0.1

那为什么0.1无法精确保存打印出来的却还是0.1?

因为浮点数就是用这个近似值去表示0.1的

只要你的二进制存储是这个,那么,Java会认为你就是浮点数0.1

尽管按照十进制的算法,这个数字并不等于0.1而是趋近于0.1.

但这也恰好印证了,浮点数不等于小数,而是以近似的方式去表示实数的

还有一种情况

float f = 5.2345556f;
System.out.println(f);//5.2345557

把5.2345556f赋值给一个float类型变量,接着输出这个变量时看到这个变量的值已经发生了改变。

为什么打印出来数字会发生变化?

1.计算机无法用二进制小数精确表示一个十进制小数

float f =  5.2345556f; 

在内存中的二进制为0100 0000 1010 0111 1000 0001 0111 1011,转换为十进制为5.234555721282958984375,保留8位精度为5.2345557

2.浮点数的取值范围

浮点数是有取值范围的,float的取值范围是-3.4*10^38 ~ 3.4*10^38

那么,它能够表达数轴上在这个范围内的所有数吗?显然是不能的,首先无限小数就无法表示。

如果把浮点数能够表示的所有数在数轴上一一列出来,我们会发现这不是一个完整的线段,而是中间带有间隔

参考:https://my.oschina.net/jasonli0102/blog/3013198

 6.当我们定义float f = 0.1时, 计算机是如何进行保存的?

首先分配4个字节去存储,然后把符号位、尾数位、指数位分别填入

这里我们看一下尾数位

将十进制的0.1 转换为二进制

小数部分乘以2    0.1*2 = 0.2;  0.2*2=0.4;  0.4*2=0.8;  0.8*2=1.6;  0.6*2=1.2;(这里小数部分又得到了最开始的2,这意味着会无限循环下去)

取整数部分                          0      0                       0                        1                     1         (接下来取到的整数是  0 0 1 1的循环)

所以0.1对应的二进制小数为0.0 0011 0011……(0011循环)

将其转换为浮点数的规范表达形式, 1.1001100110011……*2^-4

float的尾数除去默认的小数点前面的1,有23位。所以将小数点后面的值依次填入尾数,最后一位按照规定进行取舍,剩下的直接舍弃

忽略最后一位的取舍的话,尾数应该是 100 1100 1100 1100 1100 1100

查看一下0.1的二进制表示

System.out.println(Integer.toBinaryString(Float.floatToIntBits(0.1f)));//111101110011001100110011001101

即尾数应该是 100 1100 1100 1100 1100 1101,除了最后一位,其他跟我们预想的一样

   

7.阶码为何使用偏差值,float偏差值为何是127?

float的偏差值是127(2^7-1),double的偏差值是1023(2^10-1)

阶码使用偏移量是为了简化运算。

指数是有正有负的。如果把指数位的最高位作为符号位的话,一个浮点数中就会有两个符号位了,那浮点数之间的比较和运算想必会困难许多。

使用了偏移量之后,用无符号整数既可以表示正指数,又可以表示负指数

为什么偏移量是127?

8位可以表示 0000 0000 ~ 1111 1111 即0~255共256个值

规范规定0000 0000与1111 1111用作特殊情况,所以除去0和255,阶码能表示1~254共254个值

为了平衡正负指数,选取了1~254中间的值127。

8.浮点数取值范围

当尾数位全部为1时,底数取得最大值,接近于2。当尾数位全部置0时,底数取得最小值,为1。故底数的取值范围为 1~2。
指数位的取值范围为1~254,减去偏差值为 -126~127,故指数的取值范围为-126~127。
因此float型的取值范围为:
-2*2^127 ~ -1*2^(-126) 与 1*2^(-126) ~ 2*2^127
转化得:
-3.4*10^38 ~ -1.2*10^(-38) 与 1.2*10^(-38) ~ 3.4*10^38


9.浮点数的大小比较

在二进制中,是通过符号位、指数位、尾数位分别比较得到结果的

有一个不相等则不相等

注意指数位的比较,比较的是减去偏移量之后的数

10.浮点运算

浮点计算是指浮点数参与的运算,这种运算通常伴随着因为无法精确表示而进行的近似或舍入

完成浮点加减运算的操作过程大体分为四步:
1. 0 操作数的检查;
2. 比较阶码大小并完成对阶;
3. 尾数进行加或减运算;
4. 结果规格化并进行舍入处理。
 

对于0.1+0.2,为什么不精确?

因为这种情况,至少有一个就不能精确表示,然后运算的时候是底层二进制进行运算。

12-23 04:06