在 C++ 中将 double 保存为二进制文件的问题

Issues saving double as binary in c++

在我的粒子系统模拟代码中,我为粒子定义了一个 class,每个粒子都有一个 属性 of 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();

但是现在,为了保存 space,我试图将配置保存为二进制文件,我的尝试受到 here 的启发,如下所示:

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 中的这种阅读方式将永远有效!如果有人能阐明这种方法的可靠性,那就太好了。

我建议使用 而不是从头开始编写 serialization/deserialization 例程。我找到 cereal really easy to use, maybe even easier than boost::serialization。它减少了您自己的代码中出现错误的机会。

在你的情况下,我将使用谷物像这样序列化 doubles:

#include <cereal/archives/binary.hpp>
#include <fstream>

int main() {
    std::ofstream outfile("test.bin", ios::binary);
    cereal::BinaryOutputArchive out(outfile);
    double x, y, z;
    x = y = z = 42.0;
    out(x, y, z);
}

要反序列化它们,您可以使用:

#include <cereal/archives/binary.hpp>
#include <fstream>

int main() {
    std::ifstream infile("test.bin", ios::binary);
    cereal::BinaryInputArchive in(infile);
    double x,y,z;
    in(x, y, z);
}

您也可以用同样的方式 serialize/deserialize 整个 std::vector<double>。只需添加 #include <cereal/types/vector.hpp> 并使用 in / out 就像给定示例中的单个 std::vector<double> 而不是多个 doubles.

是不是膨胀了。


编辑

在您询问的评论中,是否可以使用 Python.

读取创建的二进制文件

答案:

序列化的二进制文件并不意味着非常便携(像字节顺序 可以 在这里发挥作用)。您可以轻松地修改我给您的示例代码来编写 JSON file (another advantage of using a library) and read that format in Python.

哦,cereal::JSONOutputArchive 有一个 option for setting precision

问题是 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 中打印与 printf() 中的“%a”相同的值,将字符串打印为 "Hexadecimal floating point, lowercase"。这里 radixmantissa 在以非常特定的格式打印之前都被转换为十六进制值。由于底层表示是二进制,因此这些值可以精确地以十六进制表示,并提供一种在系统之间传输数据的无损方式。 IT 还会截断前面和后面的零,因此对于很多数字来说相对紧凑。

python 一侧。也支持这种格式。您应该能够将值作为字符串读取,然后使用 float.fromhex()

将其转换为浮点数

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

但你的目标是拯救 space:

But now, to save space, I am trying to save the configuration as a binary file.

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

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

只是好奇您是否研究过将数据转换为矢量坐标而不是笛卡尔 X、Y、Z 的想法?看起来这可能会减少大约 30% 的数据大小:两个坐标而不是三个坐标,但可能需要稍高的精度才能转换回 X、Y、Z。

矢量坐标仍可通过使用上述各种压缩技术(文本压缩或二进制转换)进一步优化。