C printf %a 和 %La

C printf %a and %La

我必须使用 C 重新实现 printf(3),而不使用任何可以为我进行转换的函数。

我以为我理解了谢谢大家 %a 是如何工作的:

然后我意识到我不明白rounding是如何完成的所以我问:然后以为在你帮助我之后我完成了。

现在 %a 完全像官方的一样工作,我认为 %La 会和 %a 完全一样,但是 long double 因为那个人只说:

Modifier a, A, e, E, f, F, g, G

l (ell) double (ignored, same behavior as without it)
L long double

我发现它输出的东西完全不同:'(

double a_double = 0.0001;
long double a_long_double = 0.0001;
printf("%a\n", a_double); #=> 0x1.a36e2eb1c432dp-14
printf("%La\n", a_long_double); #=> 0xd.1b71758e21968p-17

%a 结果总是以 1. 开头,现在我完全不明白 %La 在做什么。

你能帮我理解将 0.0001 转换为 0xd.1b71758e21968p-17 过程 吗?

编辑:我真正不明白的部分是为什么 %a总是输出以1.而不是[=16=开头的东西] ?

EDIT2:更准确地说:为什么 %a 选择输出 1. ...p-14%La 选择输出 d. ...p-17 ?

不清楚结果的哪一方面让您感到惊讶。如果是指数和数字的差异,那只是因为归一化不佳。请注意,对于第一个结果,您在小数点之前得到一个 1,而对于第二个结果,您在它之前得到一个 d。将 d 右移 3 位,得到 1,低位 (101) 移到小数点后的下一个位置,如预期的那样得到 a

至于0.0001如何转化为0xd.1b71758e21968p-17,无非是找到最接近十进制数0.0001的二进制浮点数。如何有效完成这项工作的机制有点复杂,但概念很简单。

任何(有限的,非零的)浮点数都有四个不同的(有效的)十六进制浮点表示:

  1. 最大的十六进制数在8和F之间
  2. 另一个最大的十六进制数在4到7之间
  3. 另一个最高十六进制数是2或3
  4. 另一个最高十六进制数是1

并且它们具有连续递增的指数值。您可以使用右移从一个传递到另一个;这个过程可能会向右增加另一个数字; C 标准中没有关于应该使用哪种表示形式进行 %a 转换的限制。

使用 IEEE float(24 位有效数)或通常的双扩展(64 位有效数,与 i386 Linux 上的 long double 一样),因为位数可以被整除4、习惯上对归一化数使用第一种形式,因为它是用最少的十六进制数来完整表示数的形式(分别是十六进制后的5和15位)点.).

对于 IEEE double(53 位有效数),另一方面,在点 . 之后使用第四种形式和 13 个十六进制数字看起来更好:因此所有显示的数字实际上代表数据。 IEEE quad(正式为 binary128,113 位有效数)也会发生同样的情况,点后有 28 个十六进制数字。

这些表示也有很好的 属性 来匹配数字的内存表示(这就是为什么 C99 标准中的脚注指导实现这样做的原因。)

如果我们现在查看您的问题,第一个示例 (double) 符合上述准则,第一个数字是 1,点后是 13 个十六进制数字。

第二个例子比较狡猾。首先,一个 double 常量被存储到一个 long double 变量中:根据编译器的不同,常量可能会四舍五入到 double 的精度(就像这里所做的那样。)这意味着第 53 位之后的位将全为零。然后使用%La转换,同样按照上面的指导方针,因此选择第一种表示,以D开头(二进制1101xxx,与转换为11010xxx的1.A进行比较);这里也只有 13 个十六进制数字,因为编译器丢弃了打印不必要的零(由于四舍五入。)

请注意,您不能将 printf%a 转换用于 float

你没有说你在这里工作的机器是什么类型,但从你得到 0xd.... 结果 %La 的事实来看,这意味着这是一台机器long double 是一种 非规范化 浮点类型(例如 8087 上的 80 位浮点数)。使用 标准化 浮点数,您将始终得到以 0x1.... ,这就是您在这里看到的。

%a 转换规范的全部要点是允许将二进制浮点数打印为文本,以便稍后可以使用 scanf 读回,这将导致位相同的值在具有相同浮点表示的机器上。