C - float和double在内存存储解析

float和double在内存存储解析

C语言中,对于浮点类型的数据采用单精度类型(float)和双精度类型(double)来存储。

float数据占用32bit,double数据占用64bit。

我们在声明一个变量float f= 2.25f的时候,是如何分配内存的呢?如果胡乱分配,那世界岂不是乱套了么,其实不论是float还是double在存储方式上都是遵从IEEE的规范的,float遵从的是IEEE R32.24 ,而double 遵从的是R64.53。

存储方式

无论是单精度还是双精度在存储中都分为三个部分:

  1. 符号位(Sign) : 0代表正,1代表为负
  2. 指数位(Exponent):用于存储科学计数法中的指数数据,并且采用移位存储
  3. 尾数部分(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转化为二进制时,由于小数部分出现了无限循环,导致精度丢失,产生了误差

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值