float类型的存储形式

通过这篇文章将弄清楚以下几个问题:

1.任意一个小数如何用二进制表示

2.小数在计算机中如何存储

3.float类型的数为什么会有可能存在精度丢失

4.float类型的数的大小范围以及精度

1. 任何一个小数如何用二进制表示

我们先回顾一下一个十进制整数如何转换为二进制,写一个简单的将一个正整数转换为二进制的代码如下:

public static String toBinaryString(long n) {
        StringBuilder s = new StringBuilder();
        if(n == 0) {
            return "0";
        }
        while (n != 0) {
            s.append(n % 2);
            n = n / 2;
        }
        return s.reverse().toString();
    }

思想就是不断的除以2,直到结果为0,每次的余数记录下来链接起来即可,简单的解释一下这个过程的原理,比如我们想将57这个数转换为二进制,我们可以把57理解为57个1相加,所谓的二进制就是逢2进1,那整个过程就如下:

最低位1,向第二位进28;

第二位0,向第三位进14;

第三位0,向第四位进7;

第四位1,向第五位进3;

第五位1,向第六位进1;

第六位1,向第七位进0;

结果就是:111001

那小数如何转换为二进制呢?我们还是先以10进制的小数为例,比如1024.1234,我们可以用以下形式来表示:

1024.1234=1*10^3 + 0*10^2 + 2*10^1 + 4*10^0 + 1*10^-1 + 2*10^-2 + 3*10^-3 + 4*10^-4

那能不能表示成m1*2^n1 + m2*2^n2 + ...... + mk*2^nk这种形式呢?

其实计算机不论是以10进制还是多少进制存储,都无法完全精确的表示所有的小数,比如1/3,用10进制存储必然会不精确,但是用3进制确可以精确存储,所以任一小数都可以转换为任一进制,形式都是诸如上面的形式,但是任何进制都无法在有限位精确表示所有的小数

1024.1234如何用二进制表示呢?

首先整数部分1024转换位二进制:1024=2^10

0.1234如何转换为二进制?

假设0.1234 = m1*2^-1 + m2*2^-2 +  ...... + mn*2^-k

我们只用求出m1、m2、......、mn,因为是二进制,所以mk要么是0要么是1,有了该条件整个求解过程可以是下面这样:

1. 等式两边乘以2

0.1234 * 2 = m1 + m2*2^-1 + ...... + mn*2^-(k - 1) = 0.2468

m1显然不能为1,所以m1=0

2. 在1的基础上接着乘以2

0.2468 * 2 = m2 + m3*2^-1 + ...... + mn*2^-(k-2) = 0.4936

可以得出m2=0

3. 在2的基础上接着乘以2

0.4936 * 2 = m3 + m4*2^-1 + ...... + mn*2^-(k-3) = 0.9872

可以得出m3=0

4. 在3的基础上接着乘以2

0.9872 * 2 = m4 + m5*2^-1 + ...... + mn*2^-(k-4) = 1.9744

这里就可以得出m4 = 1了,等式简化为:

m5*2^-1 + ...... + mn*2^-(k-4) = 0.9744

重复乘以2的步骤可以分别求出m5、m6、......、mn;

从上面的过程就可以总结出小数转换为二进制的方法:

不断乘以2,如果结果小于1,则对应位就是0,否则对应位1,然后结果减1后再重复乘以2;

根据上述方法我们可以轻松的将以下十进制小数转换为二进制:
0.5:0.1

0.25:0.01

0.125:0.001

0.75:0.11

0.1:0.00011 0011 0011 0011...

可以看到0.1是无法用有限的二进制小数表述出来的

知道了小数是如何转换位二进制的,我们就可以将任意一个小数转换位二进制小数了:

10.5:1010.1

2.5:10.1

10.75:1010.11

(这里举例的数字都是比较容易用二进制表示的,下面讨论精度丢失会再细看那些不容易用二进制的表示的数)

2. 小数在计算机中如何存储

计算机内存是利用电流来实现存储数据,高电平代表1,低电平代表0,磁盘是利用磁效应来存储数据,根据磁极方向来表示0和1,介绍这些的目的就是为了说明:小数点是没有办法直接在计算机中存储的,所以1010.11是没办法直接存储到计算机的,必须要做转换,其实有两个办法是很容易想到的:

