今では Fortran はメジャーなプログラミング言語とは
言えませんが、数値計算の分野では、まだまだ現役の言語です。
複素数と整合配列を手軽に扱える言語は fortran しかありません。
Fortran90 も普及しつつあるようですが、過去の資産の活用、
FreeBSD や Linux で使えるコンパイラとなると、
fortran77 がまだまだ使われています。
今では書店に行ってもフォートランの本はほとんど置いておらず、
「本には載っていないけれども、実は基礎的なことがら」を知ることは
容易ではありません。そこで、このページでは Fortran77 で
プログラミングするにあたっての基礎的で実戦的な知識を
紹介致します。
以下では Fortran と表記した場合、Fortran77 を意味します。
また、実数型変数は real*8 であることを前提としています。
- 暗黙の型宣言を使わない方法
Fortran はデフォルトで変数の型宣言が不要です。i,j,k,l,m,n で
始まる変数は整数型、それ以外は実数型という暗黙の了解があります。
暗黙の型宣言を使いたくない場合は、サブルーチンの先頭で、
implicit none
と宣言すると、変数の宣言が必ず必要になります。
暗黙の型宣言を使用すると、変数の宣言を逐一行う
煩わしさがなくなりますが、以下のようなデメリットが
あります。
- 変数の名前をタイプミスすると、新しい変数と
みなされてしまいます。ftnchek などのツールを使うと
この類のミスは発見してくれますが、コンパイラが発見して
くれる方が楽です。また、後述する include 文を使う場合、
ftnchek の表示は非常に見づらくなります。
- 変数名として自由な名前を付けることが
できません。たとえば最大値を max という変数で表したいとき、
rmax や dmax などのような変数名を付ける必要があります。
変数のタイプミスを防ぐというメリットは非常に大きいので、
implicit none は使うべきであると思います。
ちょっとした使い捨てプログラムを
書くとき以外は暗黙の型宣言は使ってはいけません。
- 実数型は 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)
これは、後で混乱を招く可能性が高い危険な方法です。
- 変数名とサブルーチン名
Fortran77 の規格では変数名やサブルーチン名は 6 文字
以内となっていますが、
現在のほとんどの処理系では 6 文字以上の変数名やサブルーチン名
を許しています。
ですから、アンダーバーなども使い、
subroutine solve_eqn(matrix,vector,v_size,h_size)
のように、分かりやすいサブルーチン名や変数名をつけると良いでしょう。
- コメント文
第一桁が c ではじまる行はコメントであることは良く知られて
いますが、
a = b ! びっくりマークの後はコメント
* a = b ! 第一桁が * マークでもコメント
というコメントも使えます。f2c, g77 をはじめとして
私が使ったことのある全てのコンパイラでこの機能はサポート
されています。
- 定数は別ファイルで定義し、それをインクルードする
これを行わない場合、あらゆるファイルに定数が散りばめられる
ことになり、プログラムの保守が大変になります。定数はインクルード
ファイルで定義して、そのファイルをインクルードすべきです。
これを実現する方法として、次の 2 つの方法があります。
- cpp ( C のプリプロセサ ) を使い、#define , #include を使う
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 文を使う
定数はインクルードファイル中に parameter 文で記述し、
include 文を使ってインクルードファイルを取り込みます。
- プログラムは小文字で書く
小文字で書いた方が見やすいので小文字を使いましょう。また、
大文字はマクロ ( #define で定義する ) や parameter 文で定義する
定数にだけ使うと混乱が少なくて済みます。
- do 〜 end do は使った方が良い
do 〜 end do 構文は fortran77 規格には含まれていませんが、
FreeBSD, Linux 付属の f77, g77 や Sun 純正 fortran など、
ほぼ全てのコンパイラでサポートされているので
使った方が良いでしょう。文番号を無くすと、プログラム
を cut & paste で他のプログラムの一部として取り込む時に
文番号の衝突を避けることが出来ます。
- 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
- 比較をする演算子について
Fortran 77 では .eq. .gt. .lt. などが if 文の中で
使用されますが、FreeBSD, Linux の f77, g77 は
.eq. ==
.ne. /=
.lt. <
.le. <=
.gt. >
.ge. >=
という表記を認めています。しかし、
Sun の純正 fortran ではダメなようなので、これはお勧めできません。
使えるコンパイラもある、ということを知っておくにとどめましょう。
- 整合配列
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 次元配列
のサイズを決めることを整合配列といいます。数値計算
のプログラムはこの技法無しには書けないでしょう。
- 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
ということになります。
- 文字型変数に対する 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 に入ります。この違いを
使い分けられるようにしましょう。
- format 文は出来るだけ使わないようにしよう
format 文を使うと文番号を使う必要があります。
以下のようにすると format 文を使わずに済ませることが出来ます。
write(6,'(a,f8.5,a,i3.3)') 'a = ',a,' i = ',i
format 文を使わないことにより、プログラムの cut & paste の
ときに文番号の衝突を気にしなくて済みます。
なお、上の例の i3.3 は 3 桁で出力し、それに満たない場合は 0 を
補うと言う意味です。
- open 文実行時のエラー処理
「ファイルをオープンして読み込むが、そのファイルが
存在しない場合はエラーを出してストップする」という
処理は以下のように実現できます。
open(1,file='fname',status='old',err=990)
中略
990 ファイルが存在しない時の処理
- read 文実行時のエラー処理
Fortran の read 文はエラーチェックの機能も持っています。
数値を読み込むべき時に文字列があるとエラーを出してくれます。
ファイルの終わりまで行った場合もエラーになります。
エラーが発生した場合に飛ぶ文番号を指定することができるので、
この機能を使うと便利です。例えば、下のようなプログラムが
考えられます。
open(1,file="file-name")
read(1,*,end=980,err=990) a
中略
980 ファイルエンドに出会ったときの処理
goto xxx
990 エラーが発生した時の処理
goto yyy
なお、エラーが発生したときに飛ぶ文番号を指定しない状態で
エラーが発生すると、core をダンプしてプログラムは
ストップします。
- バイナリ入出力
変数のメモリイメージをそのまま入出力することが
できます。つまり、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
とせねばなりません。
- 内部入出力文を活用しよう
内部入出力文とは、文字型変数から読み込んだり、
文字型変数に書き込んだりする機能です。
C の sprintf , sscanf に相当します。
例を示します。
character*80 str
str = '12 34'
read(str,*) a,b a に 12 , b に 34 が入る
write(str,*) 'a = ',a
内部入出力文により「文字列」と「値」の変換が可能になります。
- 数学関数は総称名を使う。引数によりコンパイラが
自動判定する
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)
- 間違えやすい総称名
関数名は総称名を使うべきです。しかし、一見、
総称名のように見えますが、注意すべき関数があります。
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 ではコケてしまうので上記のような書き方は
やめた方が良いでしょう。
- 文字型変数
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 ) と比べて遥かに時間がかかると
考えて下さい。
- 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 文字ではありません。
- iargc getarg getenv も使える
環境変数やコマンドラインの引数を取得することが出来ます。
大抵の fortran でサポートされています。
integer iarg
character*80 str
iarg = iargc() ! 引数の個数 ( コマンド名は含まない )
call getarg(0,str) ! コマンド名
call getarg(1,str) ! 第 1 引数
call getenv('HOME',str)
- 標準出力と標準エラー出力を使い分けよう
C でプログラムを組む場合、標準出力 stdout と標準エラー出力 stderr
の使い分けは必須です。fortran でも標準出力と標準エラー出力を
使うことが出来ます。FreeBSD, Linux での f77 や g77, Sun の純正
コンパイラなど、Unix 上での fortran コンパイラでは
write(6,*) が標準出力、write(0,*) が標準エラー出力です。
- プログラム終了時の戻り値を指定する
stop 文や end 文でプログラムを終了すると、終了コードは 0 となります。
エラーが発生したときに終了コードを 1 とするには、exit 関数を呼びます。
call exit(1)
- call system('ls') として外部プログラムを実行する
C には system 関数という便利な関数があります。
Fortran でも g77 では
iret = system('ls')
あるいは
call system('ls')
のように system 関数が用意されています。
また、Fortran から呼べる system 関数を自作することは
容易です。その方法は、
ここ で紹介しています。
- save 文と entry 文を使ってオブジェクト指向しよう
Fortran でオブジェクト指向するには save 文と entry 文が不可欠です。
詳細は ここ に書いてあります。
これを活用すると、プログラムを組むのが非常に楽になることが
多いと思います。
- プログラムの高速化のテクニック
Unix の場合は prof または gprof コマンドを使うと
各サブルーチンの処理時間の統計を
出すことが出来ます。
FreeBSD の場合、まず、コンパイルする時に
% f77 -c -pg sample.f
のように -pg オプションを付けてコンパイルし、
リンク時にも
% f77 -pg -o exe sample.o
のように -pg オプションを付けます。こうすると、
実行型ファイル exe を実行すると exe.gmon という
ファイルが作成されます。
% gprof exe exe.gmon
を実行すると各サブルーチンの所用時間の統計が出力されます。
このようにしてボトルネックとなっているサブルーチンを見つけ、
高速化出来ないかどうかを検討しましょう。
- デバッグのテクニック
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
と打つと良いようです。
- 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 になります。