1.float和double的范围和精度
(1)float和double的表示范围是由指数的位数来决定的。float的指数位有8位,而double的指数位有11位,分布如下:
float:1bit(符号位)+8bits(指数位)+23bits(尾数位)
double:1bit(符号位)+ 11bits(指数位)+ 52bits(尾数位)
(2)在数学中,特别是在计算机相关的数字(浮点数)问题的表述中,有一个基本表达法:
(浮点)数值 = 尾数 × 底数 ^ 指数,(附加正负号)
于是,float的指数范围为-127~128(8bits(指数位)),而double的指数范围为-1023~1024(11bits(指数位)),并且指数位是按补码的形式来划分的。其中负指数决定了浮点数所能表达的绝对值最小的数;而正指数决定了浮点数所能表达的绝对值最大的数,也即决定了浮点数的取值范围(不代表全部位数有效)。float的范围为-2^128 ~ +2^128,也即-3.40E+38 ~ +3.40E+38;double的范围为-2^1024 ~ +2^1024,也即-1.79E+308 ~ +1.79E+308。
(3)float和double的精度(有效位数)是由尾数的位数来决定的。浮点数在内存中是按科学计数法来存储的,其整数部分始终是一个隐含着的“1”,由于它是不变的,故不能对精度造成影响。
float:2^23 = 8388608,共七位,意味着最多能有7位有效数字,但绝对能保证的为6位,也即float的精度为6~7位有效数字;
double:2^52 = 4503599627370496,一共16位,同理,double的精度为15~16位。
2.C/C++对于浮点数的内存存储
对于浮点类型的数据采用 单精度类型(float)占用32bit / 双精度类型(double)占用64bit,我们在声明一个变量float f= 8.25f的时候,它是如何分配内存的呢?其实不论是float还是double在存储方式上都是遵从IEEE的规范的,float遵从的是IEEE R32.24 ,而double 遵从的是R64.53。
无论是单精度还是双精度在存储中都分为三个部分(详见下图):
1. 符号位: 0代表正,1代表为负
2. 指数位: 用于存储科学计数法中的指数数据,并且采用移位存储
3. 尾数部分:尾数部分
R32.24和R64.53的存储方式都是用科学计数法来存储数据的,比如8.25用十进制的科学计数法表示就为:8.25*10e0, 而120.5可以表示为:1.205*10e2 。但计算机根本不认识十进制数据,它只认识0/1,所以在计算机存储中,首先要将上面的数更改为二进制的科学计数法表示。
按照IEEE浮点数表示法,下面将把double型浮点数8.25转换为二进制代码。
把整数部和小数部分开处理:
①整数部分直接化二进制:1000(b) //8(d)
②小数部分处理:
小数转换为二进制的方法:小数部分乘以2,取整数部分依次从左往右放在小数点后,直至小数点后为0。
0.25 * 2 = 0.50 ... 0 //本例
0.50 * 2 = 1.00 ... 1 //本例
/附加
小数转换为二进制的方法:小数部分乘以2,取整数部分依次从左往右放在小数点后,直至小数点后为0。
0.91 * 2 = 1.82 ... 1
0.82 * 2 = 1.64 ... 1
0.64 * 2 = 1.28 ... 1
0.28 * 2 = 0.56 ... 0
0.56 * 2 = 1.12 ... 1
0.12 * 2 = 0.24 ... 0
0.24 * 2 = 0.48 ... 0
0.48 * 2 = 0.96 ... 0
0.96 * 2 = 1.92 ... 1
0.92 * 2 = 1.84 ... 1
0.84 * 2 = 1.68 ... 1
0.68 * 2 = 1.36 ... 1
0.36 * 2 = 0.72 ... 0
0.72 * 2 = 1.44 ... 1
0.44 * 2 = 0.88 ... 0
0.88 * 2 = 1.76 ... 1
0.76 * 2 = 1.52 ... 1
0.52 * 2 = 1.04 ... 1
0.04 * 2 = 0.08 ... 0
0.08 * 2 = 0.16 ... 0
0.16 * 2 = 0.32 ... 0
/附加
所以直到加上前面的整数部分(1000 . 不要1,算3位,余下需凑够20位,隐藏位技术:最高位的1不写入内存)算够23位(尾数部分23位)就行了。
如果你够耐心,手工算到23位那么因该是:
8.25(d)=1000 . 01000000000000000000(b) //(整数部分(1000 . 不要1))
120.5(d)=1111000 . 10000000000000000(b) //(整数部分(1111000 . 不要1))
用二进制的科学计数法表示:
1000 . 01000000000000000000可以表示为1 . 00001000000000000000000 * 2e3,
1111000 . 10000000000000000可以表示为1 . 11100010000000000000000 * 2e6,
float有23bit的尾数部分,2^23=8388608,7位数表示不全,但能使float能精确到小数点后6位。
对于指数部分,因为指数可正可负,8位的指数位能表示的指数范围就应该为:-127-128,所以指数部分采用移位存储,存储的数据为元数据+127(double型则+1023)。
下面就看看8.25在内存中真正的存储方式:
0 10000010 00001000000000000000000
符号位 127+3=130(指数部分) 尾数部分
下面就看看120.5在内存中真正的存储方式:
0 10000101 11100010000000000000000
符号位 127+6=133(指数部分) 尾数部分
3.将一个float型转化为内存存储格式的步骤为:
(1)先将这个实数的绝对值化为二进制格式。
(2)将这个二进制格式实数的小数点左移或右移n位,直到小数点移动到第一个有效数字的右边。
(3)从小数点右边第一位开始数出二十三位数字放入第22到第0位。
(4)如果实数是正的,则在第31位放入“0”,否则放入“1”。
(5)如果n是左移得到的,说明指数是正的,第30位放入“1”。如果n是右移得到的或n=0,则第30位放入“0”。
(6)如果n是左移得到的,则将n减去1后化为二进制,并在左边加“0”补足七位,放入第29到第23位。如果n是右移得到的或n=0,则将n化为二进制后在左边加“0”补足七位,再各位求反,再放入第29到第23位。
举例:01101.1100011101 ----->1.1011100011101*(2^11) 则1011100011101存进尾数部分,11存进指数部分。
将一个内存存储的float二进制格式转化为十进制的步骤:
(1)将第22位到第0位的二进制数写出来,在最左边补一位“1”,得到二十四位有效数字。将小数点点在最左边那个“1”的右边。
(2)取出第29到第23位所表示的值n。当30位是“0”时将n各位求反。当30位是“1”时将n增1。
(3)将小数点左移n位(当30位是“0”时)或右移n位(当30位是“1”时),得到一个二进制表示的实数。
(4)将这个二进制实数化为十进制,并根据第31位是“0”还是“1”加上正号或负号即可。
4、为什么float从第7位开始失效?
举个例子,float ia=12345678.5;
按照前面的方法,用二进制分别表示整数部分和小数部分:101111000110000101001110 . 1
指数部分:小数点需左移23位变为:1 . 011110001100001010011101;//127+23=150
小数部分:011110001100001010011101,共有24位(不算小数点),但实际存储规定尾数部分(小数部分)只能存储23位。故将导致从第7位开始数字精度丢失。
参考网址:
https://blog.youkuaiyun.com/tom739/article/details/103577435
https://blog.youkuaiyun.com/sinat_38972110/article/details/82117072
https://www.cnblogs.com/little-white/archive/2013/08/04/3236493.html
https://blog.youkuaiyun.com/sunweiliang/article/details/82622038