float和double在内存存储解析
C语言中,对于浮点类型的数据采用单精度类型(float)和双精度类型(double)来存储。
float数据占用32bit,double数据占用64bit。
我们在声明一个变量float f= 2.25f的时候,是如何分配内存的呢?如果胡乱分配,那世界岂不是乱套了么,其实不论是float还是double在存储方式上都是遵从IEEE的规范的,float遵从的是IEEE R32.24 ,而double 遵从的是R64.53。
存储方式
无论是单精度还是双精度在存储中都分为三个部分:
- 符号位(Sign) : 0代表正,1代表为负
- 指数位(Exponent):用于存储科学计数法中的指数数据,并且采用移位存储
- 尾数部分(Mantissa):尾数部分
其中float的存储方式如下图所示:
32bit
_________________________________________________________
| 1 | 8 | 23 |
_________________________________________________________
31 30 22 0
|符号位| 指数位 | 尾数部分 |
而双精度的存储方式为:
64bit
__________________________________________________________
| 1 | 11 | 52 |
__________________________________________________________
63 62 51 0
|符号位| 指数位 | 尾数部分 |
十进制小数与二进制小数的转换
在线进制转换网站:http://www.binaryconvert.com/...
十进制小数转二进制小数
计算方式:将该数字乘以2,取出整数部分作为二进制表示的第1位;然后再将小数部分乘以2,将得到的整数部分作为二进制表示的第2位;以此类推,知道小数部分为0。
特殊情况: 小数部分出现循环,无法停止,则用有限的二进制位无法准确表示一个小数,这也是在编程语言中表示小数会出现误差的原因
以8.25为例
8.25 ------------ 1000 先取出整数部分
0.25 ------------ . 小数点
0.25*2=0.5 ------------ 0 之后的都是小数位
0.5*2=1.0 ------------ 1
------------ 1000.01 小数的二进制表示
------------ 1.00001*2³ 二进制的科学计数法
二进制小数转十进制小数
计算方式:二进制的小数转换为十进制主要是乘以2的负次方,从小数点后开始,依次乘以2的负一次方,2的负二次方,2的负三次方等。例如二进制数0.001转换为十进制。
a的(-n)次方=1/(a的n次方)
1.00001*2³
1000.01
8 + 0 + 1 * 1/2² = 8.25
精度
任何一个数都的科学计数法表示都为:
1.xxx∗2n
float
指数部分:而对于指数部分,因为指数可正可负,8位的指数位能表示的指数范围就应该为:-127 - 128了,所以指数部分的存储采用移位存储,存储的数据为元数据+127
尾数部分:由于小数点前都是1,有23bit尾数部分,所以可表示精度为24bit。
24bit可以表示的最大值为2^23=8388608,所以最多表示到0.8388608,位数为7位,精度是6~7位。
ieee wiki表示:(其实看不懂)
log102=lg2
lg224=24∗lg2≈7.22
double
指数部分:2^11=2048,所以范围为:-1023 - 1024
尾数部分: 2^52=450 359 962 737 049 6,所以精度为15~16位。
ieee wiki表示:
lg253=53∗lg2≈15.95
示例
正推:8.25和120.5
︿ _________________________________________________
| | 0 | 1000 0010 | 000 0100 0000 0000 0000 0000
| _________________________________________________
| | 0 | 127+3=130 | 000 1
| _________________________________________________
| | 0 | 3 | 000 1
| _________________________________________________
| 1.00001*2³ S:0 E:3 M:00001
| _________________________________________________
| 8.25 1000.
| 0.25*2=0.5 0
| 0.5*2=1 1
| 1000.01 = 1.00001 * 2³
| _________________________________________________
︿ _________________________________________________
| | 0 | 1000 0101 | 111 0001 0000 0000 0000 0000
| _________________________________________________
| | 0 | 127+6=133 | 110 1101
| _________________________________________________
| | 0 | 6 | 110 1101
| _________________________________________________
| 1.1101101*2⁶ S:0 E:6 M:110 1101
| _________________________________________________
| 120.5 111 1000.
| 0.5*2=1 1
| 111 1000.1 = 1.1110001 * 2⁶
| _________________________________________________
反推:内存 0 1000 0101 110 1101 0000 0000 0000 0000
| _________________________________________________
| 0 1000 0101 110 1101 0000 0000 0000 0000
| _________________________________________________
| | 0 | 1000 0101 | 110 1101 0000 0000 0000 0000
| _________________________________________________
| | 0 | 127+6=133 | 110 1101
| _________________________________________________
| | 0 | 6 | 110 1101
| _________________________________________________
| 1.1101101*2⁶ S:0 E:6 M:110 1101
| _________________________________________________
| 1.1101101*2⁶ = 1110110.1 = 118 + 1/2¹ = 118.5
﹀ _________________________________________________
单精度转换为双精度的问题
注意看下面程序的输出
#include <stdio.h>
int main(int argc, const char *argv[])
{
float f = 2.2;
double d = f;
printf("f = %1.20f d = %1.20lf\n", f, d);
f = 2.25;
d = f;
printf("f = %1.20f d = %1.20lf\n", f, d);
return 0;
}
jonathan@cloud:~/test$ gcc test.c
jonathan@cloud:~/test$ ./a.out
f = 2.20000004768371582031 d = 2.20000004768371582031
f = 2.25000000000000000000 d = 2.25000000000000000000
为什么2.2用printf打印出来精度丢失了?为什么2.25精度没变没有?这其实跟这两个数在内存中的存储有关。如下可知:
︿ _________________________________________________
| | 0 | 1000 0000 | 001 0000 0000 0000 0000 0000
| _________________________________________________
| | 0 | 127+1=128 | 001
| _________________________________________________
| | 0 | 1 | 001
| _________________________________________________
| 1.001*2¹ S:0 E:1 M:1001
| _________________________________________________
| 2.25 10.
| 0.25*2=0.5 0
| 0.5*2=1.0 1
| 10.01 = 1.001*2¹
| _________________________________________________
︿ _________________________________________________
| | 0 | 1000 0000 | 000 1100 1100 1100 1100 1100
| _________________________________________________
| | 0 | 127+1=128 | 000 1100 1100 1100 1100 1100
| _________________________________________________
| | 0 | 1 | 000 1100 1100 1100 1100 1100
| _________________________________________________
| 1.00011001100110011001100.. * 2¹
| S:0 E:6 M:00011001100110011001100..
| _________________________________________________
| 2.2 10.
| 0.2*2=0.4 0
| 0.4*2=0.8 0
| 0.8*2=1.6 1
| 0.6*2=1.2 1
| 0.2*2=0.4 0
| 0.4*2=0.8 0
| 0.8*2=1.6 1
| 0.6*2=1.2 1
| 0.2*2=0.4 0
| 0.4*2=0.8 0
| 0.8*2=1.6 1
| 0.6*2=1.2 1
| 0.2*2=0.4 0
| 0.4*2=0.8 0
| 0.8*2=1.6 1
| 0.6*2=1.2 1
| ... ...
| 10.0011001100110011001100...
| 1.00011001100110011001100... * 2¹
| _________________________________________________
2.25在内存中存储很简单,转换成二进制也简单。但是2.2转化为二进制时,由于小数部分出现了无限循环,导致精度丢失,产生了误差