あらきけいすけの雑記帳

2010-02-03 (Wed)

[] fortran 基礎文法最速マスター

基礎文法最速シリーズに便乗して*1書きかけですが上げます。fortranは書き方がフリーダム過ぎてまとめきれません。「基礎文法」なので「fortranで線形代数の計算の雛形が書ける」くらいの内容を目標にして書いてみました。説明した事項は「自力で書ける」「レガシーコードが少しは読める」くらいの内容を含んでいるつもりではあるのですが…。

fortranのインストールと簡単なプログラム作成

[2016.1.1]64bit の Windows10 上でフリーの fortran コンパイラを導入して、簡単なプログラムを作成する - あらきけいすけの雑記帳 Win7, 8でもOK

64bit の Windows 7 上でフリーの fortran コンパイラを導入して、簡単なプログラムを作成する - あらきけいすけの雑記帳

Windows上でフリーのfortranコンパイラを導入して、簡単なプログラムを作成する - あらきけいすけの雑記帳

参考にした文献

Compaq Visual Fortran 6.6 のドキュメント(ボクは手許に製本された製品を持っている)

ホームページ http://www.xlsoft.com/jp/products/intel/cvf/docs/ の中の

言語リファレンス http://www.xlsoft.com/jp/products/intel/cvf/docs/vf-html/lr/lr00.htm

多分、これが日本語で容易に無料で参照できる唯一の fortran の網羅的な文法書でしょう。無料で参照できるのはとてもありがたいことです。*2

fortran 95 の宣伝

  1. fortran 95 では関数を再帰的に呼び出せます。(でもこのまとめに出ません。ごめんなさい)
  2. fortran 95 ではポインタが一応あるので、連結リストが作れます。(でもこのまとめに出ません。ごめんなさい)*3
  3. fortran 95 の MODULE でオブジェクト指向プログラミングができます。(でもこのまとめに出ません。ごめんなさい)
  4. fortran 95 では演算子のオーバーロードができます。(でもこのまとめに出ません。ごめんなさい)
  5. fortran 95 では fortran のレガシーなコードも走ります。(このせいで fortran のソースコードの書き方がフリーダム過ぎになります。コンパイラ作る人は本当にえらいと思います。)

勉強のツボ

これはボクの個人的な偏見なのですが、新しい手続き型プログラミング言語を勉強するときは次の4個の項目を意識して勉強するといいと思います。

  1. 命令のパッケージの作り方
    C言語だと「関数」、fortran なら PROGRAM, SUBROUTINE, FUNCTION といったもの
  2. データの入れ物の作り方
    いわゆる変数の宣言
    1. 型の種類と変数宣言の書き方
      C言語だと int, float. fortran なら INTEGER(4), REAL(8)
    2. 配列の宣言の書き方
    3. スコープ
      どの関数からどのメモリのデータを読みに行けるかというルール
  3. 命令の流れの変え方
    いわゆる制御構造というもので、条件分岐、繰り返しの書き方
  4. 簡単なファイル入出力の仕方
    プログラムを走らせた結果の残し方という意味で。

この文書では

  1. [レガシー]と書くと「多分 fortran 77 までの書き方由来なんじゃないかな」という程度の意味の fortran 95 の書き方(コンパイラ通ります)
  2. [fortran 95]と書くと「fortran 90/95 以降の書き方なんだろうな」という書き方
  3. 固定形式、自由形式を守ってさえいれば、これらの書き方を混ぜてコードが書けます。

コードを書くときに利用する文字

  1. 半角文字を使え
  2. 大文字、小文字の区別が無い[レガシー, fortran 95 共通]
    REAL(8)::A,B,C
    a=b+c
    なんて書き方ができる。
  3. 白文字:変数名の中にすきまがあってもOK[ただし固定形式のみ]
    INTEGER ABC
    A B C = 5
    これを使うことは無いとは思いますが、レガシーでかなり異様な仕様だと思うのでメモ

ソースコードの書き方

  1. 自由形式[fortran 95]
    1. 半角文字で1行132文字まで
    2. それを越えたら継続行。行末にアンパサンド(&)を書くと次の行の1文字目から前行の文の続き。(詳しくは下を見てね)
    3. ファイル名は *.f90 にする(使っているコンパイラのマニュアル参照してね)
  2. 固定形式[レガシー, fortran 95]
    レガシーな書き方。fortran関連のマニュアルに掲載されているサンプルコードは大抵こっち。
    1. 半角文字で72文字*4
    2. そのうち最初の5文字は文番号用
    3. 6文字目は継続行用
    4. 結局7文字目から72文字目までの66文字分しか使えない
    5. それを越えたら継続行。次の行の6文字目に"0"ではない文字を書くと前行の文の続きになる。(詳しくは下を見てね)
    6. ファイル名は *.f (または *.for) にする(使っているコンパイラのマニュアル参照してね)
  3. 蛇足
    1. 自由形式と固定形式を同じファイルに書くことはできない。けど
      g95 hoge1.f90 hoge2.f
      のように別々に保存して1個の実行ファイル(executable)を作ることはできる。
    2. レガシーな書き方のサンプルコードでは制御構造にインデントを使う習慣が無い…場合結構ある。

