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

2024-10-09 14:57:04 浏览数 (3)

案例引入

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

代码语言:javascript复制
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
	int n = 9;
	float* pFloat = (float*)&n;
	printf("n的值为:%dn", n);
	printf("*pFloat的值为:%fn", *pFloat);
	*pFloat = 9.0;
	printf("num的值为:%dn", n);
	printf("*pFloat的值为:%fn", *pFloat);
	return 0;
} 

结果如下:

由此可知,C语言中浮点数的存储方式和整数的存储方式是不同的,下面就让我们详细了解一下。

引言

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

1. 浮点数的基本概念

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

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

2. IEEE 754标准

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

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

V=(-1)^S*M*2^EV=(-1)^S*M*2^E

(−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位表示为:

代码语言:javascript复制
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位表示为:

代码语言:javascript复制
1 10000000001 0111000000000000000000000000000000000000000000000000

3. 内存中的浮点数存储

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

代码语言:javascript复制
#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("X ", byte[i - 1]);
    }
    printf("n");
}
int main() {
    float f = -5.75;
    double d = -5.75;
    printf("Float value: %fn", f);
    printf("Double value: %lfn", 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以整型的形式存储在内存中,得到如下⼆进制序列:

代码语言:javascript复制
 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 所以:

9.0 =(-1)^0*(1.001)*2^39.0 =(-1)^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语言中的浮点数存储有所帮助。

0 人点赞