首页 > 解决方案 > 在 C++ 中将 double 保存为二进制文件的问题

问题描述

在我的粒子系统模拟代码中,我为粒子定义了一个类,每个粒子都有一个pos包含其位置的属性,这是double pos[3];因为每个粒子有 3 个坐标分量。因此,对于由定义的粒子对象particles = new Particle[npart];(因为我们有npart很多粒子),那么例如第二个粒子的 y 分量将被访问double dummycomp = particles[1].pos[1];

为了在使用二进制文件之前将粒子保存到文件中,我将使用(保存为 txt,浮点精度为10,每行一个粒子):

#include <iostream>
#include <fstream>

ofstream outfile("testConfig.txt", ios::out);
outfile.precision(10);

  for (int i=0; i<npart; i++){
    outfile << particle[i].pos[0] << " " << particle[i].pos[1]  << " " << particle[i].pos[2] << endl;
}
outfile.close();

但是现在,为了节省空间,我正在尝试将配置保存为二进制文件,我的尝试是从这里得到启发的,如下所示:

ofstream outfile("test.bin", ios::binary | ios::out);

for (int i=0; i<npart; i++){ 
outfile.write(reinterpret_cast<const char*>(particle[i].pos),streamsize(3*sizeof(double))); 
}
outfile.close();

但是我在尝试运行它时遇到了分段错误。我的问题是:


旧保存方法(非二进制)的工作示例:

#include <iostream>
#include <fstream>

using namespace std;
class Particle {

 public:

  double pos[3];

};


int main() {

  int npart = 2;
  Particle particles[npart];
  //initilizing the positions:
  particles[0].pos[0] = -74.04119568;
  particles[0].pos[1] = -44.33692582;
  particles[0].pos[2] = 17.36278231;

  particles[1].pos[0] = 48.16310086;
  particles[1].pos[1] = -65.02325252;
  particles[1].pos[2] = -37.2053818;

  ofstream outfile("testConfig.txt", ios::out);
  outfile.precision(10);

    for (int i=0; i<npart; i++){
      outfile << particles[i].pos[0] << " " << particles[i].pos[1]  << " " << particles[i].pos[2] << endl;
  }
  outfile.close();

    return 0;
}

为了将粒子位置保存为二进制,将上述样本的保存部分替换为

  ofstream outfile("test.bin", ios::binary | ios::out);

  for (int i=0; i<npart; i++){
  outfile.write(reinterpret_cast<const char*>(particles[i].pos),streamsize(3*sizeof(double))); 
  }
  outfile.close();

第二个附录:在 Python 中读取二进制文件

我设法使用 numpy 在 python 中读取保存的二进制文件,如下所示:

data = np.fromfile('test.bin', dtype=np.float64)
data
array([-74.04119568, -44.33692582,  17.36278231,  48.16310086,
       -65.02325252, -37.2053818 ])

但是考虑到评论中关于二进制格式的不可移植性的疑虑,我不相信这种在 Python 中的阅读方式会一直有效!如果有人能阐明这种方法的可靠性,那就太好了。

标签: c++fstreambinaryfiles

解决方案


问题是 ascii 中 double 的 base 10 表示存在缺陷,并且不能保证给您正确的结果(特别是如果您只使用 10 位数字)。即使您使用所有std::numeric_limits<max_digits10>数字,也可能会丢失信息,因为该数字可能无法以 10 为基数精确表示。

您遇到的另一个问题是 double 的二进制表示不是标准化的,因此使用它非常脆弱并且很容易导致代码破坏。简单地更改编译器或编译器位置可能会导致不同的双重格式和更改架构,您绝对无法保证。

您可以使用双精度的十六进制格式将其序列化为无损表示的文本。

 stream << std::fixed << std::scientific << particles[i].pos[0];

 // If you are using C++11 this was simplified to

 stream << std::hexfloat << particles[i].pos[0];

这具有在 C 中打印与“%a”相同的值的效果,printf()将字符串打印为“十六进制浮点,小写”。这里radixmantissa都被转换为十六进制值,然后以非常特定的格式打印。由于底层表示是二进制的,因此这些值可以精确地以十六进制表示,并提供一种在系统之间传输数据的无损方式。IT 还会截断前进和后继的零,因此对于很多数字来说是相对紧凑的。

在蟒蛇方面。也支持这种格式。您应该能够将值作为字符串读取,然后使用将其转换为浮点数float.fromhex()

见:https ://docs.python.org/3/library/stdtypes.html#float.fromhex

但您的目标是节省空间:

但是现在,为了节省空间,我试图将配置保存为二进制文件。

我会问你真的需要节省空间吗?您是否在低功耗低资源环境中运行?当然,节省空间绝对是一件事(但现在很少见(但这些环境确实存在))。

但似乎您正在运行某种形式的粒子模拟。这并没有尖叫低资源用例。即使你有 tera 字节的数据,我仍然会使用一种可移植的易于阅读的二进制格式。最好是没有损耗的。存储空间便宜。


推荐阅读