コメント

  1. [fortran 95] ! (感嘆符)以降、改行までがコメント。これだけでOK
  2. [レガシー(固定形式)]1文字目に C (シー), * (アスタリスク) を書く

四則演算

  1. = (等号,代入演算) + (プラス,加法) - (ハイフン・マイナス,減法) * (アスタリスク1個,乗法) / (スラッシュ,除法) が普通に使えます
  2. ** (アスタリスク2個)は指数計算です。 2.1**1.5 は「2.1の1.5乗」を計算します。
  3. ( ) (丸括弧・パーレン)も優先的な演算の意味で普通に使えます
  4. 整数型の演算では商が整数値になります。例えば 7/3 は 2 を返します。剰余の演算子はないので剰余の答えが欲しいときは a-a/3*3 と書いてください。

文(ひとつひとつの命令)

  1. 1行に1文が原則
    1. これは「改行」が文の終了の区切り子という意味
    2. [fortran 95] ; (セミコロン)を文の終了の区切り子に使ってもよい。
      DO j=1,10; DO i=1,10;
      a(i,j)=i+10*j;
      END DO; END DO;
      …みたいな書き方も可
  2. 長い文は継続行で延長
    1. 自由形式の場合:次に続けたい行の行末に&(アンパサンド)を書く
      b(i,j)= a(i-1,j-1) - a(i,j-1) - a(i+1,j-1) &
      - a(i-1,j ) + a(i,j)*8 - a(i+1,j ) &
      - a(i-1,j+1) + a(i,j+1) - a(i+1,j+1)
      39行続けられる。
    2. 固定形式の場合:続きの行の6文字目にゼロか空白でない何かを書く
            b(i,j)= a(i-1,j-1) - a(i,j-1) - a(i+1,j-1)
      & - a(i-1,j ) + a(i,j)*8 - a(i+1,j )
      & - a(i-1,j+1) + a(i,j+1) - a(i+1,j+1)
      fortran 95 の固定形式では19行続けられる。

変数(データのいれもの):型、宣言

  1. 名前の長さ:最長31文字[fortran 95]
    [蛇足]レガシーな書き方のコードではしばしば手続名、変数名を6文字以内に押さえ込む。
  2. 暗黙の型宣言
    これは蛇蝎のごとく嫌われているデフォルトの仕様
    1. 暗黙の型宣言がある
      fortranのコードは変数宣言を書かなければ, a,b,..,h,o,p,...,z で始まる変数は自動的に REAL 型(単精度実数型, Cの float に相当), i, j, k, l, m, n は INTEGER 型と決まっている。これは関数プログラムの戻り値にもあてはまる。

      fortranのマニュアルでソースの一部が示されているとき、その中の変数の型は暗黙の型宣言に従っていると想定して読まなくてはならない場合がある。

    2. 暗黙の型宣言を作れる
      IMPLICIT 文というおちゃめな命令文がある。例えば
      IMPLICIT DOUBLE PRECISION (a-h,o-z)
      ルーチンの冒頭に書くと a,b,..,h,o,p,...,z で始まる変数は自動的に DOUBLE PRECISION 型(倍精度実数型, C の double に相当)になる。
    3. 暗黙の型宣言を殺せる
      IMPLICIT NONE
      と書いておくと暗黙の型宣言を利用できなくなります。これを書いたら利用する変数はすべて宣言しないといけません。
    4. 暗黙の型宣言を無視できる
      IMPLICITを生かしたままでも変数宣言をすればそちらが優先。
  3. fortranでよく利用する型[fortran 95]
    型名内容
    INTEGER(N)整数型
    REAL(N)実数型
    COMPLEX(N)複素数型, REAL(N)×2
    丸括弧の中の N には変数1個のバイト長(4とか8とかサポートされている数値)を書く。倍精度の複素数は COMPLEX(8)
  4. fortranでよく利用する型[レガシー]
    型名fortran 95内容
    INTEGERINTEGER(4)整数型, C の int
    REALREAL(4)単精度実数型, C の float
    DOUBLE PRECISIONREAL(8)倍精度実数型, C の double
    COMPLEXCOMPLEX(4)複素数型, 単精度実数型×2
  5. 変数の宣言[fortran 95]
    型名のあとにコロンを2個を入れて変数名を書く
    REAL(8)::A,B,C
  6. 変数の宣言[レガシー]
    型名のあとに空白を入れて変数名を書く
    DOUBLE PRECISION A,B,C

