浮点型在内存中的存储。(浮点型与整型的差异)

浮点型常见的类型有:float、double、long double。

浮点型表示的范围:float.h中定义(定义了浮点数取值范围的相关信息)。

下面分别是float.h文件中double、float、long double 的精度、最大值、最小值。(下个Everything 直接搜 float.h 特别快!)

整型的取值范围在 limits.h 文件中,如下:

以后如果想直接用float/整型的最值的时候,可以引头文件然后直接用,比如用 int 最大值可以直接用 INT_MAX (但是要包含头文件),下面回归正题。

下面这段代码:

#include <stdio.h>

int main()
{

    int n = 9;
    float* pFloat = (float*) &n; // 因为 n 地址是 int* 类型,所以强制类型转换为 float* ,才能给 pFloat
    printf("n的值是:%d\n", n); // n 以 %d 形式打印是 9 ?
    printf("*pFloat的值是:%f\n", *pFloat); // *pFloat 也访问 4 个字节,以 %f 打印是 9.0 ?

    *pFloat = 9.0;
    printf("n的值是:%d\n", n); // 也是 9?
    printf("*pFloat的值是:%f\n", *pFloat); // 也是 9.0?

    return 0;

}

运行代码如下:

第 2 个与第 3 个是错的,所以不是想的那样,那怎么回事呢? 

    int n = 9;
    float* pFloat = (float*)&n; // 因为 n 地址是 int* 类型,所以强制类型转换为 float* ,才能给 pFloat
    printf("n的值是:%d\n", n); // n 以 %d 形式打印是 9 ?
    // n 是整型,以整型打印是 9 没有问题
    //
    printf("*pFloat的值是:%f\n", *pFloat); // *pFloat 也访问 4 个字节,以 %f 打印是 9.0 ?
    // 但是 n 强转了 float* 之后,以 float* 打印是访问 4 个字节的
    // *pFloat 解引用取 n 的时候,站在 pFloat 的角度去看的时候,它认为是内存放的是 float* 的类型
    // 然后当我们解引用拿的时候访问的是 4 个字节,并且认为放的是浮点型数据(但我们放的是整型)
    
    // 所以我们以整型方式放进去,以浮点型取的时候,不是 9 ,所以整型与浮点型是有差异的

    *pFloat = 9.0;
    printf("n的值是:%d\n", n); 
    // 与上面相反,浮点型放进去,整型拿出来 
    // 以浮点型方式放进去,以整型取的时候,不是 9 ,所以整型与浮点型是有差异的

    printf("*pFloat的值是:%f\n", *pFloat);
    // 浮点型存 浮点型取 没问题 只是精度原因少些几个 0 

 所以可以得出:

我们以整型方式放进去,以浮点型取的时候;或者以浮点型方式放进去,以整型型取的时候,两个都不是预测的那样,所以整型与浮点型在内存中的存储方式是有差异的。

浮点数存储规则:

根据国际标准 IEEE (电子和电子工程协会) 754,任意一个二进制浮点数 V 可以表示成下面的形式:

1. (-1) ^ S * M * 2 ^ E

2. (-1) ^ S 表示符号位,当 S = 0,V 为正数;当 S = 1,V 为负数。

3. M 表示有效数字,大于等于 1 ,小于 2。

4. 2 ^ E 表示指数位。

举例说:

5.25 —— 10 进制的浮点数

二进制表示为:101.01 (为什么这么写?)

扩展一下:

 所以 5 的二进制 101.01 按照标准写法:(小数点向左移 2 位)

再举个例子:

3.125 —— 10 进制浮点数

二进制数为:11.001 (转法与 5.25 一样)

那么 3.125 的二进制 11.001 标准写法是:

那么浮点数这样写怎么存的?

IEEE 754规定:

对于 32 位的浮点数,最高的 1 位是符号位 S,接着的 8 位是指数 E,剩下的 23 位为有效数字 M,如下:

对于 64 位的浮点数,最高的 1 位是符号位 S,接着的 11 位是指数 E,剩下的 52 位为有效数字 M,如下:

IEEE 754 对于有效数字 M 和指数 E,还有一些规定:

1 <= M < 2,所以 M 也可以写成 1. xxxxx....的形式,xxxxxx 表示小数部分。

IEEE 754 规定,在计算机内部保存 M 时,默认这个数的第一位总是 1 ,因此可以被舍去,只保存后面的 xxxxxx.... 部分,比如保存 1. 10001 的时候,只保存 10001 ,等到读取的时候,再把第一位的 1 加上,这样可以节省 1 位有效数字,以 32 位为例,留给 M 只有 23 位将第一位的 1 舍去等于可以保存 24 位有效数字。(精度更高)

