zoukankan      html  css  js  c++  java
  • 浮点数

    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

  • 相关阅读:
    面向对象的核心概念
    堆栈和托管堆 c#
    DIV绘制图形
    slideLeft DIV向左右滑动
    结构化分析
    函数
    HTML CSS 特殊字符表
    100以内的数是正整数 if的基本用法
    简单三个数比较大小 “?!”的用法
    简单计算器
  • 原文地址:https://www.cnblogs.com/shizunatsu/p/11990741.html
Copyright © 2011-2022 走看看