C语言中的十六进制浮点数表示

t9aqgxwy  于 2024-01-06  发布在  其他
关注(0)|答案(3)|浏览(122)

当我在C中阅读十六进制浮点数时,我遇到了Stephen Prata书中的一个特殊数字“0xa.1fp10”。当我将这个数字分配给一个浮点数或双精度变量并在printf中使用“%a”格式说明符打印它时,那么结果是0x1.43e000p+13,这与原始值不匹配。但两者都是十进制的10364。这是怎么回事?为什么输出值发生了变化?我怎样才能得到原来的数字作为输出?

wf82jlnq

wf82jlnq1#

不幸的是,你不能从printf中移植出 * 相同 * 格式的0xa.1fp10。C标准规定%a的输出是这样的,对于一个非零的normaldouble,在.之前有 * 一个 * 非零数字 *,在.之后有尽可能多的数字来表示值。实现可以选择第一位中有多少位进入第一位!
然而,C11标准的脚注278说,
二进制实现可以选择小数点字符左侧的十六进制数字,以便后续数字与半字节(4位)边界对齐。
问题就在这里,由于IEEE 754 double有53位尾数;第一位是1,代表正常数;其余52位都可以被4整除,所以在那个脚注之后的实现(我的机器上的Glibc似乎是一个),将 * 总是 * 输出任何有限的非零浮点数,因此它以0x1.开头!
尝试这个最小的程序:

#include <stdio.h>

int main(void) {
    for (double i = 1; i < 1024 * 1024; i *= 2) {
        printf("%a %a %a\n", 1.0 * i, 0.7 * i, 0.67 * i);
    }
}

字符串
在我电脑上的输出是

0x1p+0 0x1.6666666666666p-1 0x1.570a3d70a3d71p-1
0x1p+1 0x1.6666666666666p+0 0x1.570a3d70a3d71p+0
0x1p+2 0x1.6666666666666p+1 0x1.570a3d70a3d71p+1
0x1p+3 0x1.6666666666666p+2 0x1.570a3d70a3d71p+2
0x1p+4 0x1.6666666666666p+3 0x1.570a3d70a3d71p+3
0x1p+5 0x1.6666666666666p+4 0x1.570a3d70a3d71p+4
0x1p+6 0x1.6666666666666p+5 0x1.570a3d70a3d71p+5
0x1p+7 0x1.6666666666666p+6 0x1.570a3d70a3d71p+6
0x1p+8 0x1.6666666666666p+7 0x1.570a3d70a3d71p+7
0x1p+9 0x1.6666666666666p+8 0x1.570a3d70a3d71p+8
0x1p+10 0x1.6666666666666p+9 0x1.570a3d70a3d71p+9
0x1p+11 0x1.6666666666666p+10 0x1.570a3d70a3d71p+10
0x1p+12 0x1.6666666666666p+11 0x1.570a3d70a3d71p+11
0x1p+13 0x1.6666666666666p+12 0x1.570a3d70a3d71p+12
0x1p+14 0x1.6666666666666p+13 0x1.570a3d70a3d71p+13
0x1p+15 0x1.6666666666666p+14 0x1.570a3d70a3d71p+14
0x1p+16 0x1.6666666666666p+15 0x1.570a3d70a3d71p+15
0x1p+17 0x1.6666666666666p+16 0x1.570a3d70a3d71p+16
0x1p+18 0x1.6666666666666p+17 0x1.570a3d70a3d71p+17
0x1p+19 0x1.6666666666666p+18 0x1.570a3d70a3d71p+18


这个输出是 * 高效的 * -对于每个正常数字,代码只需要输出0x1.,然后是转换为十六进制的尾数的所有实际半字节,去掉尾随的0字符,并附加p+,然后是指数。
对于长双精度数,x86格式有64位尾数。由于64位可以精确地划分为四位字节,因此合理的实现将在.之前有一个 full 四位字节,用于正常数字,其值从0x80xF变化(第一位始终为1),最多15个四位字节跟随该点。
使用以下工具尝试您的实现

#include <stdio.h>
int main(void) {
    for (long double i = 1; i < 32; i ++) {
        printf("%La\n", i);
    }
}


看看它是否符合这个预期
在正正规数和零之间可以有次正规数-我的Glibc用0x0.表示这些双精度值,后跟尾数的实际半字节,去掉尾随的零,固定指数-1022-同样,这种表示法是最容易实现和计算最快的。

uqjltbpv

uqjltbpv2#

但两者都是相同的十进制值10364。
的确.
这是怎么回事?为什么产值发生了变化?
为什么 * 不应该 * 改变呢?内存中double的表示形式并不携带任何格式信息。正如您自己所观察到的,输出表示的数字与输入表示的数字相同,因此值 * 没有 * 改变。它只是表示形式不同而已。
使用%e指令,十进制数也可以发生大致类似的行为。
如何获得原始编号作为输出?
很有可能你无法让你的printf()实现发出程序从它的输入中读取的特定表示,但是,如果这个表示有一些系统性的东西,比如在基数点之前有一个十六进制数字的最小指数,那么,原则上,你可以编写你自己的输出函数来产生这个表示。
在您添加的备注中,
但什么是标准表示法呢?
C语言标准中没有一种表示形式,它只要求表示形式在基数点之前必须有一个十六进制数,并且如果该数被规范化且本身不为零,则表示形式必须是非零的。

mmvthczy

mmvthczy3#

这是一种十六进制浮点格式。0x之后和p之前的数字(和句点)是十六进制数字。这部分称为有效数。p之后的数字是十进制数字,表示与有效数相乘的2的幂。
0xa.1fp10中,有效位数是a.1f。这表示数字10·160 + 1·16−1 + 15·16−2,等于10 + 31/256,或2591/256。
然后p10表示将其乘以210 = 1024,因此结果为2591/256 · 1024 = 10,364。
结果只是一个数字。0xa.1fp10103640x1.43ep13是代表同一个数字的三个不同数字。当您将此值存储在floatdouble中时,对象仅包含数字。没有其原始格式的记录。当您使用%a打印时,由于没有原始数字的记录,所以没有办法使printf产生原始字符串,除非您有一些单独的信息记录并编写自己的软件来打印数字。
浮点格式通常使用二进制基数,并且很难编写出将十进制科学计数法正确转换为二进制浮点数的好软件。(这是一个已发表论文的解决问题,但是并不总是使用好的软件。)使用十六进制格式而不是十进制格式可以很容易地精确指定作者想要的浮点值-十六进制格式是为了方便阅读和写入浮点数而设计的。它不是为了方便美学考虑,如再现特定的缩放或标准化。

脚注

1当使用%a时,C标准将其留给实现来选择所使用的缩放比例,除了小数点字符之前正好有一个数字,如果该数字在浮点格式的正常范围内,则它是非零,并且点之后的位数等于格式精度。

相关问题