落とし穴#

Fortran の構文は一般的にシンプルで一貫性がありますが、多くの言語と同様に、いくつかの欠点があります。時にはレガシーな理由(Fortran 標準の進化は、現在も活発に使用されている膨大な量のレガシーコードのために、後方互換性を強く重視しています)のために、時には真の欠点(ある時点での不適切な選択、後方互換性を破ることなく修正することが難しい、不可能ではないにしても)のために、そして時には Fortran が C や C++ や Python などではないというだけの理由のためです。Fortran は独自のロジックを持っており、他の言語に慣れている開発者にとっては驚くべき機能がある場合があります。

すべてのコードスニペットは gfortran 13 でコンパイルされています。

暗黙的な型付け#

program foo
    integer :: nbofchildrenperwoman, nbofchildren, nbofwomen
    nbofwomen = 10
    nbofchildrenperwoman = 2
    nbofchildren = nbofwomen * nbofchildrenperwoman
    print*, "number of children:", nbofchildrem
end program

プログラムはコンパイルされ、実行結果は次のようになります

 number of children:           0

えっ… Fortran は 2 つの整数を乗算できないのでしょうか?もちろんそうではありません… ここでの問題は、印刷時の変数名のタイプミスです。nbofchildreM ではなく nbofchildreN です。しかし、なぜコンパイラはタイプミスを検出できなかったのでしょうか?それは、デフォルトで Fortran が暗黙的な型付けを使用しているためです。明示的に型付けされていない変数に遭遇した場合、コンパイラは名前の最初の文字に従って型を推測します。I、J、K、L、M、N で始まる変数名は INTEGER 型であり、その他すべては REAL 型です(そのため、「GODINTEGER として宣言されない限り REAL である」という古典的なジョークがあります)。

暗黙的な型付けは、明示的な型付けがなかった時代から Fortran と同じくらい古いものです。テストコードをすばやく記述するのに便利ですが、この方法は非常にエラーが発生しやすく、推奨されません。強く推奨される良いプラクティスは、すべてのプログラムユニット(メインプログラム、モジュール、スタンドアロンルーチン)の先頭で implicit none (Fortran 90 で導入) を記述して、暗黙的な型付けを常に無効にすることです。

program foo
implicit none
    integer :: nbofchildrenperwoman, nbofchildren, nbofwomen
    nbofwomen = 10
    nbofchildrenperwoman = 2
    nbofchildren = nbofwomen * nbofchildrenperwoman
    print*, "number of children:", nbofchildrem
end program

これでコンパイルに失敗し、タイプミスをすばやく修正できます

    7 |     print*, "number of children:", nbofchildrem
      |                                               1
Error: Symbol 'nbofchildrem' at (1) has no IMPLICIT type; did you mean 'nbofchildren'?

暗黙的な保存#

subroutine foo()
implicit none
    integer :: c=0

    c = c+1
    print*, c
end subroutine

program main
implicit none
    integer :: i

    do i = 1, 5
        call foo()
    end do
end program

C/C++ に慣れている人は、このプログラムが 5 回 1 を出力することを期待します。なぜなら、integer :: c=0 を、まるで

integer :: c
c = 0

しかしそうではありません。このプログラムは実際には次のようになります

1
2
3
4
5

integer :: c=0 は実際には 1 回限りのコンパイル時初期化であり、foo() の呼び出し間で変数を永続化します。実際には、次のものと同等です

integer, save :: c=0

save 属性は、変数を永続化するための C static 属性と同等であり、変数が初期化されている場合は暗黙です。これは、レガシー(および引き続き有効な)構文と比較して、近代化された構文(Fortran 90 で導入)です

integer c
data c /0/

古い Fortran 使いは、近代化された構文が、save が指定されていない場合でも、レガシー構文と同等であることを知っています。しかし、実際には、暗黙的な保存は、C のロジックに慣れている新参者にとっては誤解を招く可能性があります。そのため、save 属性を常に指定することが一般的に推奨されます

integer, save :: c=0   ! save could be omitted, but it's clearer with it

注: 派生型コンポーネントの初期化式は、まったく異なるケースです

type bar
    integer :: c = 0
end type

ここでは、c コンポーネントは、type(bar) 変数がインスタンス化されるたびにゼロに初期化されます(実行時初期化)。

浮動小数点リテラル定数#

次のコードスニペットは、倍精度定数 x を定義します(これはほとんどのシステムで IEEE754 64 ビット浮動小数点であり、15 の有効桁数があります)

program foo
implicit none
    integer, parameter :: dp = kind(0d0)
    real(kind=dp), parameter :: x = 9.3
    print*, precision(x), x
end program

出力は次のとおりです

          15   9.3000001907348633