変数(データの入れ物):配列

  1. DIMENSIONを使う
    1. [レガシー]変数の名前だけ先に宣言して、配列長をDIMENSION文で決める。
      DOUBLE PRECISION A
      DIMENSION A(3,4,5)
    2. [fortran 95]変数の型宣言の限定子(DIMENSION属性)として使う
      REAL(8),DIMENSION(3,4,5)::A
  2. 変数名の後に配列長を書く[レガシー?]
    DOUBLE PRECISION A(3,4,5)
  3. 配列の添え字はデフォルトでは1から始まる
    REAL(8),DIMENSION(3)::A ならば A(1), A(2), A(3)
  4. 配列の添え字の範囲自分で決めることができる
    添え字の範囲の書き方は [開始]:[終了] (コロン1個が区切り子)
    REAL(8),DIMENSION(0:3)::A ならば A(0), A(1), A(2), A(3)

プログラム

手続き型のプログラミング言語ではプログラム構成単位の書式は

 プログラム構成単位のインターフェース
 プログラムの本体
になっています。例えばCでは
 int main (void)    プログラム構成単位のインターフェース
{ printf("hello"); } プログラムの本体(いわゆる「複文」ですね)
です。Cではこの書式は「関数」として統一されていますが、fortranではいくつかのカテゴリに分かれています。

  1. 主プログラム
  2. 外部手続
    1. 関数
    2. サブルーチン
    3. 文関数
  3. 組込み手続
    SIN(), COS()のようなあらかじめ準備されている関数などです。
  4. モジュール手続
    「初級最速」なので書きません。fortran 90/95 の一番おいしい機能なんですけど。ちなみにモジュールの中には変数の宣言、関数、サブルーチンなどが書けます。C++の「クラス」のようなものと考えてください。

主プログラム

プログラムの実行で一番最初に動くもの(program startup, エントリポイント)です。

  1. 正式な書式
    [PROGRAM 名前]
      [宣言部]
      [実行部]
    [CONTAINS
      内部手続部]
    END [PROGRAM [名前] ]
    [ ] の部分は無くてもいい部分。
  2. サンプル:Hello world コード
    PROGRAM HELLO_WORLD
    WRITE(*,*)"Hello, world!"
    END PROGRAM HELLO_WORLD
  3. サンプル:最小限の Hello world コード
    WRITE(*,*)"Hello, world!"
    END
  4. サンプル:ムダにCONTAINSを使った Hello world コード
    PROGRAM HELLO_WORLD
    CALL HELLO
    CONTAINS
    SUBROUTINE HELLO
    WRITE(*,*)"Hello, world!"
    END SUBROUTINE HELLO
    END PROGRAM HELLO_WORLD

関数

計算をやらせるための命令パッケージ

  1. 正式な書式
    [prefix] FUNCTION 名前 ([仮引数リスト]) [RESULT (名前)]
      [宣言部]
      [実行部]
    END [FUNCTION [名前] ]
    [ ] の部分は無くてもいい部分。
  2. 定義と呼出のサンプルコード
    FUNCTION FADD(X,Y) ! 関数の定義のサンプル
    REAL(8)::X,Y,FADD ! 関数名も型宣言は必要
    FADD=X+Y ! デフォルトでは関数名が戻り値の変数名である
    END

    PROGRAM HELLO_WORLD ! 関数の呼出のサンプル
    REAL(8)::A,B,C,FADD ! 呼出側でも戻り値の型宣言が必要
    A=1
    B=1
    C=FADD(A,B) ! 関数の呼出しは関数名と実引数
    WRITE(*,*)A,B,C
    END
  3. サンプルコード2(呼出側のPROGRAMは上と同じもので動く)
    REAL(8) FUNCTION FADD(X,Y) RESULT(Z)
    ! 結果を変数 Z に代入する書き方
    ! これでも呼出側には REAL(8)::FADD が必要
    REAL(8)::X,Y
    Z=X+Y
    END FUNCTION FADD
  4. [使用上の重要な注意] fortran の引数はいわゆる「参照渡し(変数のアドレスの値を関数に与えること)」なので、「子」の側で変数の書き換えができてしまいます。
    FUNCTION FADD(X,Y)
    REAL(8)::X,Y,FADD
    X=X+1 ! 書き換えをやってしまっている
    FADD=X+Y
    END
    これを防衛するために fortran 95 には宣言文にINTENT属性を書くという仕様が備わっています
    FUNCTION FADD(X,Y)
    REAL(8),INTENT(IN)::X,Y ! これを書くとコンパイラがエラーを出してくれる
    REAL(8)::FADD
    X=X+1 ! 書き換えをやってしまっている
    FADD=X+Y
    END

