【问题标题】:Compact lossless representation of floating point constants in C/C++C/C++ 中浮点常量的紧凑无损表示
【发布时间】:2013-03-01 11:08:22
【问题描述】:

我有一个用 C++ 编写的程序,它生成用于数学计算的 C 源代码。我注意到常量在生成的代码中占用了很多空间,我正在寻找更紧凑的表示。

为了生成常量,我现在使用:

double v = ...
cfile << std::scientific << std::setprecision(std::numeric_limits<double>::digits10 + 1) << v;

我很确定这是一种无损表示,但它也非常臃肿。例如,零和一将表示为 0.00000000000000000e+00 和 1.0000000000000000e+00。和“0”。或“1”。携带同样多的信息。

有没有办法以更紧凑但仍然无损的方式将常量打印到文件中?对于人类读者来说,它不需要看起来很好,只需在以纯 C 代码出现时进行编译(如果是 C99,我希望它也是有效的 C++)。如果它是可移植的,十六进制可能没问题。

编辑:删除了代码 sn-p 中的 std::fixed

【问题讨论】:

标签: c++ c hex code-generation lossless


【解决方案1】:

可以使用十六进制浮点数(The format specifier %a for printf() in C);它被定义为保留所有位精度(C11、7.21.6.1p8、a,A 说明符)。

cfile << std::hexfloat << v;

如果您的编译器/标准库不支持hexfloat,您可以使用C99 %a printf 说明符(这是等效的,如C++11 表88 中的第22.4.2.2.2 节中所述):

printf("%a", v);

例如,以下程序是有效的 C99:

#include <stdio.h>
int main() {
   double v = 0x1.8p+1;
   printf("%a\n", v);
}

您生成的源文件将不是有效的 C++11,因为相当荒谬的是 C++11 不支持十六进制浮点文字。然而,许多 C++11 编译器支持 C99 十六进制浮点文字作为扩展。

【讨论】:

  • 这是代码生成器的例程,它可能会检查要写入的值以确定其最佳(=shortes)表示!
  • @ecatmur 感谢这个指针,特别是指出它不是有效的 C++11。因此,我可能会避免使用这种表示法,因为我的生成器必须使用 C++11(最好是 C++03,我的一些用户拥有)进行编译。
【解决方案2】:

这不是表示、语言或标准库的问题,而是算法的问题。如果您有代码生成器,那么...您为什么不更改生成的代码 使其成为最好的(= 要求精度最短的)表示?这就是你手工编写代码时所做的事情。

在假设的put_constant(double value) 例程中,您可以检查您必须编写的值是什么

  • 它是一个整数吗?不要用std::fixedset_precision 使代码膨胀,只需转换为整数并添加一个点即可。
  • 尝试使用默认设置将其转换为字符串,然后将其转换回double,如果没有任何更改,则默认(短)表示就足够了。
  • 使用您的实际实现将其转换为字符串,并检查其长度。如果超过 N(见后文),则使用另一种表示,否则只需编写它。

当浮点数有很多位数时,一种可能的(短)表示是使用它们的内存表示。有了这个,你有一个相当固定的开销,长度永远不会改变,所以你应该只将它应用于非常长的数字。一个简单的例子来展示它是如何工作的:

#define USE_L2D __int64 ___tmp = 0;
#define L2D(x) (double&)(___tmp=x)

int main(int argc, char* argv[])
{
    // 2.2 = in memory it is 0x400199999999999A

    USE_L2D
    double f1 = L2D(0x400199999999999A);
    double f2 = 123456.1234567891234567;

    return 0;
}

【讨论】:

  • 我接受了这个作为我的答案。按照您的建议,测试不同的打印输出并选择最好的打印输出可能是最好的方法。谢谢!
【解决方案3】:

首先,当你第一次说的时候,你是在自相矛盾 std::scientific,然后是 std::fixed。其次,你 可能也不想要。默认格式一般为 旨在做到最好。默认格式不 有名字,也没有操纵者,但如果没有其他人,你会得到什么 格式已经指定,可以设置(以防其他代码 已设置不同的格式)使用:

cfile.setf( std::ios_base::fmtflags(), std::ios_base::floatfield );

我建议使用它。 (你仍然需要精度, 当然。)

【讨论】:

  • 他同时使用 std::fixed 和 std::scientific 以十六进制表示法打印,以保持转换为字符串的最大精度。
  • @Adriano 这不是它的作用。最后一个优先(除非他的实现被严重破坏)。
  • 你说得对,我看到了它们的组合,但它只是在我(疲倦的)脑海中!
  • @Adriano 虽然 ios_base::fixed | ios_base::scientific 是 C++11 识别 hexfloat 的方式。
  • @ecatmur 但这是不同的上下文。这就是您调用std::ios_base::setf 时使用的内容(尽管据我所知,这仅记录在标准中std::hexfloat 的描述中)。
【解决方案4】:

我不确定你能否像这样无损地传递浮点数。浮点数必然是有损的。虽然它们可以精确地表示值的子集,但您不能包含所有有效数字 - 不同的硬件可能有不同的表示,因此您不能保证不会丢失信息。即使您可以将其全部传递,因为接收硬件可能无法表示该值。

普通的 ofstream::operator

【讨论】:

  • 我不认为最后的说法是正确的。默认精度不会打印内部表示的尽可能多的数字。
  • 如果读取器和写入器都使用相同的浮点基数,并且在该基数中具有相同数量的有效数字,则可以确保使用十进制精确传输,前提是使用足够的小数位数精度. (对于 IEEE,17 位就足够了。)
  • “虽然它们可以精确地表示值的子集,但您不能包含所有有效数字”是的,您可以。为什么你不能?并且您不需要传递所有有效数字,只需使其明确表示哪个浮点数即可。 “不同的硬件可能有不同的表示,所以你不能保证不会丢失信息”这就是 IEEE 754 标准在 1985 年发布的原因:这样我们就可以在所有计算机上使用相同的表示。您提到的问题已在 20 多年前发布该标准得到解决。
  • 不,浮点值不是“必然有损”。每个浮点值都有一个明确定义的内部表示,想要用尽可能短的外部表示写出该值并读回并获得相同的内部表示,这并不是不合理的。这样做的技术是在 70 年代开发的。本质上,你一直在写数字,直到数字表示的值更接近内部值而不是它的任何一个直接邻居。不幸的是,在某些情况下,这需要无限的整数类型。
猜你喜欢
  • 2020-05-04
  • 1970-01-01
  • 2014-02-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-05
  • 2011-02-23
  • 2015-03-26
相关资源
最近更新 更多