【问题标题】:Hex float representation in cc中的十六进制浮点表示
【发布时间】:2018-11-13 04:36:09
【问题描述】:

当我在 C 中读取浮点数的十六进制表示法时,我遇到了 Stephen Prata 书中的一个特殊数字“0xa.1fp10”。当我将此数字分配给 float 或 double 变量并使用 printf 中的“%a”格式说明符打印它时,结果是 0x1.43e000p+13 与原始变量不匹配。但两者都是十进制的相同值 10364。 到底是怎么回事?为什么输出值发生了变化?如何获取原始数字作为输出?

【问题讨论】:

  • 如您所见,同一个值有多种可能的表示形式。 C 保留值,而不是表示。
  • 但是标准表示是什么?以及如何获得原始代表?
  • 就像(十进制格式)17.3e12 和 1.73e13 是同一个数字,但在你的情况下,区别在于十六进制,所以不那么明显。
  • 我也无法在 Windows 10 系统上使用 %a 转换说明符以 p 表示法输入浮点数。有什么问题?

标签: c floating-point hex


【解决方案1】:

不幸的是,您无法从printf 中移植相同 格式0xa.1fp10。 C 标准规定%a 的输出是这样的:对于非零的normal 双精度,在@ 之前一个 非零数字之前 987654326@ 以及在. 之后 精确表示值所需的位数。实现可以选择有多少第一位进入第一位!

但是 C11 标准有脚注 278 说明

二进制实现可以选择小数点字符左侧的十六进制数字,以便后续数字与半字节(4 位)边界对齐。

这就是问题所在。由于 IEEE 754 doubles 有 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. 后跟尾数的所有实际半字节转换为十六进制,strip尾随 0 字符并附加 p+ 后跟指数。


对于长双精度,x86 格式具有 64 位尾数。由于 64 位可以精确地分割成半字节,一个合理的实现将在 normal 数字的 . 之前有一个 full 半字节,其值从 0x8 到 @987654337 不等@(第一位始终为 1),该点后面最多 15 个半字节。

尝试你的实现

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

看看是否符合这个预期……


在正正数和零之间可能存在次正数 - 我的 Glibc 用 0x0. 表示这些双精度值,后跟尾数的实际半字节,去除尾随零,以及固定指数 -1022 - 再次,表示是最容易实现且计算速度最快的表示。

【讨论】:

  • 我相信星号是无意的:i &lt; 1024* ;
  • @user694733 故意但错过了其他 1024 那里,idk 去哪里:D
【解决方案2】:

这是一种十六进制浮点格式。 0x 之后和p 之前的数字(和句点)是十六进制数字。那部分称为有效数字。 p 后面的数字是十进制数字,表示有效数乘以 2 的幂。

0xa.1fp10 中,有效数字是a.1f。这表示数字 10•160 + 1•16-1 + 15•16-2,等于 10 + 31/256,或 2591/256。

然后p10 表示将其乘以 21024,因此结果为 2591/256 • 1024 = 10,364。

结果只是一个数字。 0xa.1fp10103640x1.43ep13 是三个不同的数字,代表同一个数字。当您将此值存储在floatdouble 中时,该对象仅包含数字。没有其原始格式的记录。当您使用%a 打印它时,实现会选择前导数字1。因为没有原始数字的记录,所以没有办法让printf产生原始字符串,除非你有这个信息的一些单独的记录并编写你自己的软件来打印数字。

浮点格式通常使用二进制基数,很难编写将十进制科学计数法正确转换为二进制浮点数的好软件。 (这是已发表论文解决的问题,但并不总是使用好的软件。)使用十六进制格式而不是十进制格式可以很容易地准确指定浮点数中作者想要的值,并且编译器也很容易解释它。十六进制格式就是为此目的而设计的:读写浮点数的简便性和准确性。它并非旨在促进美学问题,例如再现特定的缩放或标准化。

脚注

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

【讨论】:

    【解决方案3】:

    但两者都是十进制的相同值 10364。

    确实。

    发生了什么事?为什么输出值变了?

    为什么不应该改变? double 在内存中的表示不携带任何格式信息。正如您自己观察到的,输出代表的数字与输入相同,因此值 没有 改变。只是表示方式不同。

    使用%e 指令也可以对十进制数进行大致类似的行为。

    我怎样才能得到原始数字作为输出?

    很有可能您无法让您的特定printf() 实现发出程序从其输入中读取的特定表示。但是,如果该表示具有系统性,例如具有在小数点之前提供单个十六进制数字的最小指数,那么原则上您可以编写自己的输出函数来生成该表示。

    在你添加的 cmets 中,

    但标准表示是什么?

    没有 C 语言标准所要求的表示形式。该语言只要求表示形式在小数点之前恰好有一个十六进制数字,并且如果数字被规范化并且它本身非零,则它是非零的。这为大多数标准化浮点数留下了四种可能性。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-04-06
      • 2021-05-04
      • 1970-01-01
      • 2014-11-18
      • 2011-07-25
      • 2011-05-31
      相关资源
      最近更新 更多