Fortran プログラミングの基礎知識

初版   1999.8.31
最終改訂 2006.2.20

今では Fortran はメジャーなプログラミング言語とは 言えませんが、数値計算の分野では、まだまだ現役の言語です。 複素数と整合配列を手軽に扱える言語は fortran しかありません。 Fortran90 も普及しつつあるようですが、過去の資産の活用、 FreeBSD や Linux で使えるコンパイラとなると、 fortran77 がまだまだ使われています。

今では書店に行ってもフォートランの本はほとんど置いておらず、 「本には載っていないけれども、実は基礎的なことがら」を知ることは 容易ではありません。そこで、このページでは Fortran77 で プログラミングするにあたっての基礎的で実戦的な知識を 紹介致します。 以下では Fortran と表記した場合、Fortran77 を意味します。 また、実数型変数は real*8 であることを前提としています。

  1. 暗黙の型宣言を使わない方法

    Fortran はデフォルトで変数の型宣言が不要です。i,j,k,l,m,n で 始まる変数は整数型、それ以外は実数型という暗黙の了解があります。 暗黙の型宣言を使いたくない場合は、サブルーチンの先頭で、

    implicit none

    と宣言すると、変数の宣言が必ず必要になります。 暗黙の型宣言を使用すると、変数の宣言を逐一行う 煩わしさがなくなりますが、以下のようなデメリットが あります。

    1. 変数の名前をタイプミスすると、新しい変数と みなされてしまいます。ftnchek などのツールを使うと この類のミスは発見してくれますが、コンパイラが発見して くれる方が楽です。また、後述する include 文を使う場合、 ftnchek の表示は非常に見づらくなります。
    2. 変数名として自由な名前を付けることが できません。たとえば最大値を max という変数で表したいとき、 rmax や dmax などのような変数名を付ける必要があります。

    変数のタイプミスを防ぐというメリットは非常に大きいので、 implicit none は使うべきであると思います。 ちょっとした使い捨てプログラムを 書くとき以外は暗黙の型宣言は使ってはいけません。

  2. 実数型は 8 バイト型実数を使う

    Fortran ではデフォルトの実数型は real*4 ( 4 byte 実数 : C 言語の float に相当 ) で、このときの有効数字は 6 桁です。 これでは有効桁数が少なすぎますので、 通常は実数型としては real*8 ( 8 byte 実数 : C 言語の double に相当 ) を 使います。この場合、約 15 桁の有効桁数が得られます。 どうしてもメモリを節約しなければならない場合を除いて、 real*8 を使うようにしてください。小数点以下を含む定数は

    ○ 1.2d0 8 byte 実数の精度 × 1.2 4 byte 実数の精度

    のように必ず d0(double precision, × 10^0 を意味する)を つけて下さい。 同様に、円周率を表す定数 pi を定義するときは、

    ○ pi = acos(-1.0d0) 8 byte 実数の精度で acos を求める × pi = acos(-1.0) 4 byte 実数の精度で acos を求める

    として下さい。 ただし、ファイルやキーボードから読み込むときは、 単精度の表記をしても、倍精度の実数として認識されるようです。

    real*8 a read(5,*) a

    において、キーボードから 0.1 と入力すると、a = 0.1d0 と 書くのと同じ結果になります。

    暗黙の型宣言を使う場合は、プログラムやサブルーチンの先頭に

    implicit real*8(a-h,o-z)

    と書いておくと、デフォルトの実数型が 8 バイト型になります。 もちろん、複素数は complex*16 を使うようにしましょう。

    ただし、私の研究室で使っているグラフ描画ライブラリの 実数型引数は 4 byte real なので当研究室の人は注意して下さい。

    暗黙の型宣言を使う場合、implicit 文の次のような使い方は 絶対に避けた方が良いでしょう。

    implicit real*8(a-h,k,o-z)

    これは、後で混乱を招く可能性が高い危険な方法です。

  3. 変数名とサブルーチン名

    Fortran77 の規格では変数名やサブルーチン名は 6 文字 以内となっていますが、 現在のほとんどの処理系では 6 文字以上の変数名やサブルーチン名 を許しています。 ですから、アンダーバーなども使い、

    subroutine solve_eqn(matrix,vector,v_size,h_size)

    のように、分かりやすいサブルーチン名や 変数名をつけると良いでしょう。

  4. コメント文

    第一桁が c ではじまる行はコメントであることは良く知られて いますが、

    a = b ! びっくりマークの後はコメント * a = b ! 第一桁が * マークでもコメント

    というコメントも使えます。f2c, g77 をはじめとして 私が使ったことのある全てのコンパイラでこの機能はサポート されています。

  5. 定数は別ファイルで定義し、それをインクルードする

    これを行わない場合、あらゆるファイルに定数が散りばめられる ことになり、プログラムの保守が大変になります。定数はインクルード ファイルで定義して、そのファイルをインクルードすべきです。 これを実現する方法として、次の 2 つの方法があります。

  6. プログラムは小文字で書く

    小文字で書いた方が見やすいので小文字を使いましょう。また、 大文字はマクロ ( #define で定義する ) や parameter 文で定義する 定数にだけ使うと混乱が少なくて済みます。

  7. do 〜 end do は使った方が良い

    do 〜 end do 構文は fortran77 規格には含まれていませんが、 FreeBSD, Linux 付属の f77, g77 や Sun 純正 fortran など、 ほぼ全てのコンパイラでサポートされているので 使った方が良いでしょう。文番号を無くすと、プログラム を cut & paste で他のプログラムの一部として取り込む時に 文番号の衝突を避けることが出来ます。

  8. do while ( 条件 ) 〜 end do 文も使った方が良いかもしれない

    do while 文は頻繁には使いませんが、FreeBSD , Linux の f77, g77 と Sun の純正 fortran では使えます。無限ループ を組むには論理条件の部分を .true. として下さい。

    do while ( 論理条件 ) ....... if ( 条件 ) exit ! ループを抜ける : C の break 文に相当 if ( 条件 ) cycle ! ループ先頭へ : C の continue 文に相当 ...... end do

  9. 比較をする演算子について

    Fortran 77 では .eq. .gt. .lt. などが if 文の中で 使用されますが、FreeBSD, Linux の f77, g77 は

    .eq. == .ne. /= .lt. < .le. <= .gt. > .ge. >=

    という表記を認めています。しかし、 Sun の純正 fortran ではダメなようなので、これはお勧めできません。 使えるコンパイラもある、ということを知っておくにとどめましょう。

  10. 整合配列

    Fortran ではサブルーチンに配列を渡し、サブルーチン側で 配列のサイズを定義することができます。次の例を 見て下さい。

    dimension a(6) a(1) = 1 a(2) = 2 a(3) = 3 a(4) = 4 a(5) = 5 a(6) = 6 call sub(a,3,2) end subroutine sub(a,n,m) dimension a(n,m) <---- a(n,*) でも同じ結果を得る do i=1,n write(6,*) (a(i,j),j=1,m) end do return end

    配列 a はメインルーチンでは 1 次元配列でしたが、 サブルーチン側では n 行 m 列の 2 次元配列であると みなしています。この例では write 文によって Fortran の 2 次元 配列の格納順序を見ています。a(3,2) の場合、 a(1,1),a(2,1),a(3,1),a(1,2),a(2,2),a(3,2) の順序で 格納されるわけです。このようにサブルーチン側で 2 次元配列 のサイズを決めることを整合配列といいます。数値計算 のプログラムはこの技法無しには書けないでしょう。

  11. read 文と write 文における改行

    Fortran は C とは違い、write 文を 1 回実行する度に 改行を行います。ゆえに、

    (1) do i=1,n write(6,*) a(i) end do (2) write(6,*) (a(i),i=1,n)

    では異なる結果となります。(1) では一つの要素毎に 改行するのに対して、(2) では全ての要素を空白で繋げて 出力したのち改行します。write の場合よりもっと厄介なのが read です。 read は 1 行読んだ後に改行します。

    ----- ファイル data の内容 ----- 1 2 3 4 5 6 7 8 9 10 11 12 空行 13 14 15 ----- プログラム ----- dimension b(4),c(4) open(1,file='data') read(1,*) a read(1,*) (b(i),i=1,2) read(1,*) c <---- (c(i),i=1,4) と同じ read(1,*) d

    まず a に 1 が入るのは誰でも分かるでしょう。 次の b(1) には何が入るか? read 文は読み込み実行後に 改行するので、b(1) には次の行の 4 が入ります。b(2) は 5 です。 次の c(1) 〜 c(4) はどうなるでしょうか。b(2) を読み込んだ後に 改行するので、c(1) は 7 です。そして c(2) = 8, c(3) = 9 と 入り、c(4) のところでその行には読み込むべきデータがなくなって しまいます。このときはデータが見つかるまで改行をして c(4) には 次の行の 10 が入ります。d は 13 です。

    このように Fortran は read を実行後に改行するので、上の場合

    1 2 3 ここには何を書いても良い 4 5 6 ここにも何も書いても良い 7 8 9 ここは c(i) の途中なので文字を書いてはダメ 10 11 12 ここにも何を書いても良い 空行 13 14 15

    ということになります。

  12. 文字型変数に対する read

    次の例を考えます。

    character moji*80 open(2,file='xxx') read(2,*) moji (1) read(2,'(a)') moji (2) ----------------------------------- ファイル xxx の内容 abc def abc def

    (1) では moji には 'abc' だけが入ります。空白は 区切り記号とみなされるようです。(2) では moji に 'abc def' が入ります。つまり format に a を指定する と、1 行全てが変数 moji に入ります。この違いを 使い分けられるようにしましょう。

  13. format 文は出来るだけ使わないようにしよう

    format 文を使うと文番号を使う必要があります。 以下のようにすると format 文を使わずに済ませることが出来ます。

    write(6,'(a,f8.5,a,i3.3)') 'a = ',a,' i = ',i

    format 文を使わないことにより、プログラムの cut & paste の ときに文番号の衝突を気にしなくて済みます。 なお、上の例の i3.3 は 3 桁で出力し、それに満たない場合は 0 を 補うと言う意味です。

  14. open 文実行時のエラー処理

    「ファイルをオープンして読み込むが、そのファイルが 存在しない場合はエラーを出してストップする」という 処理は以下のように実現できます。

    open(1,file='fname',status='old',err=990) 中略 990 ファイルが存在しない時の処理

    「ファイルをオープンして読み込むが、そのファイルが 存在しない場合はエラーを出してストップする」という 処理は以下のように実現できます。

  15. ファイルの削除

    あるファイルを削除したいときは、以下のようにすればよろしい。

    open(1,file='fname') close(1,status='delete')

  16. read 文実行時のエラー処理

    Fortran の read 文はエラーチェックの機能も持っています。 数値を読み込むべき時に文字列があるとエラーを出してくれます。 ファイルの終わりまで行った場合もエラーになります。 エラーが発生した場合に飛ぶ文番号を指定することができるので、 この機能を使うと便利です。例えば、下のようなプログラムが 考えられます。

    open(1,file="file-name") read(1,*,end=980,err=990) a 中略 980 ファイルエンドに出会ったときの処理 goto xxx 990 エラーが発生した時の処理 goto yyy

    なお、エラーが発生したときに飛ぶ文番号を指定しない状態で エラーが発生すると、core をダンプしてプログラムは ストップします。

  17. バイナリ入出 力

    変数のメモリイメージをそのまま入出力することが できます。つまり、C でいう

    double a[100]; 中略 fp=fopen("r","fname"); fwrite(a,sizeof(a[0]),100,fp);

    と似たような操作が fortran でも可能です。

    real*8 a(100) 中略 open(1,file="fname",form='unformatted') write(1) (a(i),i=1,100)

    この方法は速度は高速になりますが、ファイルの可読性はなくなり、 変数のメモリイメージは CPU に依存するので互換性は低くなります。

    また、この書式なし入出力文は C の fread, fwrite と完全に同一 ではありません。

    write(1) i,j

    のとき、

    ( i と j のバイト数の和 ) ( i の内容 ) ( j の内容 ) ( i と j のバイト数の和 )

    という順番でバイナリが書き込まれます。ゆえに、

    write(1) i,j

    write(1) i write(1) j

    では異なる内容がファイルに書き込まれます。前者を読み出すときは

    read(1) i,j

    とし、後者を読み出すときは

    read(1) i read(1) j

    とせねばなりません。

  18. 内部入出力文を活用しよう

    内部入出力文とは、文字型変数から読み込んだり、 文字型変数に書き込んだりする機能です。 C の sprintf , sscanf に相当します。 例を示します。

    character*80 str str = '12 34' read(str,*) a,b a に 12 , b に 34 が入る write(str,*) 'a = ',a

    内部入出力文により「文字列」と「値」の変換が可能になります。

  19. 数学関数は総称名を使う。引数によりコンパイラが 自動判定する

    C では sin , cos などの数学関数は、引数が double, 返り値も double です。関数プロトタイプ宣言されているので、如何なる型を 引数として入れても (double) 型に型キャストされます。

    それに対して fortran では例えば、sin で 引数が 4 byte 実数型の場合は 「引数 4 byte 精度、返り値 4 byte 精度の sin」 引数が 8 byte 実数型の場合は 「引数 8 byte 精度、返り値 8 byte 精度の dsin」 が使われます。ですから、

    exp(r4) r4 : real dexp(r8) r8 : real*8 cexp(c8) c8 : complex zexp(c16) c16: complex*16 cdexp(c16) c16: complex*16

    のように、個別名を使う必要はありません。引数が如何なる場合でも

    exp(x)

    で大丈夫です。個別名 ( dexp, cexp など ) を使うと名前を間違って しまう可能性があります。

    また、引数として定数を使う時は、倍精度の引数を入れないと 精度が落ちてしまいます。

    ○ pi = acos(-1.0d0) × pi = acos(-1.0)

  20. 間違えやすい総称名

    関数名は総称名を使うべきです。しかし、一見、 総称名のように見えますが、注意すべき関数があります。 cmplex*16 の実部をとるときは dble() を使い、 complex*16 の複素数に値を代入するときは dcmplx() を 使う必要があります。real() や cmplx() を使ってはいけません。

    real*8 a,b,c,d,e,f complex*16 c16 complex*8 c8 a = real(c8) b = imag(c8) c = dble(c16) × c = real(c16) d = imag(c16) dimag(c16) も可 c16 = conjg(c16) dconjg(c16) も可 c16 = dcmplx(e,f) × c16 = cmplx(e,f)

    うっかり c = real(c16) とやってしまうと、一旦 4 byte 精度に してから c に代入してしまいます。また、cmplx(e,f) と書くと 倍精度実数 e,f から作られる複素数を、一旦、単精度複素数に 直してから c16 に代入するので精度が落ちてしまいます。

    それに対して imag(), conjg() は引数の型によって 戻り値の精度が選ばれますので、使ってよろしい。

    ただし「複素数に関連する関数のうち imag(), conjg() は 総称名である」というのを覚えるのは 紛らわしいので、「複素数に関係する関数は総称名を使わない」 としてしまってもよいでしょう。

    < 注意!! >
    f2c は real(c16) のとき勝手に dble(c16) に置き換えます。 FreeBSD-3.x の f77 は内部で f2c を呼ぶので、上記の現象が起こります。ゆえに

    call sub(real(c16))

    のとき、f2c ではコケてしまうので上記のような書き方は やめた方が良いでしょう。

  21. 文字型変数

    fortran の文字型変数は C とは考え方が異なります。 常に fortran 文字型変数は常に「文字数」という属性を 保持しています。 文字型変数に文字を代入する場合、以下のようになります。

    character str*4 str = 'abcdef' ef は切り捨てられ 'abcd' だけが入る str = 'ab' 空白が補われ 'ab ' が入る

    文字列の比較は、短い方の文字列の右側に空白を付加して 同じ長さにしてから行われます。

    character str*3, str2*4 str = 'ab' str = 'ab ' と同じ str2 = 'ab' str2= 'ab ' と同じ if ( str.eq.str2 ) 'ab ' と 'ab ' が同一かどうか

    例えば、文字長 80 文字の文字型変数と文字長 40 の文字型変数が 等しいかどうかを判別する場合、文字長 40 の文字型変数の 後ろに 40 個の空白が付け加えられた後、80 回の比較が行われます。 コンパイラの最適化にもよりますが、一般に、 文字型変数の比較は if ( i.eq.j ) と比べて遥かに時間がかかると 考えて下さい。

  22. Fortran は文字処理に強い

    私の感覚では C よりも fortran の方が文字処理に強いように思われます。 以下に例を示します。

    character a*20,b*20,c*20 a = 'abcdefghijklmn' b = a(3:6) ! 部分文字列の代入 c(3:*) = a ! c の 3 文字目以降に a を代入 i = index(a,'d') ! 文字 'd' は a の何文字目か

    また、サブルーチンに文字型変数を渡す時は、 文字型変数の長さ ( character*80 moji なら 80 ) も 引数として渡されます。 次の例を見て下さい。

    character*6 moji moji = '123' ! moji = '123 ' と同じ。空白が補われる call sub(moji) end subroutine sub(moji) character*(*) ! 文字列の長さは * にしておく write(6,*) len(moji) return end

    サブルーチンへ渡す引数として、文字型変数 moji の先頭アドレス と共に文字数 6 も渡されます。ゆえに、 len という組み込み関数を使うと、サブルーチン側で文字列の長さを 取得することができます。ここでの文字列の長さは character*6 で 指定された時の長さであることに注意して下さい。 'abc ' が入っているので 3 文字ではありません。

  23. iargc getarg getenv も使える

    環境変数やコマンドラインの引数を取得することが出来ます。 g77 では以下のように iargc, getarg, getenv が使えます。

    integer iarg character*80 str iarg = iargc() ! 引数の個数 ( コマンド名は含まない ) call getarg(0,str) ! コマンド名 call getarg(1,str) ! 第 1 引数 call getenv('HOME',str)

    gfortran では iargc, getarg は使えず、その代わりに command_argument_count, get_command_argument があるそうですが、私は gfortran を使ったことが ないので、詳細は分かりません。

  24. 標準出力と標準エラー出力を使い分けよう

    C でプログラムを組む場合、標準出力 stdout と標準エラー出力 stderr の使い分けは必須です。fortran でも標準出力と標準エラー出力を 使うことが出来ます。FreeBSD, Linux での f77 や g77, Sun の純正 コンパイラなど、Unix 上での fortran コンパイラでは write(6,*) が標準出力、write(0,*) が標準エラー出力です。

  25. プログラム終了時の戻り値を指定する

    stop 文や end 文でプログラムを終了すると、終了コードは 0 となります。 エラーが発生したときに終了コードを 1 とするには、exit 関数を呼びます。

    call exit(1)

  26. call system('ls') として外部プログラムを実行する

    C には system 関数という便利な関数があります。 Fortran でも g77 では

    iret = system('ls')

    あるいは

    call system('ls')

    のように system 関数が用意されています。 また、Fortran から呼べる system 関数を自作することは 容易です。その方法は、 ここ で紹介しています。

  27. save 文と entry 文を使ってオブジェクト指向しよう

    Fortran でオブジェクト指向するには save 文と entry 文が不可欠です。 詳細は ここ に書いてあります。 これを活用すると、プログラムを組むのが非常に楽になることが 多いと思います。

  28. プログラムの高速化のテクニック

    Unix の場合は prof または gprof コマンドを使うと 各サブルーチンの処理時間の統計を 出すことが出来ます。 FreeBSD の場合、まず、コンパイルする時に

    % f77 -c -pg sample.f

    のように -pg オプションを付けてコンパイルし、 リンク時にも

    % f77 -pg -o exe sample.o

    のように -pg オプションを付けます。こうすると、 実行型ファイル exe を実行すると exe.gmon という ファイルが作成されます。

    % gprof exe exe.gmon

    を実行すると各サブルーチンの所用時間の統計が出力されます。 このようにしてボトルネックとなっているサブルーチンを見つけ、 高速化出来ないかどうかを検討しましょう。

  29. デバッグのテクニック

    Unix の場合は gdb というデバッガが使えます。 gdb は C 言語用のデバッガなので、fortran から使う場合、かなり 機能が限定されてしまうようです。私も詳しくは知らないのですが、 次の使い方を知っておくだけで、かなり役にたちます。

    まず、gdb を使うためには以下のように プログラムを -g オプションを付けてコンパイルしておく必要が あります。

    % f77 -c -g sample.f

    プログラムが異常終了するときは大抵 core ファイルを作成 します。core ファイルはデフォルトで作成されるようになって いると思いますが、そうでない場合は以下のように再設定して下さい。

    % limit coredumpsize unlimited

    異常終了の原因を知りたい場合、次のような引数で gdb を 起動します。

    % gdb exe-fname exe-fname.core

    そして

    (gdb) where

    と打つと、どういうファイルの何行目でプログラムが異常終了 したかが表示されますので、そのファイルの該当箇所を調べます。 これで、分からない場合、

    % gdb exe-fname (gdb) run .....fault.... (gdb) where

    のように gdb の中から実行すると、より詳しい情報が得られる 場合があります。

    また、gdb で fortran のプログラムをデバッグする場合、

    (gdb) set lang fortran

    と打つと良いようです。

  30. do ループの仕様

    本項の記述は fj.comp.lang.fortran に 2003/1/31 に投稿された 片山さんの記事 ( Message-ID: <KATE.03Jan31133240@flash.tokyo.pfu.co.jp> ) と 戸田孝さん記事 ( Message-ID: <b1d3ag$tqh$1@bluegill.lbm.go.jp> ) を ほとんどそのまま引用して書いています。

    fortran の do ループはプログラムの文面に現れない iteration count によって制御されます。

    do x = m1, m2, m3

    の場合、iteration count の最初の値は

    max(int((m2 - m1 + m3)/m3), 0)

    となります。例えば、

    do x = 1, 2, 0.3

    の場合、int((2 - 1 + 0.3)/0.3) = 4 になります。

    そして、do ループ の range を実行するごとに、

    (1) do 変数に m3 を加える
    (2) iteration count から 1 を 引く
    (3) iteration count を調べ、0 より大きければ do ループを繰り返す
    

    という処理を行います。(1) の手順の後に、do ループを抜けるか どうかの (3) の判断するという点が重要です。ゆえに、

    do i = 1, 10 .... end do

    の do ループが終わった直後、i の値は 11 になります。