C/ c++中浮点常量的紧凑无损表示

Compact lossless representation of floating point constants in C/C++

本文关键字:表示 c++ 浮点常量      更新时间:2023-10-16

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

为了生成常量,我现在使用:
double v = ...
cfile << std::scientific << std::setprecision(std::numeric_limits<double>::digits10 + 1) << v;

我很确定这是一个无损表示,但它也非常臃肿。例如,0和1可以表示为0.0000000000000000e+00和1.00000000000000e +00。而"0."或"1."所承载的信息量也是一样的。

是否有一种方法可以更紧凑地打印常量到文件中,但仍然是无损的方式?对于人类读者来说,它不需要看起来很好,只需在纯C代码中进行编译(如果是C99,我更希望它也是有效的c++)。十六进制是可以的,如果它是便携式的。

编辑:删除代码片段中的std::fixed

可以使用十六进制浮点数(C中的printf()的格式说明符%a);它被定义为保持所有位的精度(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("%an", v);
}

生成的源文件在c++ 11中是无效的,因为c++ 11不支持十六进制浮点量。然而,许多c++ 11编译器支持C99十六进制浮点量作为扩展。

这不是表示、语言或标准库的问题,而是算法的问题。如果你有一个代码生成器,那么…为什么不修改生成的代码,使其成为最好的(=最短的精度)表示形式呢?当你手工写代码时,你就会这么做。

在假设的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;
}

首先,当你第一次说std::scientific,然后是std::fixed。其次,你可能两者都不想要。默认格式为设计得最好。默认格式不需要有一个名字,没有一个操纵者,但如果没有其他,你得到的是什么格式已指定,并可设置(以防其他代码设置了不同的格式)使用:

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

我建议使用这个。(你仍然需要精确的课程。)

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

普通的ofstream::operator<<可以根据需要打印出尽可能多的数字,因此没有必要使问题复杂化。