指数 E 比较复杂。

首先,E 是无符号整数(unsigned int),所以如果 E 为 8 位,它的取值范围是 0 — 255;如果 E 为 11 位,它的取值范围为 0 —— 2047 。但科学计数法中的 E 是可以负数的,所以 IEEE 754 规定,存入内存时 E 的真实值必须再加上一个中间数,对于 8 位的 E ,这个中间数是 127 ;对于 11 位的 E ,这个中间数是 1023,比如 2^10 的 E 是10,所以保存成 32 位浮点数时,必须保存成 10+127 = 137 即 10001001。

然后,指数 E 从内存中取出还可以分为三种情况:

E 不全为 0 或者 不全为 1:

这时候,浮点数用下面的规则表示,即指数 E 的计算值减去 127 (或者1023),得到真实值,再将有效数字 M 前加上第一位的 1。

例如:

#include <stdio.h>

int main()
{
    float a = 2.125;
    // 2.125 二进制为===> 10.001
    // (-1)^0 * 1.0001 * 2^1
    // S = 0
    // M = 1.0001
    // E = 1

    // 因为是正数最高位是 0
    // 因为要加中间数所以 1 + 127 = 128 ; 128 的二进制是 ==> 10000000
    // M 的话,不存小数点前面的数,只存小数点后面的数,但是位不够后面补 0 
    // 所以存储到内存为:
    // 01000000000010000000000000000000
    //
    // 可以查看一下内存中是不是这个数?
    // 0100 0000 0000 1000 0000 0000 0000 0000
    //   4   0    0     8    0    0    0   0
    // 0x40080000

    return 0;
}

调试查看内存如下:

 根据小端存储规则,在内存中存的就是 0x40080000

比如:

0.5 (10 进制)的二进制形式为 0.1 ,由于规定正数部分必须为 1,即将小数点右移 1 位,则位 1.0*2^(-1),其阶码为 -1 + 127 =126(如果是 11 位的 E 的话=> -1+1023=1022),表示为 01111110,而尾数 1.0 去掉整数部分为 0 ,补齐 0 到 23 位 00000000000000000000000,则其二进制表示为:

0 01111110 00000000000000000000000

E 全为 0:

这时,浮点数的指数 E 等于 1 - 127(或者 1 - 1023)即为真实值。有效数字 M 不再加上第一位的 1,而是还原为 0.xxxxxx....的小数。这样做是为了表示 ±0 ,以及接近于 0 的很小的数字。

E 全为 1:

这时,如果有效数字 M 全为 0,表示 ± 无穷大(正负取决于符号位 S)。

回到最开始的题:

    int n = 9;
    // 00000000000000000000000000001001 —— 9 的二进制(原码、反码、补码相同)

    float* pFloat = (float*)&n; 
    printf("n的值是:%d\n", n); // 以 %d 打印 还是 9

    printf("*pFloat的值是:%f\n", *pFloat); 
    // 以浮点数打印的话, pFloat 就当它是浮点型, 就按浮点类型算:
    // 最高位 S 是 1 bit ; M 是 8 个bit ; E 是 23 个bit
    // 0 00000000 00000000000000000001001
    // 这里 E 是全 0, 所以 E 的真实值是 1 - 127 = -126
    // M 就是 0.00000000000000000001001 (不在加前面的 1)
    // (-1)^0 * 0.00000000000000000001001 * 2^-126 
    //  M 已经很小了, 再 *2^-126 这是无限接近于 0 的数
    //  所以打印 0 
    *pFloat = 9.0;
    // 1001.0 —— 9.0 二进制
    // 9.0 科学表示法==> (-1)^0 * 1.001 * 2^3
    // S = 0
    // M = 1.001
    // E = 3
    // 往内存存的话是:
    // E 加上修正值 127 ====> 3+127=130 ; 130 二进制是 10000010
    // 0 10000010 00100000000000000000000


    printf("n的值是:%d\n", n); 
    // 以 %d 打印的话, n 就把那串二进制当作是有符号数, 高位是 0 那就是正数(原码、反码、补码相同)
    // 那么打印的就是 01000001000100000000000000000000 ===> 1091567616 

    printf("*pFloat的值是:%f\n", *pFloat); // 浮点数放进去拿出来还是 9.0

运行结果对比一下,所以浮点型在内存中的存储就介绍完了。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值