C语言中的浮点数存储:深入探讨

简介: C语言中的浮点数存储:深入探讨

案例引入

请看下面一段代码并思考结果:

#define _CRT_SECURE_NO_WARNINGS
#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("num的值为:%d\n", n);
  printf("*pFloat的值为:%f\n", *pFloat);
  return 0;
} 

结果如下:

引言

在C语言中,浮点数用于表示实数,尤其是那些带有小数点的数值。浮点数的存储机制复杂,但它是计算机科学中的重要组成部分。本文将详细介绍C语言中的浮点数在内存中的存储方式,基于IEEE 754标准,并涵盖单精度和双精度浮点数的内部表示。

1. 浮点数的基本概念

浮点数的表示由三个主要部分组成:

  • 符号位(Sign Bit):表示数值的正负。0表示正数,1表示负数。
  • 指数位(Exponent):确定浮点数的范围或数量级。通过调整指数,浮点数可以表示非常大或非常小的值。
  • 尾数(Mantissa):也称为有效数字,表示浮点数的精确值。尾数在计算中决定了浮点数的精度。

2. IEEE 754标准

IEEE 754标准是浮点数存储的国际标准,定义了浮点数的表示和运算规则。

根据国际标准IEEE(电⽓和电⼦⼯程协会) 754,任意⼀个⼆进制浮点数V可以表⽰成下⾯的形式:

(−1)^S 表⽰符号位,当S=0,V为正数;当S=1,V为负数

 M 表⽰有效数字,M是⼤于等于1,⼩于2的

2^E 表⽰指数位

浮点数的存储,实际上存储的就是S、M、E相关的值。

根据IEEE 754标准,浮点数分为单精度(32位)和双精度(64位)两种格式。

2.1 单精度浮点数(32位)

单精度浮点数使用32位存储,其中包括:

  • 符号位:1位
  • 指数位:8位
  • 尾数:23位(实际尾数有24位,因为有一个隐含的1位)

单精度浮点数的存储格式如下:

对于32位的浮点数,最⾼的1位存储符号位S,接着的8位存储指数E,剩下的23位存储有效数字M。

存储示例:

假设我们要存储浮点数 -5.75。首先将 -5.75 转换为二进制格式,并按照IEEE 754标准进行编码。

表示步骤:

符号位:-5.75 是负数,所以符号位是 1。

绝对值转换为二进制:5.75 的二进制表示是 101.11。

标准化:将 101.11 转换为标准化形式 1.0111 × 2^2。

阶码:IEEE 754 使用偏移量为 127 的阶码。在这里,实际阶码是 2,所以存储的阶码是 2 + 127 = 129,其二进制形式是 10000001。

尾数:标准化形式的尾数部分是 0111,补充至 23 位得到 01110000000000000000000。

因此,-5.75的32位表示为:

1 10000001 01110000000000000000000

2.2 双精度浮点数(64位)

双精度浮点数使用64位存储,其中包括:

  • 符号位:1位
  • 指数位:11位
  • 尾数:52位(实际尾数有53位,因为有一个隐含的1位)

双精度浮点数的存储格式如下:

对于64位的浮点数,最⾼的1位存储符号位S,接着的11位存储指数E,剩下的52位存储有效数字M 。

存储示例: 对于浮点数 -5.75,转换步骤如下:

表示步骤:

符号位:-5.75 是负数,所以符号位是 1。

绝对值转换为二进制:5.75 的二进制表示是 101.11。

标准化:将 101.11 转换为标准化形式 1.0111 × 2^2。

阶码:IEEE 754 使用偏移量为 1023 的阶码。在这里,实际阶码是 2,所以存储的阶码是 2 + 1023 = 1025,其二进制形式是 10000000001。

尾数:标准化形式的尾数部分是 0111,补充至 52 位得到 0111000000000000000000000000000000000000000000000000。

最终,64 位双精度浮点数的表示为:

因此,-5.75的64位表示为:

1 10000000001 0111000000000000000000000000000000000000000000000000

3. 内存中的浮点数存储

浮点数在内存中的实际存储取决于系统的字节序(大端或小端)。例如,在大端系统中,较低位字节存储在较高内存地址。以下是如何查看浮点数在内存中的实际存储示例:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
// 将浮点数以大端格式打印
void print_memory_representation(void* p, size_t size) {
    unsigned char* byte = (unsigned char*)p;
 
    // 大端打印需要从高位字节开始
    for (size_t i = size; i > 0; i--) {
        printf("%02X ", byte[i - 1]);
    }
    printf("\n");
}
int main() {
    float f = -5.75;
    double d = -5.75;
    printf("Float value: %f\n", f);
    printf("Double value: %lf\n", d);
    printf("Float类型内存表示(大端):\n");
    print_memory_representation(&f, sizeof(f));
    printf("Double类型内存表示(大端):\n");
    print_memory_representation(&d, sizeof(d));
    return 0;
}

在此代码中,print_memory_representation 函数将浮点数在内存中的每个字节打印为十六进制格式。运行代码可以观察到浮点数在内存中的具体存储方式。

4. 精度问题与误差

浮点数表示有限精度的实数,可能导致精度问题。例如,0.1 不能精确地用二进制表示,这会在浮点运算中引入微小的误差。因此,比较浮点数时,通常要考虑误差范围,使用一个接近的值进行比较而非直接等于比较。

题⽬解析

下⾯,让我们分析一开始的案例。

