Python/Numpy生成的二进制文件需要被C语言读取

4

我正在用Python创建一个5*7的整数矩阵二进制文件,文件名为random_from_python_int.dat。然后我从C中读取这个二进制文件,但是无论如何我都无法得到正确的数字。以下是我生成此矩阵的Python代码:

import numpy as np
np.random.seed(10)
filename = "random_from_python_int.dat"
fileobj = open(filename, mode='wb')
b = np.random.randint(100, size=(5,7))
b.tofile(fileobj)
fileobj.close

这将生成一个矩阵。
[ [  9 15 64 28 89 93 29]
  [  8 73 0  40 36 16 11]
  [ 54 88 62 33 72 78 49]
  [ 51 54 77 69 13 25 13]
  [ 92 86 30 30 89 12 65] ]

但是当我从下面的C代码中读取它时:

#include <stdio.h>
#include <math.h>
int main()
{
  /* later changed 'double' to 'int', but that still had issues */
  double randn[5][7];

  char buff[256];
  FILE *latfile;

  sprintf(buff,"%s","random_from_python_int.dat");
  latfile=fopen(buff,"r");
  fread(&(randn[0][0]),sizeof(int),35,latfile);
  fclose(latfile);
  printf("\n %d     %d     %d     %d     %d     %d     %d",randn[0][0],randn[0][1],randn[0][2],randn[0][3],randn[0][4],randn[0][5],randn[0][6]);
  printf("\n %d     %d     %d     %d     %d     %d     %d",randn[1][0],randn[1][1],randn[1][2],randn[1][3],randn[1][4],randn[1][5],randn[1][6]);
  printf("\n %d     %d     %d     %d     %d     %d     %d",randn[2][0],randn[2][1],randn[2][2],randn[2][3],randn[2][4],randn[2][5],randn[2][6]);
  printf("\n %d     %d     %d     %d     %d     %d     %d",randn[3][0],randn[3][1],randn[3][2],randn[3][3],randn[3][4],randn[3][5],randn[3][6]);
  printf("\n %d     %d     %d     %d     %d     %d     %d\n",randn[4][0],randn[4][1],randn[4][2],randn[4][3],randn[4][4],randn[4][5],randn[4][6]);
}

它将为我提供以下内容(根据空格进行调整以避免在stackoverflow网站上滚动):
      28      15         64      93         29 -163754450   9
      40      73          0      16         11 -163754450   8
      33      88         62      17         91 -163754450  54
     256       0 1830354560       0    4196011 -163754450 119
 4197424 4197493 1826683808 4196128 2084711472 -163754450  12

我不确定问题出在哪里。我曾试过在Python中写入一个浮点矩阵,并在C语言中以double类型读取它,这很好地运行了。但是这个整数矩阵就是不起作用。


