初版 1999.8.31
最終改訂 2006.2.20
今では Fortran はメジャーなプログラミング言語とは 言えませんが、数値計算の分野では、まだまだ現役の言語です。 複素数と整合配列を手軽に扱える言語は fortran しかありません。 Fortran90 も普及しつつあるようですが、過去の資産の活用、 FreeBSD や Linux で使えるコンパイラとなると、 fortran77 がまだまだ使われています。
今では書店に行ってもフォートランの本はほとんど置いておらず、 「本には載っていないけれども、実は基礎的なことがら」を知ることは 容易ではありません。そこで、このページでは Fortran77 で プログラミングするにあたっての基礎的で実戦的な知識を 紹介致します。 以下では Fortran と表記した場合、Fortran77 を意味します。 また、実数型変数は real*8 であることを前提としています。
Fortran はデフォルトで変数の型宣言が不要です。i,j,k,l,m,n で 始まる変数は整数型、それ以外は実数型という暗黙の了解があります。 暗黙の型宣言を使いたくない場合は、サブルーチンの先頭で、
と宣言すると、変数の宣言が必ず必要になります。 暗黙の型宣言を使用すると、変数の宣言を逐一行う 煩わしさがなくなりますが、以下のようなデメリットが あります。
変数のタイプミスを防ぐというメリットは非常に大きいので、 implicit none は使うべきであると思います。 ちょっとした使い捨てプログラムを 書くとき以外は暗黙の型宣言は使ってはいけません。
Fortran ではデフォルトの実数型は real*4 ( 4 byte 実数 : C 言語の float に相当 ) で、このときの有効数字は 6 桁です。 これでは有効桁数が少なすぎますので、 通常は実数型としては real*8 ( 8 byte 実数 : C 言語の double に相当 ) を 使います。この場合、約 15 桁の有効桁数が得られます。 どうしてもメモリを節約しなければならない場合を除いて、 real*8 を使うようにしてください。小数点以下を含む定数は
のように必ず d0(double precision, × 10^0 を意味する)を つけて下さい。 同様に、円周率を表す定数 pi を定義するときは、
として下さい。 ただし、ファイルやキーボードから読み込むときは、 単精度の表記をしても、倍精度の実数として認識されるようです。
において、キーボードから 0.1 と入力すると、a = 0.1d0 と 書くのと同じ結果になります。
暗黙の型宣言を使う場合は、プログラムやサブルーチンの先頭に
と書いておくと、デフォルトの実数型が 8 バイト型になります。 もちろん、複素数は complex*16 を使うようにしましょう。
ただし、私の研究室で使っているグラフ描画ライブラリの 実数型引数は 4 byte real なので当研究室の人は注意して下さい。
暗黙の型宣言を使う場合、implicit 文の次のような使い方は 絶対に避けた方が良いでしょう。
これは、後で混乱を招く可能性が高い危険な方法です。
Fortran77 の規格では変数名やサブルーチン名は 6 文字 以内となっていますが、 現在のほとんどの処理系では 6 文字以上の変数名やサブルーチン名 を許しています。 ですから、アンダーバーなども使い、
のように、分かりやすいサブルーチン名や 変数名をつけると良いでしょう。
第一桁が c ではじまる行はコメントであることは良く知られて いますが、
というコメントも使えます。f2c, g77 をはじめとして 私が使ったことのある全てのコンパイラでこの機能はサポート されています。
これを行わない場合、あらゆるファイルに定数が散りばめられる ことになり、プログラムの保守が大変になります。定数はインクルード ファイルで定義して、そのファイルをインクルードすべきです。 これを実現する方法として、次の 2 つの方法があります。
Sun の純正 fortran や昔の Sony の News 付属の fortran 、g77 は 拡張子を .F とすると cpp を通した後、コンパイルします。従って、 cpp の命令である #define や #include や #ifdef が使えます。 cpp を通すので、C 形式の /* */ のコメントも可能になります。 ただし、/* */ 形式のコメントを使うと mule の fortran モードの インデントがおかしくなるので、あまりお勧めはできません。
FreeBSD 3.x の f77 コマンドなど .F のファイルを 扱えないコンパイラもあります。 これを解決するために、 私が作った ff77 という perl スクリプトがあります。 cpp を通した後、f77 に渡します。 また、多くの Linux のディストリビューションに 含まれている fort77 という perl スクリプトは拡張子が .F の 場合は cpp を通してから f2c を呼び出して、gcc でコンパイルするという perl スクリプトです。
定数はインクルードファイル中に parameter 文で記述し、 include 文を使ってインクルードファイルを取り込みます。
小文字で書いた方が見やすいので小文字を使いましょう。また、 大文字はマクロ ( #define で定義する ) や parameter 文で定義する 定数にだけ使うと混乱が少なくて済みます。
do 〜 end do 構文は fortran77 規格には含まれていませんが、 FreeBSD, Linux 付属の f77, g77 や Sun 純正 fortran など、 ほぼ全てのコンパイラでサポートされているので 使った方が良いでしょう。文番号を無くすと、プログラム を cut & paste で他のプログラムの一部として取り込む時に 文番号の衝突を避けることが出来ます。
do while 文は頻繁には使いませんが、FreeBSD , Linux の f77, g77 と Sun の純正 fortran では使えます。無限ループ を組むには論理条件の部分を .true. として下さい。
Fortran 77 では .eq. .gt. .lt. などが if 文の中で 使用されますが、FreeBSD, Linux の f77, g77 は
という表記を認めています。しかし、 Sun の純正 fortran ではダメなようなので、これはお勧めできません。 使えるコンパイラもある、ということを知っておくにとどめましょう。
Fortran ではサブルーチンに配列を渡し、サブルーチン側で 配列のサイズを定義することができます。次の例を 見て下さい。
配列 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 次元配列 のサイズを決めることを整合配列といいます。数値計算 のプログラムはこの技法無しには書けないでしょう。
Fortran は C とは違い、write 文を 1 回実行する度に 改行を行います。ゆえに、
では異なる結果となります。(1) では一つの要素毎に 改行するのに対して、(2) では全ての要素を空白で繋げて 出力したのち改行します。write の場合よりもっと厄介なのが read です。 read は 1 行読んだ後に改行します。
まず 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) では moji には 'abc' だけが入ります。空白は 区切り記号とみなされるようです。(2) では moji に 'abc def' が入ります。つまり format に a を指定する と、1 行全てが変数 moji に入ります。この違いを 使い分けられるようにしましょう。
format 文を使うと文番号を使う必要があります。 以下のようにすると format 文を使わずに済ませることが出来ます。
format 文を使わないことにより、プログラムの cut & paste の ときに文番号の衝突を気にしなくて済みます。 なお、上の例の i3.3 は 3 桁で出力し、それに満たない場合は 0 を 補うと言う意味です。
「ファイルをオープンして読み込むが、そのファイルが 存在しない場合はエラーを出してストップする」という 処理は以下のように実現できます。
「ファイルをオープンして読み込むが、そのファイルが 存在しない場合はエラーを出してストップする」という 処理は以下のように実現できます。
あるファイルを削除したいときは、以下のようにすればよろしい。
Fortran の read 文はエラーチェックの機能も持っています。 数値を読み込むべき時に文字列があるとエラーを出してくれます。 ファイルの終わりまで行った場合もエラーになります。 エラーが発生した場合に飛ぶ文番号を指定することができるので、 この機能を使うと便利です。例えば、下のようなプログラムが 考えられます。
なお、エラーが発生したときに飛ぶ文番号を指定しない状態で エラーが発生すると、core をダンプしてプログラムは ストップします。
変数のメモリイメージをそのまま入出力することが できます。つまり、C でいう
と似たような操作が fortran でも可能です。
この方法は速度は高速になりますが、ファイルの可読性はなくなり、 変数のメモリイメージは CPU に依存するので互換性は低くなります。
また、この書式なし入出力文は C の fread, fwrite と完全に同一 ではありません。
のとき、
という順番でバイナリが書き込まれます。ゆえに、
と
では異なる内容がファイルに書き込まれます。前者を読み出すときは
とし、後者を読み出すときは
とせねばなりません。
内部入出力文とは、文字型変数から読み込んだり、 文字型変数に書き込んだりする機能です。 C の sprintf , sscanf に相当します。 例を示します。
内部入出力文により「文字列」と「値」の変換が可能になります。
C では sin , cos などの数学関数は、引数が double, 返り値も double です。関数プロトタイプ宣言されているので、如何なる型を 引数として入れても (double) 型に型キャストされます。
それに対して fortran では例えば、sin で 引数が 4 byte 実数型の場合は 「引数 4 byte 精度、返り値 4 byte 精度の sin」 引数が 8 byte 実数型の場合は 「引数 8 byte 精度、返り値 8 byte 精度の dsin」 が使われます。ですから、
のように、個別名を使う必要はありません。引数が如何なる場合でも
で大丈夫です。個別名 ( dexp, cexp など ) を使うと名前を間違って しまう可能性があります。
また、引数として定数を使う時は、倍精度の引数を入れないと 精度が落ちてしまいます。
関数名は総称名を使うべきです。しかし、一見、 総称名のように見えますが、注意すべき関数があります。 cmplex*16 の実部をとるときは dble() を使い、 complex*16 の複素数に値を代入するときは dcmplx() を 使う必要があります。real() や cmplx() を使ってはいけません。
うっかり 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 を呼ぶので、上記の現象が起こります。ゆえに
のとき、f2c ではコケてしまうので上記のような書き方は やめた方が良いでしょう。
fortran の文字型変数は C とは考え方が異なります。 常に fortran 文字型変数は常に「文字数」という属性を 保持しています。 文字型変数に文字を代入する場合、以下のようになります。
文字列の比較は、短い方の文字列の右側に空白を付加して 同じ長さにしてから行われます。
例えば、文字長 80 文字の文字型変数と文字長 40 の文字型変数が 等しいかどうかを判別する場合、文字長 40 の文字型変数の 後ろに 40 個の空白が付け加えられた後、80 回の比較が行われます。 コンパイラの最適化にもよりますが、一般に、 文字型変数の比較は if ( i.eq.j ) と比べて遥かに時間がかかると 考えて下さい。
私の感覚では C よりも fortran の方が文字処理に強いように思われます。 以下に例を示します。
また、サブルーチンに文字型変数を渡す時は、 文字型変数の長さ ( character*80 moji なら 80 ) も 引数として渡されます。 次の例を見て下さい。
サブルーチンへ渡す引数として、文字型変数 moji の先頭アドレス と共に文字数 6 も渡されます。ゆえに、 len という組み込み関数を使うと、サブルーチン側で文字列の長さを 取得することができます。ここでの文字列の長さは character*6 で 指定された時の長さであることに注意して下さい。 'abc ' が入っているので 3 文字ではありません。
環境変数やコマンドラインの引数を取得することが出来ます。 g77 では以下のように iargc, getarg, getenv が使えます。
gfortran では iargc, getarg は使えず、その代わりに command_argument_count, get_command_argument があるそうですが、私は gfortran を使ったことが ないので、詳細は分かりません。
C でプログラムを組む場合、標準出力 stdout と標準エラー出力 stderr の使い分けは必須です。fortran でも標準出力と標準エラー出力を 使うことが出来ます。FreeBSD, Linux での f77 や g77, Sun の純正 コンパイラなど、Unix 上での fortran コンパイラでは write(6,*) が標準出力、write(0,*) が標準エラー出力です。
stop 文や end 文でプログラムを終了すると、終了コードは 0 となります。 エラーが発生したときに終了コードを 1 とするには、exit 関数を呼びます。
C には system 関数という便利な関数があります。 Fortran でも g77 では
あるいは
のように system 関数が用意されています。 また、Fortran から呼べる system 関数を自作することは 容易です。その方法は、 ここ で紹介しています。
Fortran でオブジェクト指向するには save 文と entry 文が不可欠です。 詳細は ここ に書いてあります。 これを活用すると、プログラムを組むのが非常に楽になることが 多いと思います。
Unix の場合は prof または gprof コマンドを使うと 各サブルーチンの処理時間の統計を 出すことが出来ます。 FreeBSD の場合、まず、コンパイルする時に
のように -pg オプションを付けてコンパイルし、 リンク時にも
のように -pg オプションを付けます。こうすると、 実行型ファイル exe を実行すると exe.gmon という ファイルが作成されます。
を実行すると各サブルーチンの所用時間の統計が出力されます。 このようにしてボトルネックとなっているサブルーチンを見つけ、 高速化出来ないかどうかを検討しましょう。
Unix の場合は gdb というデバッガが使えます。 gdb は C 言語用のデバッガなので、fortran から使う場合、かなり 機能が限定されてしまうようです。私も詳しくは知らないのですが、 次の使い方を知っておくだけで、かなり役にたちます。
まず、gdb を使うためには以下のように プログラムを -g オプションを付けてコンパイルしておく必要が あります。
プログラムが異常終了するときは大抵 core ファイルを作成 します。core ファイルはデフォルトで作成されるようになって いると思いますが、そうでない場合は以下のように再設定して下さい。
異常終了の原因を知りたい場合、次のような引数で gdb を 起動します。
そして
と打つと、どういうファイルの何行目でプログラムが異常終了 したかが表示されますので、そのファイルの該当箇所を調べます。 これで、分からない場合、
のように gdb の中から実行すると、より詳しい情報が得られる 場合があります。
また、gdb で fortran のプログラムをデバッグする場合、
と打つと良いようです。
本項の記述は 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 によって制御されます。
の場合、iteration count の最初の値は
となります。例えば、
の場合、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 の値は 11 になります。