したがって、x には期待どおり 15 の有効桁数がありますが、それでも出力された値は 8 桁目から間違っています。その理由は、浮動小数点リテラル定数には、暗黙的にデフォルトの実数型があり、通常は IEEE754 単精度浮動小数点(約 7 の有効桁数)であるためです。実数 \(9.3\) には正確な浮動小数点表現がないため、最初に単精度で 7 桁目まで近似され、x に割り当てられる前に倍精度にキャストされます。しかし、以前に失われた桁が回復されることは明らかではありません。

解決策は、定数の型を明示的に指定することです

real(kind=dp), parameter :: x = 9.3_dp

これで、出力は 15 桁目まで正しくなります

          15   9.3000000000000007     

浮動小数点リテラル定数(再び)#

次に、1/3(3 分の 1)である浮動小数点定数が必要だとします。次のように記述できます

program foo
implicit none
    integer, parameter :: dp = kind(0d0)
    real(dp), parameter :: onethird = 1_dp / 3_dp
    print*, onethird
end program

すると、出力は (!)

   0.0000000000000000     

その理由は、1_dp3_dp が、浮動小数点型を表すと想定される _dp 接尾辞にもかかわらず、整数リテラル定数であるためです。その結果、除算は整数除算となり、結果は 0 になります。ここでの落とし穴は、標準ではコンパイラが REAL 型と INTEGER 型に同一の種類値を使用できることです。たとえば、gfortran では、ほとんどのプラットフォームで値 \(8\) は、倍精度の種類と 64 ビット整数の種類の両方であるため、1_dp は完全に有効な整数定数になります。対照的に、NAG コンパイラはデフォルトで一意の種類値を使用するため、上記の例では 1_dp はコンパイルエラーを生成します。

浮動小数点定数を表す正しい方法は、常に小数点を含めることです

    real(dp), parameter :: onethird = 1.0_dp / 3.0_dp

すると、出力は次のようになります

  0.33333333333333331     

プリントの先頭スペース#

program foo
implicit none
    print*, "Hello world!"
end program

出力

% gfortran hello.f90 && ./a.out
 Hello world!

ソースコードの文字列には存在しない余分な先頭スペースに注意してください。歴史的に、最初の文字には初期のプリンター用のキャリッジコントロールコードが含まれており、それ自体は出力されませんでした。スペース " " は、プリンターにコンテンツを印刷する前に CR+LF シーケンスを実行するように指示しており、Fortran print* ステートメントによって自動的に先頭に追加されました。一部のコンパイラでは、現代の出力デバイスは制御文字を傍受も使用もしませんが、それでもそれを行います。この先頭の空白が問題になる場合(まれにありますが)、* の代わりに(これは「コンパイラに出力形式を決定させる」ことを意味します)明示的な形式をコーディングできます

    print "(A)", "Hello world!"

この場合、コンパイラは先頭スペースを付けなくなります

% gfortran hello.f90 && ./a.out
Hello world!

ファイル名拡張子#

上記の「Hello world」プログラムをソースファイル hello.f に配置すると仮定します。ほとんどのコンパイラは多くのコンパイルエラーを生成します

% gfortran hello.f
hello.f:1:1:

 program foo
 1
Error: Non-numeric character in statement label at (1)
hello.f:1:1:

 implicit none
 1
Error: Non-numeric character in statement label at (1)
hello.f:2:1:

     print*, "Hello world!"
     1
Error: Non-numeric character in statement label at (1)

...[truncated]

その理由は、.f拡張子が、パンチカードシステム用に設計された旧式の「固定ソース形式」のために、広く受け入れられた慣習によって「予約」されているためです。特に、1~6列目はラベル、継続文字、コメントのために予約されており、実際のステートメントと命令は7~72列目に入力する必要がありました。自由ソース形式は、固定ソース形式のすべての制限を取り除きますが、後者と共存するため、ほとんどのコンパイラが採用している慣習では、自由形式のソースにはデフォルトで.f90拡張子を使用します。これは一般的にコンパイラのスイッチで変更可能であり、Fortran Package Manager (fpm) の最新バージョンでは、拡張子に関係なく、すべてのソースが自由形式であるとデフォルトでみなされることに注意してください。

注: よくある誤解として、.f90ソースファイルは、標準の Fortran 90 リビジョンに限定されており、より新しいリビジョン (Fortran 95/2003/2008/2018) で導入された機能を含めることはできないというものがあります。これは間違いであり、完全に無関係です.f90が選択された唯一の理由は、自由形式が Fortran 90 リビジョンで導入されたためです。.f.f90のどちらのソースファイルも、標準の任意のリビジョンの機能を含むことができます。