浮点数在内存中的存储以及用指针改变内存与强制转换的区别

简介: 浮点数在内存中的存储以及用指针改变内存与强制转换的区别

浮点型在内存中的存储

引例

  • 我们先来看下面一段代码
#include<stdio.h>
int main()
{
  int n = 9;
  float* pFloat = (float*)&n;
  printf("n的值为:%d\n", n);
  printf("pFloat的值为:%f\n", *pFloat);
  *pFloat = 9.0;
  printf("n的值为:%d\n", n);
  printf("*pFloat的值为:%f\n", *pFloat);
  return 0;
}
  • 可能大部分小伙伴都会认为,打印的结果分别是:
n的值为:9
pFloat的值为:9.000000
n的值为:9
*pFloat的值为:9.000000
  • 实际上,运行得到的结果为:
n的值为:9
pFloat的值为:0.000000
n的值为:1091567616
*pFloat的值为:9.000000
  • 可以看到,操作float* pFloat = (float*)&n,我们将整型n以浮点数的形式存入float型指针pFloat中,然后发现printf("pFloat的值为:%f\n", *pFloat)打印结果错误
  • 当我们执行*pFloat = 9.0这一操作,即向整型n中存入float型的值,发现printf("n的值为:%d\n", n)打印结果错误。
  • 由此我们可以知道,在内存中,浮点数和整数的存储形式是不一样的,那具体有哪些差别呢?