1. 约定优先,比如用前多少bit表示整数部分,后多少bit表示小数部分

2.用另外的空间来表示小数点的位置

这两种思想其实就是定点数和浮点数的区别,而且这两种方式都有在应用,但是稍作分析就知道第一种方式的缺陷:同样的空间,存储的数据范围太小了;所以目前主流的存储小数的方式都是采用第二种方式,也就是浮点数,浮点数会将存储区域划分为3部分:符号域、指数域、有效数字域,这3个区域的数据都应该如何存储和使用?IEEE标准给出了我们答案,IEEE(电气和电子工程协会)标准认为,任何一个浮点数的可以用下面的二进制形式表示:

(-1) ^ S * M * 2 ^ E;//(S表示符号,E表示阶乘,M表示有效数字)

S为0时,表示正数,S为1时,表示负数,所以S占用1bit就行了

E指数域,E的大小决定着浮点数能表示的最大数与最小数

1<=M<2, M的大小决定着浮点数的精度

所以给定空间,必须要在M和E空间分配上做权衡

同样,IEEE指定了:

两种基本的浮点格式:单精度和双精度。其中单精度格式具有24位有效数字(即尾数)精度,总共占用32位;双精度格式具有53位有效数字(即尾数)精度,总共占有64位       

两种扩展浮点格式:单精度扩展和双精度扩展。此标准并未规定这些格式的精确精度和大小,但指定了最小精度和大小,例如IEEE双精度扩展格式必须至少具有64位有效数字精度,并总共占用至少79位

所以要想在计算机上存储单精度浮点数,需要32bit的空间大小,因为需要24位的有效数字,所以意味着M需要占用24bit,然后符号位1bit,似乎S就占用7bit,但是事实并非如此,因为1<=M<2,也就是说M的第一位始终是1,所以我们是不需要单独来存储的,所以最终32bit的分配情况为:
符号域S:1bit

指数域E:8bit

有效数字域M:23bit

如果仅仅是这样还是不行的,因为1<=M<2,而E>=0,所以M * 2 ^ E>=1,也就是说目前为止只能表示绝对值大于1的小数,0到1之间的小数是没办法表示的,IEEE帮我们规定好了:

对于单精度浮点数来说,规定在存入E时,在它原本的值上加上中间数(127),在使用时减去中间数(127),这样E的真正取值范围就成了(-127~128)

同时还规定:

当指数域为00000000时,此时E的值实际为-127,直接表示0;

当指数域为11111111时,此时E的值实际为128,如果有效数字域全部为0,则用来表示无穷,如果有效数字域不全为0,则不是一个数字(NaN);

问:10.5在计算机中如何存储?

10.5转换为二进制为1010.1

用IEEE标准表示为(-1)^0 * 1.0101 * 2^3

S=0, M=1.0101, E=3(存储的实际值为130)

所以存储形式为:

0 10000010 01010000000000000000000
 

3. float类型的数为什么会有可能存在精度丢失

其实理解了第2节,这个问题的原因就显得非常简单了,因为有些小数的二进制表示的位数是无限的,或者位数很大,但是计算机只给了32bit来表示一个float的数,所以必然存在着取舍

比如0.1转换为二进制为0.00011 0011 0011 0011...

转换为IEEE格式的:(-1)^0 * 1.1001100110011... * 2^(-4)

此时S=0, M=1.1001100110011..., E=-4(存储的实际值为123)

所以0.1的存储形式为:

0 01111011 10011001100110011001101

这里主要是看最后一位是0还是1,用代码测试一下即可:

因为f>0.1,说明实际存储0.1时进位了,也就是最后一位应该为1,这里为啥0.1可以被正确输出?其实这是输出时做了类似“四舍五入”操作了,否则的话按原样输出长度是非常长的

4. float类型的数的大小范围

先考虑什么情况下表示的数字最大,首先23bit的有效数字全为1,然后指数域为11111110(因为指数域全为1是保留值,代表着无穷),此时表示的数的大小为:

1.1111 1111 1111 1111 1111 111 * 2^127 = (2 - 2^-23) * 2^127 

大概也就是2^128,大约为3.4*10^38

而我们知道,同样为32bit的int类型最大也就能表示2147483647,这相差太大了,这是什么原因呢?这是因为实际上float类型虽然能表示的最大数很大,但是能表示的数字是不连续的,有很多比较大的整数都是没办法精确表示,我们先从原理分析,然后辅助代码验证

