提问人:Fallendo 提问时间:9/29/2023 最后编辑:chqrlieFallendo 更新时间:9/30/2023 访问量:84
为什么类型转换为整数的浮点数的十六进制值与浮点数的实际十六进制值不同
why is the hexadecimal value of a float typecasted into an integer different from the float's actual hexadecimal value
问:
为什么程序打印的十六进制值彼此不同,因为知道 -12.342(浮点数)和存储在 bi 中的值的二进制表示是相同的?
#include <stdio.h>
int main() {
float bf = -12.342;
int bi = *(int *)&bf;
float rbf = *(float *)&bi;
printf("%X %p\n", bf, &bf);
printf("%X %p\n", bi, &bi);
printf("%X %p\n", rbf, &rbf);
return 0;
}
了解 C 语言中的基础转换。
答:
float
当参数指向可变函数(如 )时,值将提升为值。所以你传递的是 ,但 是 的预期。因此,虽然 和 可能对您具有相同的大小,但 显然不是 的情况。double
printf
double
unsigned int
%X
int
float
int
double
修复,产生合理的结果。
#include <stdio.h>
void dump( void *p, size_t n ) {
char *cp = p;
while ( n-- )
printf( "%02hhX", *(cp++) );
printf( "\n" );
}
int main( void ) {
printf( "%zu\n", sizeof( float ) );
printf( "%zu\n", sizeof( int ) );
float bf = -12.342;
int bi = *(int*)&bf;
float rbf = *(float*)&bi;
dump( &bf, sizeof( bf ) );
dump( &bi, sizeof( bi ) );
dump( &rbf, sizeof( rbf ) );
}
4
4
D57845C1
D57845C1
D57845C1
(请记住,字节在 little-endian 机器上是“向后”的。
int bi = *(int*)&bf;
而且还是各种错。但我们只是在探索,对吧?float rbf = *(float*)&bi;
以 System V Application Binary Interface AMD64 Architecture Processor Supplement 为例进行说明(其他 ABI 将以不同的方式将参数传递给函数):
对于 ,第一个参数(指向格式说明符字符串文字的指针)将在寄存器中传递,因为它是 INTEGER 类中的第一个参数(因为就此 ABI 而言,指针位于 INTEGER 类中),第二个参数(转换为后的值)将在寄存器中传递,因为它是 SSE 类中的第一个参数, 第三个参数(指向 的指针)将在寄存器中传递,因为它是 INTEGER 类中的第二个参数。但是,该函数期望第一个参数是寄存器中的指针 (OK),第二个参数是寄存器中的指针(错误的寄存器!),第三个参数是寄存器中的指针(错误的寄存器!)。由于值位于错误的寄存器中,因此输出与预期不符。
printf("%X %p\n",bf, &bf);
%rdi
bf
double
%xmm0
bf
%rsi
printf
%rdi
unsigned int
%rsi
%rdx
对于 ,第一个参数(指向格式说明符字符串文本的指针)将在寄存器中传递,因为它是 INTEGER 类中的第一个参数,第二个参数(的值 )将在寄存器中传递,因为它是 INTEGER 类中的第二个参数,第三个参数(指向 的指针)将在寄存器中传递,因为它是 INTEGER 类中的第三个参数。这些参数都位于函数期望找到它们的寄存器中,因此输出是可以的(忽略一些微妙之处,例如在预期时传递 an,在预期时传递 a,实现并不真正关心)。
printf("%X %p\n",bi, &bi);
%rdi
bi
%rsi
bi
%rdx
printf
int
unsigned int
int *
void *
对于 ,情况与 相同。
printf("%X %p\n",rbf, &rbf);
printf("%X %p\n",bf, &bf);
printf
声明为在格式字符串参数之后(即在第一个参数之后)使用可变数量的参数进行调用。这些是可变参数。对可变参数进行的唯一转换是默认参数升级,将任何小于 的整数类型化值转换为 ,并将任何值转换为 。 使用格式字符串来确定每个可变参数的预期类型,并且 ABI 确定在何处查找不同类型的参数。每个可变参数的预期类型与参数的实际类型(在默认参数升级之后)之间的任何不匹配都将导致未定义的行为。int
int
float
double
printf
代码中的其他未定义行为是由于违反了表达式中的严格别名规则,例如 。*(int*) &bf
为什么浮点数的十六进制值是...?
考虑退后一步,与其使用 OP 的不可移植代码或字节转储(其他好的答案)打印“十六进制”值,不如考虑使用 ,以类似十六进制的标准输出进行打印。"%a"
"%A"
#include <float.h>
#include <stdio.h>
int main() {
float bf = -12.342f;
printf("%.*g\n", FLT_DECIMAL_DIG, bf);
printf("%a\n", bf);
printf("%A\n", bf);
}
-12.342
-0x1.8af1aap+3
-0X1.8AF1AAP+3
^^ Decimal power-of-2 exponent
^^^^^^^^ Hexadecimal significand
^ Sign
请注意,8AF1AA 的 23 个最高有效位是 4578D5,并且与 binary32 编码的分数匹配得多么好。
启用所有警告以节省时间。
次要:而不是用常量初始化 a。使用常量。有时它会有所作为。
float
double
float
// float bf = -12.342;
float bf = -12.342f;
发布的代码具有多个未定义行为的计数:
两者都违反了严格的别名规则:您不能以这种方式将变量的内存内容重新解释为不同的类型。您应该用于将表示的字节复制到变量的字节中,反之亦然。
int bi = *(int *)&bf;
float rbf = *(float *)&bi;
memcpy
float
int
传递转换具有未定义的行为:将值隐式转换为 a,因为传递到是一种可能与传递值完全不同的方式。行为是未定义的,任何事情都可能发生,包括随机输出。
bf
%X
float
bf
double
printf
int
传递转换并不完全正确:预期类型为 。这不是一个问题,但如果值为负数,则输出未定义。
bi
%X
unsigned int
int
传递转化具有未定义的行为,请参阅上文。
rbf
%X
传递 和 对于转换也是未定义的行为。您应该按照预期的 .这在大多数环境中应该不是问题,但建议使用强制转换。
&bf
&bi
&rbf
%p
(void *)
%p
void *
另请注意以下备注:
float bf = -12.342;
执行隐式转换。double
float
- 以透明方式传递值会将值转换为值并传递值。
float
printf
float
double
double
这是修改后的版本:
#include <stdio.h>
#include <string.h>
int main(void) {
float bf = -12.342f;
int bi;
float rbf;
memcpy(&bi, &bf, sizeof bi);
memcpy(&rbf, &bi, sizeof rbf);
printf(" bf: %p %a %.3f\n", (void *)&bf, bf, bf);
printf(" bi: %p %X\n", (void *)&bi, bi);
printf("rbf: %p %a %.3f\n", (void *)&rbf, rbf, rbf);
return 0;
}
输出:
bf: 0x30d148314 -0x1.8af1aap+3 -12.342
bi: 0x30d148318 C14578D5
rbf: 0x30d14831c -0x1.8af1aap+3 -12.342
和 的输出是相同的,这是人们期望的最小值,因为两个变量的字节在构造上是相同的,假设 .bf
rbf
sizeof(int) == sizeof(float)
但请注意,在上面的代码中还有一种可能的未定义情况,因为 C 标准允许这些类型具有填充位和陷阱值:如果 的表示恰好是陷阱值,则读取这样初始化的值可能具有未定义的行为。为此代码产生实际问题需要反常的实现,但在 DS9K 上技术上是可行的。int
bi
float
-12.342f
int
bi
评论
%X
unsigned int
double
void*
printf
int bi = *(int*) &bf;
float rbf = *(float*) &bi;
memcpy()