浮点数的表示形式

  • 根据国际标准IEEE(电器和电子工程协会)754任意一个二进制浮点数v都可以表示为这个形式:(-1)^S * M * 2 ^ E
  • (-1) ^ S表示符号位,当S为0时表示正数,当S为1时表示负数。
  • M表示有效数字,大于等于1,小于2
  • 2 ^ E表示指数位
  • 举个例子:
  • 例如十进制浮点数5.5
  • 其二进制形式为:101.1(如果不会怎么转换,请看二进制、八进制、十六进制与十进制的相互关系
  • 再用科学计数法表示:1.011 * 22
  • 加上符号位:(-1)0 * 1.011 * 22
  • 即这里的S = 0,M = 1.011,E = 2
  • 因此,对浮点数进行存储时,只需要对S,M,E这三个数进行存储就行了

浮点数的存储

  • IEEE 754规定:
  • 对于32位的浮点数(float型),最高的1位是符号位S,接着的8位是指数E,剩下的23位是有效数字M

  • 对于64位的浮点数(double型),最高的1位是符号位S,接着的11位是指数E,剩下的52位是有效数字M

  • IEEE 754对有效数字M和指数E还有些特殊的规定:
  • 对于有效数字M:
  • 之前说过,1<=M<2因此M总是可以写成1.XXXXX这样的形式
  • 754规定,在计算机内部保存有效数字M时,默认这个数的第一位总是1,因此可以被舍去,只保留后面的xxxxxx部分。
  • 等到读取的时候,再把第一位的1加上去。
  • 这样做,就可以节省一位有效数字,以double型为例,原本M为52,只能存储52为有效数字,但舍去第一位后,就相当于可以存储53位数字了。
  • 对于指数E:
  • 首先我们需要知道,指数E为一个无符号整数(unsigned int)
  • 以float型为例,E所能表示的范围是0~255。但问题是,在实际的表示中,指数E也可能存在负数的情况,例如十进制浮点数0.5,转换为二进制为0.1,则S = 1,E = -1,M = 1,那么怎么处理E为负数的情况呢?
  • IEEE 754规定,存入内存时E的真实值必须再加上一个中间数,对于8位的E,这个中间数是127,对于11位的E,这个中间数是1023。例如对于十进制浮点数0.5,存入内存时,E的值应该是-1 + 127 = 126
  • 将指数E从内存取出要分以下三种情况:
E不全为零且E不全为1
  • 这时,指数E的真实值就是存储值减去中间值(127/1023)
  • 有效数字M需要加上第一位的1。
E全为0
  • 这时,指数E的真实值就是(1 - 127) / (1 - 1023)(标准规定)
  • 有效数字M不需要加上第一位的1,而是还原为0.xxxxxxx的小数这样做是为了表示±0,以及几近于0的极小的数字(标准规定)
E全为1
  • 这时,有效数字M全为0,表示±∞(正负取决于符号位)(标准规定)

Eg

对于十进制浮点数5.5

十进制float型 -> 5.5
二进制 -> 101.1
二进制科学技术 -> 1.011 * 2 ^ 2
加上符号位 -> (-1) ^ 0 * 1.011 * 2 ^ 2
S = 0, M = 1.011, E = 2
存入到二进制序列:0 10000001 01100000000000000000000
转换为十六进制:4 0 B 0 0 0 0 0

总结

  • 我们再来回顾上面引例的代码:
#include<stdio.h>
int main()
{
  int n = 9;
  float* pFloat = (float*)&n;
  printf("n的值为:%d\n", n);
  printf("pFloat的值为:%f\n", *pFloat);
  *pFloat = 9.0;
  printf("n的值为:%d\n", n);
  printf("*pFloat的值为:%f\n", *pFloat);
  return 0;
}
  • 整数9在内存中的存储形式为0000 0000 0000 0000 0000 0000 0000 1001
  • 我们用float类型的指针pFloat指向这段整型内存,那么根据上面的知识,我们可以得到:

  • 指数E全为0,因此printf("pFloat的值为:%f\n", *pFloat);打印出来的结果就是pFloat的值为:0.000000
  • 之后,将这段内存改为浮点数9.0
  • 9.0的二进制序列为:1001.0
  • 写成科学计数法为:(-1) ^ 0 * 1.001 * 2 * 3,即S = 0, E = 3, M = 1.001(存入内存时,E = 127 + 3 = 130,M = 0.001),即:0 10000010 00100000000000000000000

  • 如果我们用读取整数的方法来读取这段内存,那结果就是1,091,567,616也就是printf("n的值为:%d\n", n)的打印结果

用指针改变内存和强制转换的区别

  • 通过对浮点数在内存中如何储存的了解,我们应该知道,用指针改变内存的读取和强制转换变量类型是两个截然不同的操作
  • 强制转换类型:
float num_1 = 3.14;
int num_2 = (int)num_1;
printf("%d\n",num_2);
/*
  将float强制转换为int只是简单的对小数点之后的数据进行舍去,而没有改变对内存的处理
  打印结果为3.
*/
  • 指针改变内存
float num_1 = 9.5;
int *num_2 = (int *)&num_1;
printf("%d\n",*num_2);
/*
  用读取整型数据的方式读取float型的内存
  打印结果为1092091904
*/


相关文章
|
存储 缓存 安全
阿里云服务器计算型c7/c8y/c8i,通用型g7/g8y/g8i,内存型r7/r8y/r8i区别及选择参考
为了满足不同企业级用户的多样化需求,阿里云在当下的活动中推出了多款计算型、通用型和内存型的云服务器实例,包括计算型c7/c8y/c8i、通用型g7/g8y/g8i以及内存型r7/r8y/r8i等。这些实例各具特色,适用于不同的应用场景和业务需求。本文将为您详细解析这些实例的区别,以及选择参考,帮助您根据自己的需求选择合适的阿里云服务器实例。
|
存储 固态存储
磁盘和内存的区别
存储特性: • 磁盘:非易失性存储,数据在断电后不会丢失,适合长期存储数据。 • 内存:易失性存储,数据在断电后会丢失,适合临时存储当前运行的程序和数据。 容量: • 磁盘:容量通常较大,从几百GB到数TB不等,适合存储大量的文件和数据。 • 内存:容量相对较小,一般在几GB到几十GB之间,用于提供快速的临时存储空间。 速度: • 磁盘:读写速度较慢,HDD一般在几十MB/s,SSD可以达到几百MB/s甚至数GB/s。 • 内存:读写速度非常快,通常在几十纳秒到几百纳秒之间,能够快速响应CPU的指令。
1336 2
|
存储 缓存 资源调度
阿里云服务器经济型、通用算力型、计算型、通用型、内存型实例区别与选择指南
在我们通过阿里云的活动选购云服务器的时候会发现,相同配置的云服务器往往有多个不同的实例可选,而且价格差别也比较大,这会是因为不同实例规格的由于采用的处理器不同,底层架构也有所不同(例如X86 计算架构与Arm 计算架构),因此不同实例的云服务器其性能与适用场景是有所不同。本文将详细解析阿里云的经济型、通用算力型、计算型、通用型和内存型实例的性能特点及适用场景,帮助用户根据自己的业务需求做出明智的选择。
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
2134 13
|
存储 C语言 开发者
C 语言指针与内存管理
C语言中的指针与内存管理是编程的核心概念。指针用于存储变量的内存地址,实现数据的间接访问和操作;内存管理涉及动态分配(如malloc、free函数)和释放内存,确保程序高效运行并避免内存泄漏。掌握这两者对于编写高质量的C语言程序至关重要。
710 11
|
存储 算法 程序员
C 语言指针详解 —— 内存操控的魔法棒
《C 语言指针详解》深入浅出地讲解了指针的概念、使用方法及其在内存操作中的重要作用,被誉为程序员手中的“内存操控魔法棒”。本书适合C语言初学者及希望深化理解指针机制的开发者阅读。
|
存储 C语言 计算机视觉
在C语言中指针数组和数组指针在动态内存分配中的应用
在C语言中,指针数组和数组指针均可用于动态内存分配。指针数组是数组的每个元素都是指针,可用于指向多个动态分配的内存块;数组指针则指向一个数组,可动态分配和管理大型数据结构。两者结合使用,灵活高效地管理内存。
|
容器
在使用指针数组进行动态内存分配时,如何避免内存泄漏
在使用指针数组进行动态内存分配时,避免内存泄漏的关键在于确保每个分配的内存块都能被正确释放。具体做法包括:1. 分配后立即检查是否成功;2. 使用完成后及时释放内存;3. 避免重复释放同一内存地址;4. 尽量使用智能指针或容器类管理内存。
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
886 4
|
存储 缓存 安全
阿里云服务器内存型r7、r8a、r8y、r8i实例区别及选择参考
随着阿里云2024年金秋云创季的开始,目前在阿里云的活动中,属于内存型实例规格的云服务器有内存型r7、内存型r8a、内存型r8y和内存型r8i这几个实例规格,相比于活动内的经济型e和通用算力型u1等实例规格来说,这些实例规格等性能更强,虽然这几个实例规格的云服务器通常处理器与内存的配比为都是1:8,但是他们在处理器、存储、网络、安全等方面等性能并不是一样的,所以他们的适用场景也有着不同。本文为大家介绍内存型r7、r8a、r8y、r8i实例的性能、适用场景的区别以及选择参考。