在不损失精度的情况下将双精度从 C++ 转移到 python

Posted

技术标签:

【中文标题】在不损失精度的情况下将双精度从 C++ 转移到 python【英文标题】:Transferring a double from C++ to python without loss of precision 【发布时间】:2020-05-28 10:22:56 【问题描述】:

我有一些输出双值数组的 C++ 代码。我想在 python 中使用这些双精度值。传输值的最明显和最简单的方法当然是将它们转储到文件中,然后在 python 中重新读取文件。但是,这会导致精度损失,因为并非所有小数位都可以转移。另一方面,如果我添加更多小数位,文件会变大。我尝试传输的数组有几百万个条目。因此,我的想法是使用 double 的二进制表示,将它们转储到二进制文件中并在 python 中重新读取。

第一个问题是,我不知道双精度值是如何在内存中格式化的,例如here。从内存中读取对象的二进制表示很容易,但我必须知道符号位、指数和 mantiassa 的位置。当然有standards。因此,第一个问题是,我如何知道我的编译器使用哪个标准?我想使用g++-9。我尝试为各种编译器搜索这个问题,但没有任何准确的答案。下一个问题是关于如何将字节转回给定格式。

另一种可能是将 C++ 代码编译为 python 模块并直接使用它,仅从内存中传输没有文件的数组。但我不知道这是否容易快速设置。 我还看到可以使用 numpy 直接从 python 中的字符串编译 C++ 代码,但我找不到任何文档。

【问题讨论】:

为什么需要知道内存表示?它很可能是 IEEE 754,无论您在哪里使用它,您都可以直接读写它 这能回答你的问题吗? What is the best method to read a double from a Binary file created in C? 如果您的 python 和 C++ 实现不同(假设它们都在同一平台上运行),我会感到惊讶。 使用 struct 库,这就是它的用途 【参考方案1】:

您可以以二进制形式写出双精度值,然后在 python 中使用struct.unpack("d", file.read(8)) 读取和转换它们,从而假设使用的是 IEEE 754。

但是有几个问题:

C++ 没有指定双精度的位表示。虽然在我遇到的任何平台上都是 IEEE 754,但这不应该被认为是理所当然的。 Python 采用大端字节序。所以在小端机器上你必须告诉struct.unpack什么时候读或者写之前改变字节序。

如果此代码针对特定机器,我建议仅在机器上测试该方法。 然后不应假定此代码适用于其他体系结构,因此建议您在 Makefile/CMakefile 中检查拒绝在意外目标上构建。

另一种方法是使用常见的序列化格式,例如 protobuf。他们基本上必须处理同样的问题,但我认为他们已经解决了。

【讨论】:

【参考方案2】:

我还没有检查过,但是 python 的 C++ 接口可能会通过复制它们所代表的二进制图像(64 位图像)来存储 doubles,因为很可能两种语言都使用相同的二进制浮点数内部表示(IEEE -754 二进制 64 位格式)这有一个原因:这是因为两者都使用浮点协处理器对它们进行操作,而这是传递数字所需的格式。

对此有一个问题,因为您没有说:您如何确定您正在丢失数据的精度?您是否只检查了不同的十进制数字?或者您是否导出了实际的二进制格式来检查位模式的差异?一个常见的错误是打印两个数字,比如说20 有效数字,然后观察最后两位或三位数字的差异。这是因为您不知道doubles 以这种方式表示(二进制 IEEE-752 格式)只有大约 17 个有效数字(这取决于数字,但您可能会有差异在第 17 位或之后,这是因为数字是二进制编码的)

我强烈不建议您将这些数字转换为十进制表示形式并将它们作为 ascii 字符串发送。您将在编码中失去一些精度(以舍入误差的形式,见下文),然后在 python 的解码阶段再次丢失。认为将二进制浮点数(即使以最大精度)转换为十进制,然后再转换回二进制几乎总是是一个丢失信息的过程。问题是一个可以精确表示为十进制的数字(如0.1)不能精确表示为二进制形式(你会得到一个周期性的无限重复序列,当你将1.0除以十进制的3.0时,你得到一个不精确的结果)相反的转换是不同的,因为您总是可以将有限十进制二进制数转换为有限十进制基数,但不在 53 位内 - 这是数量在 64 位浮点数中专用于有效位的位数)

所以,我的建议是重新检查您的数字显示差异的位置并与我在这里所说的进行比较(如果数字显示第 16 位十进制数字之后的数字位置差异,这些差异是可以的 --- 他们必须这样做 仅使用 C++ 库和 python 库将数字转换为十进制格式所使用的不同算法)如果在此之前出现差异,请检查 python 中浮点数的表示方式,或者检查是否在某些时候,通过将这些数字存储在单个精度 float 变量中(这比通常估计的更频繁),您会失去精度,并查看两种环境使用的格式是否存在一些差异(我不相信会有)。顺便说一句,在您的问题中显示此类差异应该是一个加分项(您也没有做过),因为我们可以告诉您您观察到的差异是否正常。

【讨论】:

以上是关于在不损失精度的情况下将双精度从 C++ 转移到 python的主要内容,如果未能解决你的问题,请参考以下文章

如何在不损失精度的情况下解码 NSDecimalNumber?

C++ fmtlib:如何将双精度转换为现有的 wchar_t 缓冲区?

将双精度转换为字符数组 C++

将双精度数舍入到小数点后一位(去掉小数位)

如何将双精度向量传递给构造函数,然后在子类中访问其数据(在 C++ 中)?

在 C++ 中使用 boost::lexical_cast 将双精度转换为字符串?