【发布时间】:2014-01-27 15:10:18
【问题描述】:
我对以下代码的输出感到困惑:
float a = 1.32;
int* b;
b= &a;
printf("%d", *b);
这段代码把float变成了int,但是我得到的输出是:1068037571
这与计算机的 IEEE 754 转换能力有关吗? 谢谢!
【问题讨论】:
标签: c floating-point integer type-conversion
我对以下代码的输出感到困惑:
float a = 1.32;
int* b;
b= &a;
printf("%d", *b);
这段代码把float变成了int,但是我得到的输出是:1068037571
这与计算机的 IEEE 754 转换能力有关吗? 谢谢!
【问题讨论】:
标签: c floating-point integer type-conversion
您的程序调用了未定义的行为。您只是将float 的表示重新解释为int。您绝对不会将int 转换为float。你不能期待任何特定的行为。
要将 float 转换为 int,您可以使用如下代码:
int b = a;
将float 截断为int。
【讨论】:
a 的按位表示,并将该组位解释为int。并希望这两种数据类型具有相同的大小。编译代码时是否看到编译器警告?
*b 具有含义,那么您的教授要么是错误的,要么是指的是保证这种非标准行为的特定 C 实现。检查编码浮点a 的字节的正确方法是将其表示的字节复制到另一个对象中,例如unsigned int b; memcpy(&b, &a, sizeof b);,或者使用联合,例如unsigned int b = (union {float f; unsigned int u;}){a}.u;。这两个都需要sizeof(float) == sizeof(unsigned int)。
unsigned int。这样做只是为了检查浮点数的编码,而不是它的值。要使用浮点数的值,只需在表达式中正常使用它,包括强制转换和赋值。
您正在对代码执行的操作是查看浮点数的存储方式。一个浮点数(通常)在内存中占用 4 个字节,如下(来自维基百科的示例):
当你运行你的代码时,你假装这些位是一个四字节整数:
float a = 1.32;
int* b;
b= &a;
printf("%d", *b);
如果您想查看十六进制表示,只需这样做
printf("%08x", *b);
你会得到
3f9d70a4
表示位模式是
00111111100111010111000010100100
分手:
0 01111111 00111010111000010100100
符号位0
指数01111111
分数(1)00111010111000010100100
你会发现那个二进制数
100111010111000010100100 = 10317988
还有那个
10317988.0 / (4096.0*2048.0) = 1.23
更新完整的程序,详细说明如何执行此操作:
#include <stdio.h>
#include <math.h>
#include <stdint.h>
int main(void) {
float a = 1.23;
uint32_t *b = (uint32_t *)&a;
uint32_t signbit;
uint32_t exponent;
uint32_t mantissa;
uint32_t fpAsInt;
fpAsInt = *b;
signbit = (fpAsInt & 0x80000000) >> 31;
exponent = (fpAsInt & 0x7F800000) >> 23 ;
mantissa = (fpAsInt & 0x007FFFFF) | 0x00800000;
printf("fpAsInt: 0x%08x\n", fpAsInt);
printf("sign bit: %d\n", signbit);
printf("exponent: 0x%02x\n", exponent);
printf("mantissa: 0x%03x\n", mantissa);
printf("the value is %10f\n", ((signbit == 1)?-1.0:1.0)*mantissa / pow(2.0, (127 - exponent + 23)));
printf("the original value was %10f\n", a);
}
打印结果
fpAsInt: 0x3f9d70a4
sign bit: 0
exponent: 0x7f
mantissa: 0x9d70a4
the value is 1.2300000191
the original value is 1.2300000191
我在最后一行使用浮点数学似乎是作弊 - 有道理的批评,但这里的重点是展示浮点是如何构造的,而不是如何使用整数数学来提取值。
注意 - 我假设浮点数是 IEEE 4 字节表示。此外,我还包括了一些特定的转换来消除编译器警告。仅当您确定自己知道自己在做什么时才这样做...
更多编辑
有人指出,代码仍然存在未定义的行为。为了解决这个问题,并且仍然让您对上述内容有所了解,让我们再做一次。现在我正在使用位数组和浮点数的联合来“合法地”访问不同的元素 - 并且在使用 -Wall -pedantic 编译时没有收到任何警告。这可能还不够,但这是我所知道的最好的......
#include <stdio.h>
#include <math.h>
#include <stdint.h>
union ieee754_float
{
float f;
/* This is the IEEE 754 single-precision format on a little-endian machine. */
struct
{
unsigned int mantissa:23;
unsigned int exponent:8;
unsigned int negative:1;
} ieee;
};
int main(void) {
float a = 1.23;
union ieee754_float *pa;
pa = (union ieee754_float*)&a;
uint32_t signbit;
uint32_t exponent;
uint32_t mantissa;
signbit = pa->ieee.negative;
exponent = pa->ieee.exponent;
mantissa = pa->ieee.mantissa | 0x00800000;
printf("sign bit: %d\n", signbit);
printf("exponent: 0x%02x\n", exponent);
printf("mantissa: 0x%03x\n", mantissa);
printf("the value is %.10f\n", ((signbit == 1)?-1.0:1.0)*mantissa / pow(2.0, (127 - exponent + 23)));
printf("the original value is %.10f\n", a);
}
【讨论】:
float 和int 都存储在四个字节中。您可以更可靠地编写代码以确保确实如此 - 例如,使用<stdint.h>,然后声明uint32_t。
floats 必须以这样一种方式对齐,它们也可以被硬件解释为uint32_t。跨度>
StackOverflow 上C 标签中的许多问题都可以通过回到这些基本规则来回答:
& 接受一个变量并产生一个指针* 接受一个指针并产生一个变量所以你说“取这个浮点变量,让我成为一个指针。现在取那个指针,让我成为一个 int 变量。”你从来没有说过“取这个浮点值并让我成为一个 int 值”。您说“取一个可以转换为浮点变量的指针并将其转换为 int 变量”。当你这样做时会发生什么?这完全由编译器决定。
【讨论】:
*b 未定义行为,并且它已经有十多年没有“通常工作”了(google “gcc type-based alias analysis”)。 6.3.2.3:7 为(uint32_t *)&a 在某些实现上定义敞开大门:我将把这个条款总结为说明它是实现定义的,这种转换的作用(UB 是选择之一,但不是选择OP 的编译器,除非 OP 要求使用 70 年代的一些奇怪的计算机)。