Fortranで作られたバイナリをc++から読む必要が生じたので、コードを書いてみました。
Fortranコード
バイナリを作成するFortranのコードは
program main
implicit none
integer::i,j,k
real(8)::a
real(8)::vec(3)
i = 1
j = 2
k = 7
a = cos(1d0)
vec(1) = 2.1d0
vec(2) = 3.2d0
vec(3) = sin(2d0)
open(11,file="test.dat",form="unformatted")
write(11) i
write(11) j,k
write(11) a
write(11) vec
close(11)
end program
です。このコードをbinary.f90
として保存して、
gfortran binary.f90
でコンパイルして実行すると、test.dat
というバイナリファイルができます。
バイナリの構造ですが、
https://akebi28.hatenablog.jp/entry/2017/02/28/081307
によると、Fortranはwrite文を書くたびにrecord markerなる4バイトのバイナリをwriteの前後に付け加えているようです。この4バイトに注意して読み込む必要があります。
C++で読む
作ったファイルの中身をC++で読んでみましょう。C++はまだ勉強中であまりよくわかっていませんが、C++17を使うことにします。まず、バイナリファイルを読むためには、
std::string filename = "test.dat";
std::ifstream fin(filename, std::ios::in | std::ios::binary );
のようにstd::ifstream
のオブジェクトを使います。整数を読み込む関数は
int read_int(std::ifstream &fin){
int ii;
fin.read( ( char * ) &ii, 4);
int ii2;
fin.read( ( char * ) &ii2, ii );
fin.read( ( char * ) &ii, 4);
return ii2;
};
とします。ここで、fin.read( ( char * ) &ii, 4)
は、Fortranのwrite文の前についている4バイトの情報を読み込むものです。この4バイトには、write文で書いた変数のサイズが格納されています。ですので、このサイズii
でread( ( char * ) &ii2, ii );
を使ってバイナリを読み込みます。
倍精度実数は
double read_double(std::ifstream &fin){
int ii;
fin.read( ( char * ) &ii, 4);
double x;
fin.read( ( char * ) &x, ii );
fin.read( ( char * ) &ii, 4);
return x;
};
は同じ感じでこれで読めます。
Fortranでwrite(11) j,k
のように複数の整数が並んでいるものを読み込む場合には、std::vector<int>
を使って読み込みます。例えば、
std::vector<int> read_multiint(std::ifstream &fin){
int ii;
fin.read( ( char * ) &ii, 4);
int num = ii / 4;
std::vector<int> x(num);
fin.read( ( char * ) &x[0], ii );
fin.read( ( char * ) &ii, 4);
return x;
};
とします。ここで、整数は4バイトなので、書かれているバイトii
を4で割ることで何個の要素があるかをnum = ii / 4
で調べています。
複数の倍精度実数も同様に
std::vector<double> read_multidouble(std::ifstream &fin){
int ii;
fin.read( ( char * ) &ii, 4);
int num = ii / 8;
std::vector<double> x(num);
fin.read( ( char * ) &x[0], ii );
fin.read( ( char * ) &ii, 4);
return x;
};
で読み込めます。倍精度実数は8バイトなのでnum = ii /8
となっています。
結局、Fortranで書き込んだバイナリを読むためのコードは
# include <string>
# include <fstream>
# include <iostream>
# include <vector>
int read_int(std::ifstream &fin){
int ii;
fin.read( ( char * ) &ii, 4);
int ii2;
fin.read( ( char * ) &ii2, ii );
fin.read( ( char * ) &ii, 4);
return ii2;
};
std::vector<int> read_multiint(std::ifstream &fin){
int ii;
fin.read( ( char * ) &ii, 4);
int num = ii / 4;
std::vector<int> x(num);
fin.read( ( char * ) &x[0], ii );
fin.read( ( char * ) &ii, 4);
return x;
};
double read_double(std::ifstream &fin){
int ii;
fin.read( ( char * ) &ii, 4);
double x;
fin.read( ( char * ) &x, ii );
fin.read( ( char * ) &ii, 4);
return x;
};
std::vector<double> read_multidouble(std::ifstream &fin){
int ii;
fin.read( ( char * ) &ii, 4);
int num = ii / 8;
std::vector<double> x(num);
fin.read( ( char * ) &x[0], ii );
fin.read( ( char * ) &ii, 4);
return x;
};
int main(int argc, char *argv[]){
std::string filename = "test.dat";
std::ifstream fin(filename, std::ios::in | std::ios::binary );
int i = read_int(fin);
std::cout << i << std::endl;
std::vector<int> x = read_multiint(fin);
for(auto v: x){
std::cout << v << " ";
};
std::cout << std::endl;
double a = read_double(fin);
std::cout << a << std::endl;
std::vector<double> y = read_multidouble(fin);
for(auto v: y){
std::cout << v << " ";
};
std::cout << std::endl;
};
と書けます。このコードをread.cpp
として保存して、
g++ -std=c++17 read.cpp
でコンパイルして実行すると、
1
2 7
0.540302
2.1 3.2 0.909297
となり、ちゃんと読み込めていることがわかります。