当我在C中读取浮点数的十六进制表示法时,我会遇到一本来自Stephen Prata书的特殊数字“0xa.1fp10”。当我将此数字分配给float或double变量并使用printf
中的“%a”格式说明符打印时,结果为0x1.43e000p + 13,与原始值不匹配。但两者都是十进制的相同值10364。
到底是怎么回事?为什么产值会发生变化?如何将原始数字作为输出?
答案 0 :(得分:3)
很遗憾,您无法从0xa.1fp10
移植到相同的格式printf
。 C标准规定%a
的输出是这样的,对于非{0}的normal双,一个非零数字之前 .
以及在 .
后完全表示值的所需数字。实现可以选择第一个数字中有多少个进入第一个数字!
然而,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位可以完全整除为半字节,因此合理的实现将在<{>>正常数字的.
之前具有完整半字节,其值从{{1}开始变化} 0x8
(第一位始终为1),点后面最多15个半字节。
尝试使用
实施0xF
看它是否符合这个期望......
在正正数和零之间可能存在次正规数 - 我的Glibc用#include <stdio.h>
int main(void) {
for (long double i = 1; i < 32; i ++) {
printf("%La\n", i);
}
}
表示这些双精度值,后跟尾数的实际半字节,删除尾随零,以及固定指数0x0.
- 再次,表示是最简单的实现和最快计算。
答案 1 :(得分:1)
这是一种十六进制浮点格式。 0x
之后和p
之前的数字(和句点)是十六进制数字。那部分被称为有效数字。 p
后面的数字是十进制数字,表示乘以有效数字的2的幂。
在0xa.1fp10
中,有效数字为a.1f
。这表示数字10•16 0 + 1•16 -1 + 15•16 -2 ,等于10 + 31/256,或2591/256。
然后p10
说要乘以2 1024 ,所以结果为2591/256•1024 = 10,364。
结果只是一个数字。 0xa.1fp10
,10364
和0x1.43ep13
是三个不同的数字,代表相同的数字。将此值存储在float
或double
中时,该对象仅包含该数字。没有原始格式的记录。当您使用%a
打印时,实现会选择前导数字 1 。因为没有原始数字的记录,所以没有办法使printf
生成原始字符串,除非您有一些单独的信息记录并编写自己的软件来打印数字。
浮点格式通常使用二进制基,并且很难编写能够将十进制科学记数法正确转换为二进制浮点的优秀软件。 (这是已发表论文的一个已解决的问题,但并不总是使用好的软件。)使用十六进制格式而不是十进制格式可以很容易地在浮点数中准确指定作者想要的值,并且编译器很容易解释它。十六进制格式是为此目的而设计的:读取和写入浮点数的简便性和准确性。它不是为了促进审美问题而设计的,例如复制特定缩放或标准化。
1 当使用%a
时,C标准将其留给实现来选择使用的缩放,除了小数点字符前面只有一个数字,它是如果数字在浮点格式的正常范围内,并且该点后面的位数等于精度,则非零。
答案 2 :(得分:1)
但两者都是十进制的相同值10364。
确实
发生了什么事?为什么输出值会发生变化?
为什么不应该更改?内存中double
的表示不包含任何格式信息。正如您自己观察到的那样,输出表示与输入相同的数字,因此值不会更改。它的表现方式不同。
使用%e
指令也可以使用十进制数进行大致类似的行为。
如何将原始数字作为输出?
很有可能无法让您的特定printf()
实现发出程序从其输入中读取的特定表示。但是,如果有关于该表示的系统性,例如具有在小数点之前提供单个十六进制数字的最小指数,那么原则上您可以编写自己的输出函数来生成该表示。
在你添加的评论中,
但标准表示是什么?
C语言标准要求的表示意义上没有一个。该语言仅要求表示在小数点之前只有一个十六进制数字,并且如果数字被标准化并且本身非零,则它非零。这为大多数标准化浮点数留下了四种可能性。