浮点数

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.浮点数的底层二进制表示

当我们定义一个浮点数如 float f = 0.1f; 时,0.1在计算机底层是通过二进制来进行存储的。

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

  • 整数部分:除以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.浮点数不精确

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

浮点数并不一定等于小数

在银行等一些对数据有严格要求的地方,使用的数据类型是BigDecimal 

浮点数不精确的原因是因为底层采用二进制表示。

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

0.1f 在计算机中存储的二进制是  0011 1101 1100 1100 1100 1100 1100 1101,换算成10进制为0.100000001490116119384765

在计算机中,只要二进制存储如上,Java就认为是0.1。(用这个近似值去表示小数0.1)

如 float f = 0.099999999f;  打印出来也是0.1,因为其二进制存储也和上面的一样。

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

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

float a = 0.7f;
float b = 0.69999996f;
float c = 0.70000001f;

System.out.println(a);  //0.7
System.out.println(b);  //0.7
System.out.println(c);  //0.7

如上代码打印出来的都是0.7,这是因为a,b,c三个数字的二进制底层表示是相同的。只要二进制表示是这个数字,那么在Java里,就是0.7

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

首先分配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,为什么不精确?

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

计算二进制的小数部分对应的十进制

public static void binaryFloatToDecimalFloat(String s) {
    
    String[] arr = s.split("\.");
    int length = arr[1].length();
    
    //把小数部分的字符串解析成一个一个的字符串
    int[] array = new int[length];
    for(int i=0;i<array.length;i++) {
        String m = arr[1].charAt(i)+"";
        array[i] = Integer.parseInt(m);
    }
    
    BigDecimal big = new BigDecimal("0");
    for(int i=0;i<array.length;i++) {
        BigDecimal b1 = new BigDecimal(Math.pow(2, -(i+1))*array[i]+"");
        big = big.add(b1);
    }
    System.out.println(big);
}

如11.11,小数部分对应的十进制是0.75

原文地址:https://www.cnblogs.com/shizunatsu/p/11990741.html