用 printf("%a", 3.14) 表示十六进制浮点数
Representing float point in hex with printf("%a", 3.14)
像这样的简单代码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/
您使用的网站正在显示浮点数的二进制表示。例如,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 种表示形式之间的差异(请注意,由于从 uint32_t*
到 float*
转换之后是取消引用,此程序在技术上会导致 C 中的未定义行为,但在这种情况下行为是使用二进制表示来创建一个浮点数,正如人们所期望的那样):
#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
Why did the C compiler show exponent as -2 instead of -1?
这里的问题是浮点表示法——无论是使用二进制、十进制还是十六进制——往往不是唯一的。查看您以 10 为基数的数字,其科学记数法表示可能是
3.14 × 100
或
0.314 × 101
甚至可能
31.4 × 10-1
或
0.0314 × 102
为了解决唯一性问题,我们通常会定义一个“规范化形式”——但可以有多种方式来进行该定义!例如,我们可以说小数点左边应该总是恰好一位数 (3.14 × 100) ― 或者我们可以说左边应该有 0小数点,但紧靠右边的非零数字 (0.314 × 101)。还有其他规范化规则的选择。
然后当涉及到 printf %a
时,它变得更加混乱,因为有效数字是十六进制,但指数是 two 的幂。因此,即使我们说我们想要小数点左边的“一个数字”,对于该数字可能有四种不同的选择,因为我们可以有效地将小数点放在任何 之间十六进制数字的位!
我们可以用您的 3.14 示例来说明这一点。在二进制中,四舍五入到24位有效位(也就是IEEE单精度,a.k.a.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 也是如此)。但是正如我们刚刚看到的,这两种表示是等价的。
正如其他答案和评论所解释的那样,您认为您可能会看到的十六进制数 0x4048f5c3
与值没有直接关系;它是 IEEE-754 单精度原始 编码 的十六进制表示。隐藏在该编码中的是一个符号位 0,一个偏向指数 0x80
,以及一个尾数,取决于您如何看待它,0x91eb86
或 0x48f5c3
。但是现在我们可以很容易地看到它们是如何组合在一起的,因为有效数字与我们看到的十六进制模式相匹配,并且有偏差的指数值 0x80
计算出实际指数为 128 - 127 = 1。(我说编码的有效数字“取决于你如何看待它,0x91eb86
或 0x48f5c3
”,但你可以相信我的话,它全部对应于 0x1.91eb86 × 2¹
,其中前导 1 是隐含的。)
不过,我无法解释你提到的 0x4.8f5c3p-1
来自哪里。
像这样的简单代码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/
您使用的网站正在显示浮点数的二进制表示。例如,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 种表示形式之间的差异(请注意,由于从 uint32_t*
到 float*
转换之后是取消引用,此程序在技术上会导致 C 中的未定义行为,但在这种情况下行为是使用二进制表示来创建一个浮点数,正如人们所期望的那样):
#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
Why did the C compiler show exponent as -2 instead of -1?
这里的问题是浮点表示法——无论是使用二进制、十进制还是十六进制——往往不是唯一的。查看您以 10 为基数的数字,其科学记数法表示可能是
3.14 × 100
或
0.314 × 101
甚至可能
31.4 × 10-1
或
0.0314 × 102
为了解决唯一性问题,我们通常会定义一个“规范化形式”——但可以有多种方式来进行该定义!例如,我们可以说小数点左边应该总是恰好一位数 (3.14 × 100) ― 或者我们可以说左边应该有 0小数点,但紧靠右边的非零数字 (0.314 × 101)。还有其他规范化规则的选择。
然后当涉及到 printf %a
时,它变得更加混乱,因为有效数字是十六进制,但指数是 two 的幂。因此,即使我们说我们想要小数点左边的“一个数字”,对于该数字可能有四种不同的选择,因为我们可以有效地将小数点放在任何 之间十六进制数字的位!
我们可以用您的 3.14 示例来说明这一点。在二进制中,四舍五入到24位有效位(也就是IEEE单精度,a.k.a.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 也是如此)。但是正如我们刚刚看到的,这两种表示是等价的。
正如其他答案和评论所解释的那样,您认为您可能会看到的十六进制数 0x4048f5c3
与值没有直接关系;它是 IEEE-754 单精度原始 编码 的十六进制表示。隐藏在该编码中的是一个符号位 0,一个偏向指数 0x80
,以及一个尾数,取决于您如何看待它,0x91eb86
或 0x48f5c3
。但是现在我们可以很容易地看到它们是如何组合在一起的,因为有效数字与我们看到的十六进制模式相匹配,并且有偏差的指数值 0x80
计算出实际指数为 128 - 127 = 1。(我说编码的有效数字“取决于你如何看待它,0x91eb86
或 0x48f5c3
”,但你可以相信我的话,它全部对应于 0x1.91eb86 × 2¹
,其中前导 1 是隐含的。)
不过,我无法解释你提到的 0x4.8f5c3p-1
来自哪里。