用 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 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/

【问题讨论】:

0x4048f5c3float 值的编码。这不是数字的价值。 0xc.8f5c3p-2 是数字的值,以十六进制表示。 另外请注意3.14double。如果您想要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

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

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

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

【讨论】:

以上是关于用 printf("%a", 3.14) 以十六进制表示浮点数的主要内容,如果未能解决你的问题,请参考以下文章

如何用c语言实现真值表

怎么用c 程序写verygood

printf函数用于啥?

chr a ; printf("%d",a); 和 int a; printf ("%d",a);有区别吗 或者交换下??

c语言中case后可以跟字符么,如case +:printf("...")

c语言中,使用scanf输入一句话,中间有空格,怎样用printf将这句话打印出来