在C++中以完整精度写入浮点数

8
在 C++ 中, 我可以以文本格式写入和读取浮点数(float 或 double)而不会丢失精度吗?
考虑以下内容:
float f = ...;
{
    std::ofstream fout("file.txt");
    // Set some flags on fout
    fout << f;
 }
 float f_read;
 {
     std::ifstream fin("file.txt");
     fin >> f;
  }
  if (f != f_read) {
      std::cout << "precision lost" << std::endl;
  }

我理解有时候会丢失精度。然而,如果我打印足够多的位数,我应该能够读回完全相同的值。

是否有一组标志可以保证不会丢失精度?这种行为是否跨平台可移植?


如果您想要精确地存储和加载,请将浮点数的二进制表示转换为base64并进行存储。 - Kerrek SB
然而,如果我打印足够多的数字,应该能够读回完全相同的值。是的,大约有17个有效十进制数字。相关C问题:Printf宽度说明符以维护浮点值的精度 - chux - Reinstate Monica
4个回答

6

6

如果您不需要支持不支持C99(MSVC)的平台,则最好使用%a格式说明符与printf一起使用,它始终生成数字的精确(十六进制)表示,并使用有限数量的位数。 如果您使用此方法,则在将数字转换为字符串或返回时不会进行舍入,因此舍入模式对结果没有影响。


如果文件中不需要人类阅读,则只需将数字打印为十六进制数字 printf("%8X %16X", 1.1f, 1.1);。这是最快的方法,无论是否支持 C99 都可以使用。 - phuclv
1
@LưuVĩnhPhúc:有两点需要注意:%a 是人类可读的,而你的建议会引发未定义的行为(特别是在大多数 64 位平台上不会按照你的期望工作;它只会打印任意垃圾位)。 - Stephen Canon
行为不是未定义的,因为printf只从堆栈中弹出8个字节,并根据格式化程序决定那是什么数据类型。如果使用%a,它将把这些字节视为double,%lld将把它们视为long long int,类似地,%llX将只将它们打印为十六进制数。我上面输入的代码有一些错误。正确的应该是printf("%08llX %016llX", 1.1f, 1.1);另一种使用方法是printf("%08X\n", *(int *)&a); - phuclv
这就是printf在一些平台上的工作方式。然而,并没有什么可以保证它。试试64位的osx或Linux,看看会发生什么... - Stephen Canon
@LưuVĩnhPhúc “只是从堆栈中弹出8个字节并决定那些数据” --> 预计FP值有时会在FP堆栈中传递,而整数则在通常的堆栈中传递。 printf("%8X %16X", 1.1f, 1.1);仍然是未定义的行为。 - chux - Reinstate Monica

5
如果我打印出足够多位数的值,我应该能够读取回完全相同的值。 不过如果你用十进制来写它,二进制位数和所需的十进制位数之间没有整数关系,因此你将丢失精度。如果你用二进制或十六进制打印数字,你就可以在不丢失任何精度的情况下读取回它。
一般来说,浮点数在不同平台之间是不可移植的,所以你的文本表示法不能填补这个差距。实际上,大多数机器使用IEEE 754浮点数,所以它可能会工作得相当好。

1
如果我有足够的小数位数,那么应该有一个最接近的单精度浮点数表示,对吧? - luispedro
@luis,不一定 - 你可能无法控制舍入行为,例如,如果您打印一个十进制数并进行四舍五入,然后在读取时进行了不同的四舍五入,那么您将被卡住。 - Carl Norum
这是我的问题:是否可以设置四舍五入,使其对称工作? - luispedro
@luis - 这取决于编译器和系统,你需要去阅读文档。它肯定是不可移植的。 - Carl Norum

3

在十进制中,不能精确打印“二的幂”浮点数的值。
想象一下使用三进制存储1/3,现在尝试在十进制中完美地打印1/3。

有关解决方案,请参见:如何打印浮点数的EXACT值?


1
这是错误的。因为10是2的倍数,每个浮点数在十进制中都有一个有限的表示(反之则不成立)。 - Stephen Canon
@Stephen - 你说得对,显然IEEE754要求舍入规则生成正确的浮点数,如果你使用恰好正确数量的小数位。但是如果你打印更多,它们就不会了! - Martin Beckett
我实际上是在纯数学意义上这么说的。这种情况与将1/3写成十进制形式完全不同,因为3不能被10整除。忽略IEEE754,每个二进制有理数都可以用有限数量的十进制数字来表示。 - Stephen Canon
@Stephen - 是的,那个例子不太好,我混淆了解释在一个进位制中不能用有限位表示分数的概念,但当然你可以在二进制和十进制中表示(除了格式中的舍入规则)。 - Martin Beckett

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接