用 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 00110x4.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

事实上,在我的计算机上,%a3.14f 打印为 0x1.91eb86p+1。但是你说你的打印了 0xc.8f5c3p-2 (@DarkAtom 也是如此)。但是正如我们刚刚看到的,这两种表示是等价的。

正如其他答案和评论所解释的那样,您认为您可能会看到的十六进制数 0x4048f5c3 与值没有直接关系;它是 IEEE-754 单精度原始 编码 的十六进制表示。隐藏在该编码中的是一个符号位 0,一个偏向指数 0x80,以及一个尾数,取决于您如何看待它,0x91eb860x48f5c3。但是现在我们可以很容易地看到它们是如何组合在一起的,因为有效数字与我们看到的十六进制模式相匹配,并且有偏差的指数值 0x80 计算出实际指数为 128 - 127 = 1。(我说编码的有效数字“取决于你如何看待它,0x91eb860x48f5c3”,但你可以相信我的话,它全部对应于 0x1.91eb86 × 2¹,其中前导 1 是隐含的。)

不过,我无法解释你提到的 0x4.8f5c3p-1 来自哪里。