我们先以一种临界情况为切入点分析,有效数字域分配了23bit,如果全为1,然后指数E为23,那么表示的数字其实为16777215

16777216=2^24

所以16777216二进制为 1 0000 0000 0000 0000 0000 0000

实际上16777216的尾数23bit已经装不下了,尾数有24个0,存入float时有效数字域存了23个0,舍弃了一个0,但是由于舍去0后对大小并无影响,所以仍然可以用float类型表示16777216

但是16777217呢?

16777217的二进制为 1 0000 0000 0000 0000 0000 0001,如果要存入float数的有效数字域里面,最后面的1必须要处理,要不直接舍去,要不向前进1,直接舍去则16777217用float表示实际就为16777216,如果向前进1,16777217用float表示实际值就为16777218,实际处理根据代码结果看是直接舍去了末尾的1

也就是说如果用float来表示整数,那能精确表示的连续整数范围为:[-2^24, 2^24]

int的最大数为2147483647,出了最高位为0,剩下的31bit全为1,那如果用float表示2147483647近似值是多少呢?

其实分析一下就知道,应该为2^31=2147483648

java输出是2147483650,因为输出时做了四舍五入,可以用C语言测试一把

可以看到,与预想的是一样的 

接下来探讨一下float的精度问题,首先需要理解“精度”这个概念,我个人是这么理解的,给定一个实数,然后用浮点数存储,这时可能会存在精度缺失,然后把该实数和取出来来的浮点数都用10进制的科学记数法表示,如果存储的时候发生了精度缺失,那么科学计数法会不一样,如果说对这两个科学计数法同时做保留n位小数取近似值,如果结果相同,我们就可以说,浮点数的精度可以达到n+1位有效数字(小数点前面有个1),或者说可以精确到小数点后n位,现在的问题就是对于任意一个实数,我们要找到这个最大的n

我们拿16777217来分析,前面已经知道16777217用浮点数保存取出来后的值为16777216,都用科学计数法表示:

16777217=1.6777217 * 10^7

16777216=1.6777216 * 10^7

如果同时保留7位小数,他们的值肯定不等,所以可以断定单精度浮点数的精度达不到8位有效数字,如果同时保留6位小数,是相等的,那能不能说精度就是7位有效数字呢?暂时还不能,因为我们还要证明普适性,下面粗略的来个证明

对于任意两个正实数m、n,如果|m-n|/m < 1/10^k(k为正整数),那么m和n在保留k位有效数字的值是一样的,比如5000,与5000相差1/10^3之内的数字范围为(4995,5005),这个区间的任何数字如果保留3位有效数字的话得到的值都是5000,如果存在进位的情况会复杂一点

好了,现在我们主要证明任何一个浮点数存储到计算机的实际值如果和该浮点数相差不到1/10^7,那我们就可以说他们保留7位有效数字时值时相等的,我们知道,有效数字域为23bit,如果一个实数X转化为IEEE标准二进制存储时,M的位数不止23位,那就面临着舍去或者进位

假设X转换为IEEE二进制的形式为X = 1.xxxxxx * 2^k

Y为保存后的值

先看舍去的情况,舍去的肯定是23位之后的数据 ,而且第24位不为1,这些数据最大不会超过:

2^-25 + ... + 2^-n = 2^-25*(1 - 2^-n)/(1 - 2^-1) < 2^-24

也就是(X-Y)/X <  2^-24 * 2^k / 1.xxxxxxx * 2^k = 1/1.xxxxxx * 2^24 < 1/2^24 = 1/16777216 < 1/10^7

所以X,Y在保留7位有效数字时值是一样的

再看进位的情况

进位也是根据1.xxxxxx小数部分的第24位往前进,其实也就是在原有基础上加

0.0000 0000 0000 0000 0000 0001然后再舍去23位后面的数字,即得到Y

所以Y < X + 2^-24

(Y - X)/X < 1/10^7

所以无论是舍去还是进位,实际值X与保存的Y值都满足|X - Y|/X <10^-7

所以X,Y保留7位有效数字时他们的值时一样的,也就是单精度浮点数的精度为7位有效数字,用科学计数法表示可以精确到小数点后6位

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值