先看第1环节,为什么 9 还原成浮点数,就成了 0.000000 ? 9以整型的形式存储在内存中,得到如下⼆进制序列:

1 0000 0000 0000 0000 0000 0000 0000 1001

⾸先,将 9 的⼆进制序列按照浮点数的形式拆分,得到第⼀位符号位s=0,后⾯8位的指数

E=00000000 , 最后23位的有效数字M=000 0000 0000 0000 0000 1001。 由于指数E全为0,所以符合E为全0的情况。因此,浮点数V就写成:

V=(-1)^0 × 0.00000000000000000001001×2^(-126)=1.001×2^(-146)

显然,V是⼀个很⼩的接近于0的正数,所以⽤⼗进制⼩数表⽰就是0.000000

再看第2环节,浮点数9.0,为什么整数打印是 1091567616

⾸先,浮点数9.0 等于⼆进制的1001.0,即换算成科学计数法是:1.001×2^3

所以: 那么,第⼀位的符号位S=0,有效数字M等于001后⾯再加20个0,凑满23位,指数E等于3+127=130, 即10000010

所以,写成⼆进制形式,应该是S+E+M,即 1 0 10000010 001 0000 0000 0000 0000 0000

这个32位的⼆进制数,被当做整数来解析的时候,就是整数在内存中的补码,原码正是

1091567616

总结

C语言中的浮点数存储是一个复杂而重要的主题。它涉及到符号位、指数位和尾数的详细布局,以及IEEE 754标准的规范。通过理解浮点数的存储机制,你可以更好地处理浮点数的计算和调试问题。希望本文对你理解C语言中的浮点数存储有所帮助。


相关文章
|
2月前
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
187 0
|
28天前
|
存储 编译器 C语言
C语言存储类详解
在 C 语言中,存储类定义了变量的生命周期、作用域和可见性。主要包括:`auto`(默认存储类,块级作用域),`register`(建议存储在寄存器中,作用域同 `auto`,不可取地址),`static`(生命周期贯穿整个程序,局部静态变量在函数间保持值,全局静态变量限于本文件),`extern`(声明变量在其他文件中定义,允许跨文件访问)。此外,`typedef` 用于定义新数据类型名称,提升代码可读性。 示例代码展示了不同存储类变量的使用方式,通过两次调用 `function()` 函数,观察静态变量 `b` 的变化。合理选择存储类可以优化程序性能和内存使用。
140 82
|
29天前
|
存储 人工智能 C语言
数据结构基础详解(C语言): 栈的括号匹配(实战)与栈的表达式求值&&特殊矩阵的压缩存储
本文首先介绍了栈的应用之一——括号匹配,利用栈的特性实现左右括号的匹配检测。接着详细描述了南京理工大学的一道编程题,要求判断输入字符串中的括号是否正确匹配,并给出了完整的代码示例。此外,还探讨了栈在表达式求值中的应用,包括中缀、后缀和前缀表达式的转换与计算方法。最后,文章介绍了矩阵的压缩存储技术,涵盖对称矩阵、三角矩阵及稀疏矩阵的不同压缩存储策略,提高存储效率。
|
1月前
|
存储 算法 C语言
数据结构基础详解(C语言): 二叉树的遍历_线索二叉树_树的存储结构_树与森林详解
本文从二叉树遍历入手,详细介绍了先序、中序和后序遍历方法,并探讨了如何构建二叉树及线索二叉树的概念。接着,文章讲解了树和森林的存储结构,特别是如何将树与森林转换为二叉树形式,以便利用二叉树的遍历方法。最后,讨论了树和森林的遍历算法,包括先根、后根和层次遍历。通过这些内容,读者可以全面了解二叉树及其相关概念。
|
1月前
|
存储 机器学习/深度学习 C语言
数据结构基础详解(C语言): 树与二叉树的基本类型与存储结构详解
本文介绍了树和二叉树的基本概念及性质。树是由节点组成的层次结构,其中节点的度为其分支数量,树的度为树中最大节点度数。二叉树是一种特殊的树,其节点最多有两个子节点,具有多种性质,如叶子节点数与度为2的节点数之间的关系。此外,还介绍了二叉树的不同形态,包括满二叉树、完全二叉树、二叉排序树和平衡二叉树,并探讨了二叉树的顺序存储和链式存储结构。
|
1月前
|
存储 算法 C语言
C语言手撕数据结构代码_顺序表_静态存储_动态存储
本文介绍了基于静态和动态存储的顺序表操作实现,涵盖创建、删除、插入、合并、求交集与差集、逆置及循环移动等常见操作。通过详细的C语言代码示例,展示了如何高效地处理顺序表数据结构的各种问题。
|
1月前
|
存储 缓存 程序员
c语言的存储类型-存储类
本文详细介绍了C语言中的存储类型及其分类,包括基本类型(如整型、浮点型)和复合类型(如数组、结构体)。重点讲解了不同存储类别(`auto`、`static`、`register`、`extern`、`typedef`、`volatile`、`const`)的特点及应用场景,并展示了C11/C99引入的新关键字(如`_Alignas`、`_Atomic`等)。通过示例代码解释了每个存储类别的具体用法,帮助读者更好地理解和运用这些概念。
|
2月前
|
存储 API C语言
【C语言】 作用域和存储期
【8月更文挑战第18天】
|
2月前
|
存储 C语言
【C语言】进程间通信之存储映射区mmap
【C语言】进程间通信之存储映射区mmap
30 0