Fortran程序生成的二进制文件的格式问题

最近在用C++程序读取Fortran程序生成的二进制文件(即用unformatted格式打开并写入的文件)时遇到一些问题,这里对解决这一问题的过程做一个简单的记录。

对于PWmat生成的电荷密度文件,可以用convert_rho.x将其转成xsf文件,相关代码如下:

program convert_rho
implicit none
integer n1,n2,n3,nnodes
real*8 AL(3,3)
open(11,file="TEST.RHO",form="unformatted")
rewind(11)
read(11) n1,n2,n3,nnodes
read(11) AL
......

读取的前四个整型数依次为128、128、128和16384。如果用C++程序读取同样的文件,可以的将上面的代码对应的改写为:

#include <iostream>
#include <fstream>
int main()
{
    int n1,n2,n3,nnodes;
    double AL[3][3];
    std::ifstream infile ("TEST.RHO", std::ios::in|std::ios::binary);
    if (infile.is_open())
    {
        infile.seekg(0, std::ios::beg);
        //read the first line
        infile.read((char*)&n1, sizeof(int));
        infile.read((char*)&n2, sizeof(int));
        infile.read((char*)&n3, sizeof(int));
        infile.read((char*)&nnodes, sizeof(int));
        //read the second line
        for(int i = 0; i < 3; i++)
        {
            infile.read((char*)AL[i], 3 * sizeof(double));
        }
......

注意到这里读取二进制文件使用read方法中用到的是一个char*型的指针,实际上是按照字节读取再转成相应的数据类型。然而用上面的代码读出的前四个整型参数依次为16、128、128和128,与Fortran程序读取出的不一致。首先,我们用hexdump工具查看TEST.RHO文件的头部信息:

$ hexdump -n 48 TEST.RHO 
0000000 0010 0000 0080 0000 0080 0000 0080 0000
0000010 4000 0000 0010 0000 0048 0000 5f10 5fc3
0000020 b9d5 4028 0000 0000 0000 0000 0000 0000
0000030

注意到x86平台使用的是小端(little endian)字节序,即低位字节在前而高位字节在后。因此前四个字节存储的整型数为0x00000010即16,后面三个整型数均为0x00000080即128。由此可见C++程序运行过程是没有问题的,问题在于二进制文件具有特殊的格式。

关于Fortran程序输出的格式可以参看这篇文章。简单的说,Fortran对于unformatted格式的文件是按照行进行读写的,在每行的开头和结尾会各使用4个字节(整形)存储该行存储需要的字节数(不包括头部和尾部)。

以上面的TEST.RHO文件为例,前四个字节存储的整型数16表示第一行存储需要16个字节(4个整型数,即n1, n2 , n3, nnodes)因此后面三个128对应n1、n2 和n3,接下来四个字节存储的0x00004000即16384表示nnodes,后面四个字节存储的16表示第一行的尾部。接下来看第二行,开头四个字节0x00000048即72表示第二行存储需要72个字节(8个双精度浮点数),再接下8个字节0x4028B9D55FC35F10,使用转换工具可以看到其表示的(IEEE 754)双精度浮点数约为12.363,以下的部分类似。

关于每一行的头部和尾部,Fortran语言中的readwrite过程会自动进行处理,但是C++语言中按照字节依次读取就需要在每行读取前后分别加上一次读取整型数。因此上面的C++代码应该改为:

#include <iostream>
#include <fstream>
int main()
{
    int nbyte;
    int n1,n2,n3,nnodes;
    double AL[3][3];
    std::ifstream infile ("TEST.RHO", std::ios::in|std::ios::binary);
    if (infile.is_open())
    {
        infile.seekg(0, std::ios::beg);
        //read the first line
        infile.read((char*)&nbyte, sizeof(int));
        infile.read((char*)&n1, sizeof(int));
        infile.read((char*)&n2, sizeof(int));
        infile.read((char*)&n3, sizeof(int));
        infile.read((char*)&nnodes, sizeof(int));
        infile.read((char*)&nbyte, sizeof(int));
        //read the second line
        infile.read((char*)&nbyte, sizeof(int));
        for(int i = 0; i < 3; i++)
        {
            infile.read((char*)AL[i], 3 * sizeof(double));
        }
        infile.read((char*)&nbyte, sizeof(int));
......

这样读取的数据就和Fortran程序一致了。当然,这里的程序还比较粗糙,也许还有更好的处理的方法。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注