サブルーチン

何かをやらせるための命令のパッケージ

  1. 正式な書式
    [prefix] SUBROUTINE 名前 [([仮引数リスト])]
      [宣言部]
      [実行部]
    END [SUBROUTINE [名前] ]
    [ ] の部分は無くてもいい部分。
  2. 呼出はCALL文を用いる。引数が無い場合には実引数を囲む ( ) は書かなくてもよい。
  3. サンプルコード。
    SUBROUTINE SADD (X,Y,Z) ! サブルーチンの定義のサンプル
    REAL(8)::X,Y,Z
    Z=X+Y
    END

    PROGRAM HELLO_WORLD ! サブルーチンの呼出のサンプル
    REAL(8)::A,B,C
    A=1
    B=1
    CALL SADD(A,B,C) ! サブルーチンを呼出し
    WRITE(*,*)A,B,C
    END
    fortran の引数はいわゆる「参照渡し」です。このコードでは変数Cの書き換えが起こっています。そしてサブルーチンとはそういう使い方をするものです(きりっ。[fortran 95]潰したくない引数には宣言文に INTENT(IN) 属性を書き加えます。
  4. 引数を持たない場合
    SUBROUTINE HELLO ! サブルーチンの定義のサンプル
    WRITE(*,*)"hello, world!"
    END

    PROGRAM HELLO_WORLD ! サブルーチンの呼出のサンプル
    CALL HELLO ! サブルーチンを呼出し
    END

制御構造

  1. 条件による分岐には IF...THEN 文を使います
    IF (condition) THEN
    [TODO]
    END IF
  2. 条件判定のための論理式の主なものです。
    fortran 95レガシー内容
    a == b A .EQ. B 等しい
    a /= b A .NE. B 等しくない
    a < b A .LT. B 小なり
    a <= b A .LE. B 等しいまたは小なり
    a > b A .GT. B 大なり
    a >= b A .GE. B 等しいまたは大なり
    A .AND. B AかつB 論理積
    A .OR. B AかつB 論理積
    .NOT. B Bの否定(単項演算)
  3. インクリメントによる反復にはDO文を使います。
    [fotran 95]DO 文で始まるブロックの終了には END DO 文を使います。
    fortran 95Cのfor文との比較
    DO I=1,10
    [TODO]
    END DO
    for(i=1;i<=10;i++){
    [TODO]
    }
    でもC言語のfor文のようなアナーキーなことはできません
    int a,b; // これはC文法的に許されるよ
    for ( a=0, b=0; a<10; printf("a=%d b=%d\n",a,b+=++a) );
  4. [fortran 95]インクリメントの変数は整数型です。
    インクリメントの間隔のデフォルト値は1です。インクリメント幅の値は指定することができます。
    DO I=1,10,2 ! この例では1,3,5,7,9と変化します
    [TODO]
    END DO
  5. DOブロックのネスト(入れ子)ができます。ネストはちゃんと対応する END DO 文を書かねばなりません。
    DO J=1,10
    DO I=1,10
    A(I,J)=I+10*J
    END DO
    END DO
  6. [レガシー]DOブロックの終了を文番号で指定することもできます。
          DO 10 I=1,10
    A=A+I
    10 CONTINUE
    ブロックの終了にはCONTINUE文を使うことが多いのですが、普通の文を指定することもできます(古い教科書のサンプルコードで見かけます)
          DO 10 I=1,10
    10 A=A+I
    ネストしたDOブロックの終了を一つの行で済ませることもあります。
          DO 10 J=1,10
    DO 10 I=1,10
    A=A+I+10*J
    10 CONTINUE
    文番号でブロックを指定するときはきちんとネストさせないといけません
          DO 10 J=1,10
    DO 20 I=1,10
    A=A+I+10*J
    10 CONTINUE ! これは文法的にダメ
    20 CONTINUE ! "10"行と"20"行が逆
  7. 条件による反復 while も DO 文で書きます
    DO WHILE (condition)
    [TODO]
    END DO
  8. サンプルコード: IF...THEN...ELSE IF...ELSE...END IF, DO WHILE (...)...END DO のサンプルです。実際にきちんと走るバビロニア式アルゴリズムで平方根を計算するFUNCTIONです。
    REAL(8) FUNCTION babylonianSQRT(x) RESULT(z)
    ! バビロニア式アルゴリズムによる平方根の計算(負の値が代入されると-1を返す)
    REAL(8),INTENT(IN)::x
    REAL(8),PARAMETER::EPS=1.D-16
    REAL(8)::y,yp,er
    IF ( x < 0 ) THEN
    z= -1;
    ELSE IF ( x == 0 ) THEN
    z= 0;
    ELSE
    y= 1.D+0
    er= EPS*2
    DO WHILE ( er > EPS )
    yp= y
    y= .5D+0*(y+x/y)
    er= ABS(y-yp)/y
    END DO
    z=y
    END IF
    END

ファイル入出力の最低限の知識

まずは最小限のサンプルコード。

  1. 倍精度実数1個が入ったデータファイルを作成する例
    まずはテキストファイルに出力する例
    REAL(8)::A=1.0
    WRITE (1,*) A
    ! | |
    ! | 書式[必須]: * は「デフォルトの書式」の意味
    ! 装置番号[必須]: 5(標準入力),6(標準出力)ではない正の整数値
    これを実行するとfort.1というファイル名のファイルに(もし無ければそれを勝手に作って)テキスト形式で書き出します(ファイル名はコンパイラのマニュアル見てね。それからファイル作成の権限がないとコケます、多分)。ファイル名を指定したい場合は OPEN 文, CLOSE 文を書きます
    OPEN(1,FILE="test.txt")
    ! | |
    ! | ファイル名[必須]
    ! 装置番号[必須]
    この間のどこかにWRITE文が入る
    CLOSE(1)
    ! |
    ! 装置番号[必須]
    次にバイナリファイル(書式なし順番WRITE文と言います)として作成する例
    REAL(8)::A=1.0
    WRITE (1) A
    ! |
    ! 装置番号[必須]: 5(標準入力),6(標準出力)ではない正の整数値
    これも fort.1 を勝手に作成します。さてちょっとビックリする話ですが、出来上がったファイルはサイズが16バイトです。というのも
    WRITE文が書き出すデータは [4バイトの記録長データ][データ本体][4バイトの何か記録長データ] なので「書き込んだバイナリデータのサイズ+8バイト×WRITE文の数」のファイルができます。
    ファイル名を指定するときのOPEN文の最小限の書式です
    OPEN(1,FILE='test.dat',FORM='UNFORMATTED')
    ! | | |
    ! | | 書式指定子[必須]
    ! | ファイル名[必須]
    ! 装置番号[必須]
    [WRITE文が続く]
    [CLOSE(1)文が続く]
  2. 倍精度実数1個が入ったファイルを読む例:
    REAL(8)::A
    OPEN(1,file="test.txt") ! テキストファイルを開く
    READ(1,*)A ! テキストで書かれたデータを読んで A に格納
    CLOSE(1)
    ファイル名が fort.1 の場合の最小限のコードは
    REAL(8)::A
    READ(1,*)A
    テキストファイルの読み込みではREAD文の2個目の引数の * (アスタリスク)は必須です。
    バイナリファイルで倍精度実数1個を読み込む場合の最小限は
    REAL(8)::A
    OPEN(1,file="test.dat",FORM='UNFORMATTED') ! バイナリファイルを開くとき
    READ(1)A
    CLOSE(1)
    READ文に2個目の引数が無いときは「バイナリのデータを前から順番に」読みに行きます。ファイル名が fort.1 の場合の最小限のコードは
    REAL(8)::A
    READ(1)A
  3. ごめん、書きかけ

変更履歴

2010.2.8: 最小限の WRITE, READ を書く

2010.2.4: ファイルI/Oを書き始めてみた。他にもちょこっと書き加え。

2010.2.3: とにかく上げてみた


*1(基礎|変態)文法最速マスターシリーズのまとめ - gifnksmの雑多なメモ

*2:これは個人的感想ですが、書店等で比較的容易に入手できる fortran 本は『fortran入門』タイプの本ばかりで、まとまった「ルールブック」的な書籍(Cならば C99 http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf, S・P・ハービソン3世とG・L・スティール・ジュニアのCリファレンスマニュアル みたいなヘビー級のリファレンス)があまり見当たりません。ビギナー向け文献とfortranで研究をするのに必要なリファレンスにギャップがありすぎます。

*3Welcome to NCI - National Computational Infrastructure

*4多分、パンチカードによるプログラムの入力の名残なのでしょう。

リンク元