4
您将整数读入双精度浮点数。 - 0andriy
2
那么,在整数与双精度浮点数混淆之后,剩下的问题是:你如何知道“整数”numpy写入的大小与C使用的“int”相同? - ndim
糟糕!但是当我将双精度改为整型后,我得到了 9 0 15 0 64 0 28 0 89 0 93 0 29 0 8 0 73 0 0 0 40 0 36 0 16 0 11 0 54 0 88 0 62 0 33。 - harmony
1
“你怎么知道numpy写的“integer”和C语言使用的“int”大小相同?” - 这是一个非常好的想法。你可以尝试在谷歌上搜索一下。在C语言中,你可以通过使用int32_t、int64_t等类型来强制指定大小。 - rustyx
2
步骤1:latfile=fopen(buff,"r"); --> latfile=fopen(buff,"rb");(添加b - chux - Reinstate Monica
显示剩余4条评论
2个回答

6
正如 @tdube 所写的,您问题的简要概述是:您的 numpy 实现编写了 64 位整数,而您的 C 代码读取了 32 位整数。
更多细节请继续阅读。
当您将整数作为二进制数据的补码进行读写时,您需要确保以下三个整数属性在二进制数据的生产者和消费者之间相同:整数大小、整数字节序、整数符号。
numpy 和 C 的符号都是有符号的,因此在这里我们匹配。
字节序在这里不是问题,因为 numpy 和 C 程序都在同一台机器上,因此您可能具有相同的字节序(无论实际上是什么字节序)。
但是,大小是问题。
默认情况下,numpy.random.randint 使用 np.int 作为其 dtype。根据文档,np.int 的大小未知,但在您的系统上为 64 位。 numpy scalars reference 列出了一些整数类型(令人惊讶的是不包括 np.int),其中三个组合对于与 numpy 外部程序进行强大的接口非常有用:
 # | numpy    | C
---+----------+---------
 1 | np.int32 | int32_t
 2 | np.int64 | int64_t
 3 | np.intc  | int

如果你只是与用于构建numpy的相同C环境进行接口,那么使用(np.intc,int)类型对(来自情况3)看起来是安全的。然而,出于以下原因,我强烈建议使用明确大小的类型之一(情况1和2): 1.在numpy和C中都很明显整数的大小是多少。 2.因此,您可以使用numpy生成的输出将其与使用不同大小int编译的程序进行接口。 3.您甚至可以使用numpy生成的输出将其与使用完全不同的语言编写或编译并在完全不同的机器上运行的程序进行接口。但是,您必须考虑不同机器的字节顺序。

np.int 不是 numpy 的类型。它只是内置的 Python int 的一个令人困惑的别名,现在已经太晚删除了。 - Eric
事实是:np.int在NumPy标量参考文档中没有记录,但是numpy.random.randint的文档记录了使用np.int作为默认类型。也许可以修改文档? - ndim
那是randint文档中的一个错误,我认为我最近在主分支中已经修复了它。np.int不应该出现在numpy标量页面上,因为它不是标量类型 - 但也许应该在那里发出一个明确说明的警告。(对于np.float、np.complex等也是如此...) - Eric
不错的发现。看起来我在#9517中漏掉了pyx文件。 - Eric

3

简短回答

你的Python程序输出64位整数,而不是你尝试用C程序读取的32位整数。

您可以更改以下代码行:

b = np.random.randint(100, size=(5,7), dtype=np.int32)

现在您将在输出文件中看到32位整数。
如何确定Python代码的输出内容
根据对输出文件的hexdump分析,您的Python代码会输出64位整数。当然,您可以使用任何十六进制编辑器应用程序检查二进制数据文件。
$ hexdump random_from_python_int.dat
0000000 09 00 00 00 00 00 00 00 0f 00 00 00 00 00 00 00
0000010 40 00 00 00 00 00 00 00 1c 00 00 00 00 00 00 00
0000020 59 00 00 00 00 00 00 00 5d 00 00 00 00 00 00 00

如@ndim在他的答案中指出的那样,二进制补码整数表示由三个主要元素组成:[存储] 大小字节序符号性。我不会重复他在答案中提供的信息,只是展示如何从上面的输出中推导出这些信息,这也是我在原始答案中开始做的事情。
在多维数组的情况下,您可能还需要了解线性存储中元素的顺序

推断整数存储大小

由于您间接指定了(十进制)100 的最大非包含随机值,因此您的值将在十进制范围内[0, 100),或在十六进制中为[0x0, 0x64),它们都可以用单个“十六进制字节”表示。请注意,上述hexdump输出中的非00十六进制字节都不在此范围之外。正如您所看到的,在表示每个整数值时使用了总共8个字节(基于此情况下数字范围的1个非00字节和7个00字节)。

推断字节序

此外,你现在也可以推断整数表示的 endianness,在这种情况下是 little endian,因为线性存储中的第一个字节包含了 least significant bit(LSB)。LSB 也可以称为 least signficant byte

推断符号

在这种情况下,你无法推断符号,因为取样中没有负值。如果有负值,则在二进制补码表示中,你会看到 signed bit 的值为 1。我不会深入探讨关于这个问题的二进制补码负整数表示的细节,因为这与本问题无关。

推断多维数组顺序

在文件偏移量(0x0000000(未标记的是0000008)处开始的前两个8字节小端整数的检查结果为十六进制值0x00000000 000000090x00000000 0000000f,它们分别是915的十进制值。十进制值9将是行主序列主序中的第一个值,但线性存储中的第二个十进制值为15表明采用了行主序,因为元素在连续存储中。

文件偏移量(0x0000010处的第三个整数值的十六进制值为0x00000000 00000040,它在十进制中是数字值64。这个值是按行主序排列的期望输出中的第三个值。

为了完整起见,列优先顺序将输出线性存储中表示的第二个整数的十进制值为8。
如何使Numpy在Python代码中转储32位数字
要使您的代码转储32位数字(这是int的常见实现长度,但在C标准中它是“实现定义的”,只指定int表示的最小范围),您可以更改以下代码行:
b = np.random.randint(100, size=(5,7), dtype=np.int32)

现在您将在输出文件中看到32位整数。
$ hexdump random_from_python_int.dat
0000000 09 00 00 00 0f 00 00 00 40 00 00 00 1c 00 00 00
0000010 59 00 00 00 5d 00 00 00 1d 00 00 00 08 00 00 00
0000020 49 00 00 00 00 00 00 00 28 00 00 00 24 00 00 00

注意:C语言中,实际存储大小(精度)的int变量是“实现定义”的,这意味着您可能需要在输出前调整numpy数组的整数存储大小,以获得与C的最大兼容性。请参见@ndim的出色答案,其中提供了更多详细信息。
C代码的更改
您的C代码必须更新以反映二维数组数据类型的更改。在您的代码中,double randn[5][7] 应为 int randn[5][7]。如@ndim所指出,您还可以将类型设置为int32_t,但您的编译器可能会发出错误并建议使用数据类型__int32_t(在我的系统上是inttypedef)。进行更改和编译后,我得到以下输出:
 9     15     64     28     89     93     29
 8     73     0     40     36     16     11
 54     88     62     33     72     78     49
 51     54     77     69     13     25     13
 92     86     30     30     89     12     65

更新(请参考更新#2)

按照@ndim下面的评论,您也可以像下面这样使用np.intc除非您针对整数表示的特定存储大小,否则此选项可能是最佳选项。

b = np.random.randint(100, size=(5,7), dtype=np.intc)

我测试过,它也可以生成32位整数。
更新#2
我完全同意@ndim的观点,指定整数大小是最大化兼容性的最佳选择。这里适用Python惯用语“最少惊讶原则”。

1
根据numpy文档,np.intc将使用C语言视为“int”的任何内容,并将其写入numpy文件中。 - ndim

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