用 printf("%a", 3.14) 以十六进制表示浮点数
Posted
技术标签:
【中文标题】用 printf("%a", 3.14) 以十六进制表示浮点数【英文标题】:Representing float point in hex with printf("%a", 3.14) 【发布时间】:2022-01-05 13:08:38 【问题描述】:像printf("hex representation for %f is [%a]\n", 3.14, 3.14);
这样的简单代码,我希望结果是0x4048f5c3
(完整的二进制版本是:0100 0000 0100 1000 1111 0101 1100 0011
,0x4.8f5c3p-1
)根据下面的参考网站,但编译的可执行文件显示[0xc.8f5c3p-2]
(二进制:1100.1000 1111 0101 1100 0011
),为什么 C 编译器将指数显示为 -2 而不是 -1?
编译器设置为:
"command": "c:/msys64/mingw64/bin/gcc.exe",
"args": [
"-g",
"$file",
"-o",
"$fileDirname\\$fileBasenameNoExtension.exe",
"-Wall",
"-Werror",
"-std=c11"
参考: https://users.cs.fiu.edu/~downeyt/cop2400/float.htm https://gregstoll.com/~gregstoll/floattohex/
【问题讨论】:
0x4048f5c3
是float
值的编码。这不是数字的价值。 0xc.8f5c3p-2
是数字的值,以十六进制表示。
另外请注意3.14
是double
。如果您想要float
,请使用3.14f
。
这是我很困惑的地方,我按照参考链接将3.14
转换为十六进制,结果出来是404.8f5c3p-1
,怎么是电脑上的0xc.8f5c3p-2
?
【参考方案1】:
您正在使用的网站正在显示浮点数的二进制表示。例如,3.14
在 big-endian 中是 40 48 F5 C3
,在 little-endian 中是 C3 F5 48 40
。
C 中的十六进制表示是以 16 为基数的实际浮点数,而不是二进制表示。 0xc.8f5c3p-2
表示 c.8f5c3 * 2^(-2)
。如果我们使用通常的转换算法将其转换为十进制,我们会得到3.14
:
12(C) * 16^0 + 8 * 16^(-1) + 15(F) * 16^(-2) + 5 * 16^(-3) + 12(C) * 16^(-4) + 3 * 16^(-5) = 12 + 0.5 + 0.05859 + ... = 12.56
(带近似值)
现在乘以2^(-2)
,或除以4
,我们得到3.14
。
此处显示了 2 种表示之间的差异(请注意,该程序在技术上会导致 C 中未定义的行为,因为 uint32_t*
到 float*
转换后跟取消引用,但在这种情况下,行为是使用二进制表示来创建一个浮点数,正如人们所期望的那样):
#include <stdio.h>
#include <inttypes.h>
// Assuming IEEE 754 representation of 32-bit floats
int main(void)
float x = 0xC.8F5C3p-2;
uint32_t y = 0x4048F5C3;
printf("Base-16 representation of %f is: %A\n", x, x);
printf("Binary representation of %f is: 0X%"PRIX32"\n", *(float*)&y, y);
这是我在电脑上得到的输出:
Base-16 representation of 3.140000 is: 0XC.8F5C3P-2
Binary representation of 3.140000 is: 0X4048F5C3
【讨论】:
【参考方案2】:为什么 C 编译器将指数显示为 -2 而不是 -1?
这里的问题是浮点表示——无论是使用二进制、十进制还是十六进制——往往不是唯一的。查看以 10 为底的数字,它的科学记数法表示可能是
3.14 × 100
或
0.314 × 101
甚至可能
31.4 × 10-1
或
0.0314 × 102
为了解决唯一性问题,我们通常定义一个“规范化形式”——但可以有多种方法来定义!例如,我们可以说小数点左边应该总是有一个数字 (3.14 × 100) ——或者我们可以说小数点左边应该有 0指向右侧的非零数字 (0.314 × 101)。规范化规则还有其他选择。
然后当涉及到printf %a
时,它变得更加混乱,因为有效数字是十六进制但指数是 2 的幂。所以即使我们说我们想要小数点左边的“一个数字”,我们也有四种不同的选择来选择那个数字,因为我们可以有效地将小数点放在任何位之间 十六进制数字!
我们可以用你 3.14 的例子来说明这一点。在二进制中,四舍五入到 24 个有效位(这是 IEEE 单精度,又名float
),它是
0b1.10010001111010111000011 × 21
如果我们直接将其转换为十六进制,我们得到
0x1.91eb86 × 21
但我们可以将该有效位向左移动 1、2 或 3 位,而小数点左侧仍然只有一个十六进制数字:
0x3.23d70c × 20 0x6.47ae18 × 2-1 0xc.8f5c30 × 2-2
事实上,在我的电脑上,%a
将3.14f
打印为0x1.91eb86p+1
。但是你说你的打印了0xc.8f5c3p-2
(@DarkAtom's 也是如此)。但正如我们刚刚看到的,这两种表示是等价的。
正如其他答案和 cmets 所解释的,您认为您可能会看到的十六进制数 0x4048f5c3
与值没有直接关系;它是 IEEE-754 单精度原始编码的十六进制表示。隐藏在该编码中的是符号位 0、有偏指数 0x80
和有效位,具体取决于您如何看待它,0x91eb86
或 0x48f5c3
。但是现在我们可以很容易地看到它们是如何组合在一起的,因为有效数字与我们看到的十六进制模式匹配,并且有偏指数值0x80
的实际指数为 128 - 127 = 1。(我说过编码的有效数字是“取决于你如何看待它,0x91eb86
或 0x48f5c3
”,但你可以相信我的话,这一切都对应于 0x1.91eb86 × 2¹
,其中前导 1 是隐含的。)
不过,我无法解释你提到的 0x4.8f5c3p-1
来自哪里。
【讨论】:
以上是关于用 printf("%a", 3.14) 以十六进制表示浮点数的主要内容,如果未能解决你的问题,请参考以下文章
chr a ; printf("%d",a); 和 int a; printf ("%d",a);有区别吗 或者交换下??