(目次は末尾 →。)
Fortran 2008 以降、書式仕様は実用上、すっかり変わりました。 でも地味な変化だったので、気が付いていない人が多いと想像します。 何がどう変わったか見ていきましょう。
まず、「書式仕様」とは何だったかを確認します。 入出力のときにファイル中のデータがどうなるか指示するあれです。 1954 年、最初の FORTRAN では
FORMAT(I4,F8.3)
のような見た目でした。
これで整数値 -4 と実数値 6.89 を出力すると、
結果は「 -4 6.890
」でした。
最初の 4 文字分に整数値を右詰めで、次の 8 文字に実数値を小数点以下 3 桁、右詰めで、
というのがこの書式仕様の意味です。
1990 年代以前の FORTRAN プログラムを扱う(扱った)人にはお馴染みのスタイルです。
新しいコードでは FORMAT 文は書きませんが、
構成要素の「I4
」「F8.3
」みたいなのは今も現役です。
FORTRAN 77 では書式仕様の指定が変更されて
WRITE(*,'(I4,F8.3)') I,X
のように WRITE 文や READ文に直接書くようになりました。 1980 年代に FORTRAN 77 処理系が普及。 それでもこの新しい書き方が世間一般のプログラマに普及したのは 1990 年代以降だと思います。 習慣を変えるというのはものすごく時間がかかるものなのです。
2020 年代に入った今、そろそろ Fortran 2008 が導入した機能を普及させる時期かなぁと思うわけです。
書式仕様の部品になる、1 つのデータ要素の文字列での表現を指示するのが「編集記述子」です。
内部表現のデータを外部表現の文字列に変換、
あるいは逆に文字列をデータ値に変換することを「編集」と言います。
「(I4,F8.3)
」みたいなのが「書式仕様」で、
「I4
」「F8.3
」それぞれが「編集記述子」です。
編集記述子の説明をするときは数値の部分を文字で置き換えて
「Fw.d 形編集記述子」などと呼んだり、
数値部分の詳細に関わらない話の場合は「F 形編集記述子」と呼びます。
編集記述子はデータの型(整数型、実数型、文字型、論理型)ごとに使えるものが決まっています。 一つの型で使う編集記述子もあれば、複数の型に使えるものもあります。 その他、データ値と直接は対応しない編集記述子もあります。 「n 文字進める」を意味する「nX 形編集記述子」 が代表例でしょうか。
次節からはおすすめの編集記述子を紹介していきます (これが 2021 年現在の世間の常識とずいぶん違います)。
Fortran 解説書には書式仕様の章があって、整数型なら I 形、実数型なら
E 形と F 形、文字型は A 形、論理型は
L 形、という具合にいろいろな編集記述子が説明されていました。
これが Fortran 2008 では編集記述子 G0
が導入されて、ほとんどの説明が不要になりました。
整数型の値を G0
で書くと、単に最小の文字数で出力されます。
つまり 10 進整数が普通に書かれます。
文字型の値を G0
で書くと、値の長さの文字数が出力されます。
Fortran では文字変数の長さを大きめに取っておいて、
末尾を空白で埋めることで文字データの長さを調節する習慣ですが、
G0
での出力では末尾の空白も出力されます。
末尾の空白の出力を避けるには組込み関数 TRIM
を使います。
論理型の値を G0
で書くと、T
または
F
の 1 字で出力されます。
G0
での出力まとめ。実数型の値を G0
で書くこともできます。
その場合の結果は処理系依存です。
ただし指数部なしで表現できる程度なら指数なしの小数(要するに
3.14
みたいな表記)で出力。
そうでない場合、仮数部の絶対値が 0.1 以上 1 未満の指数表記(例えば
0.314E+1
)で出力される、ということは決まっています。
残念ながらあまり見易い形式にはなりません。
編集記述子 G0
の利点は、何にしろ何かしら読める文字列が出力される点です。
以前の Fortran では
になるという欠点がありました。
出力結果が「変」というのは Fortran 用語ではありません (Fortran 規格からすればこの場合は単に「未定義」)。 人からすれば想像しにくい結果になるという意味です。 つまり、結果を見て何かがおかしいということはわかりますが、 どこに問題があるのかピンポイントするのが難しい。 出力項目と書式仕様内の編集記述子の対応がずれた場合に、どれが問題なのか探しにくい。
一方、「星で埋める」は Fortran 用語で、出力文字数に対して欄幅が足りない場合に、
出力欄の文字がすべて「*
」になることです。
この場合も出力に「失敗した」ことはわかりますが、
その値が読めないので、やはりどれがまずかったのか探すのが難しい。
G0
だとどんな値も読めるように出力してくれます
(ただし元々不正な値だと別ですが)。
出力結果に想定外の内容が含まれている場合に、何を間違えて書いたかを探す手掛かりになります。
コンピュータプログラミングで実数値を出力する際には書式の指定が必須です。 これには本質的な理由があって、 人がある実数値にどれだけの情報量を期待しているかがデータ値そのものには記録されていないためです。
例えば文字データだったら、普通は人が意味を見いだす文字数でデータを作成します。 意味のない文字をデータに含める必要はありません。 技術的な理由で人にとって意味のない部分をデータに含める場合、 同時に意味のある文字数を記録します (固定長レコードの集まりとしてデータを用意する場合ですね)。
つまり文字データは、目的に応じて要約したり一部を切り取ったりすることはあっても、 データとしてあえて用意したからには本質的に意味のない部分はありません。 整数値も同様で、目的に応じて近似値で扱うことはあっても、 整数値データとして用意したからにはすべての桁が意味を持ちます。
ところが実数値に意味のある桁が何桁含まれるかは、目的に応じて人が決めるものです。 数学的実数値の精度は無限です。 紙に書かれた実数値は必要な桁数だけ書くお約束があります。 一方でデータとしてコンピュータがもつ内部表現の実数値は、 コスト的に見合う表現可能な最大の桁数が一律に含まれます。 そのうち現在の目的に使っていない桁にはまったく無意味な値が入っていることになります。 そのため出力時に何桁必要なのかを人が指定する必要があるのです。
編集記述子 G0
で実数型の値を出力することはできますが、
ちょっと残念な結果になります。
例えば筆者の環境で「3.14」「6.02×1023」を G0
で書くと
「3.1400000000000001
」「0.60200000000000000E+24
」となって、
思っていたのとは違う結果になります。
これは、「何桁必要か」を処理系が知らない、という本質的な問題があるためです。
また、歴史的事情により G0
の指数付き出力は科学表記(仮数部絶対値が 10 未満)
ではなく、仮数部絶対値が 1 未満になってしまうという問題もあります。
実数型の出力には F0.d 形か ES0.dE0 形編集記述子を使います。
どちらも d には小数点以下の桁数が入ります。
F 形が指数なし表記、ES 形が科学表記です。
例えば上の例の数値をそれぞれ F0.2
、ES0.2E0
で書くと
「3.14
」「6.02E+23
」になって、望みの出力が得られます。
なお、F0.d 形編集記述子は Fortran 95 で導入されました。 ES0.dE0 形編集記述子は Fortran 2018 で導入された、最新機能です。 ES0.dE0 が導入される前は ESw.dEe 形編集記述子で e と w に適切な値を与える、というのがおすすめの編集記述子でした。 この例の場合は指数部の桁数 e が 2、欄幅 w は非負の数では (d+e+4)、 負の数では符号の分も入れて (d+e+5) とする所です。 値に応じて設定を変えるのも厄介なので、筆者は e はいつも 3(IEEE 倍精度実数の最大値)、w はいつも (d+9) としていました(区切りのために先行する空白を 1 つ含める)。
多くの Fortran の本では ES 形ではなく E 形の使用を解説しています。 E 形は科学表記ではなく、仮数部が 1 未満になる表記なのでおすすめできません。 Fortran 90 で ES 形が導入されて E 形は使う必要がなくなりました。 でも解説はなかなか更新されないようです。
Fortran 2008 以降の編集記述子についてこれまで書いてきましたが、 もう一つの重要な機能が Fortran 2008 で導入された「無限書式項目」です (unlimited format item、まだ定訳がありません)。
以前から編集記述子には書式反復数を組み合わせることができました。
例えば「6F8.3
」と書いたら F8.3
による出力を
6 回繰り返すという意味です。
最初の FORTRAN 以来の機能です。
これを「*(F8.3)
」と書いて、回数無限大の意味としたのが無限書式項目です。
無限と言っても、出力項目が尽きれば終わりです。
書式反復数を整数値でなく「*
」で書いたような形式ですが、
続く部分は必ず括弧でくくります。
同時に導入された編集記述子 G0
と組み合わせると
WRITE(*,"(*(G0))") "I, J = ", I, ", ", J
書式を明示的に指定しながら、書式の内容はあえて考える必要はない、 特に出力項目と編集記述子の対応を考える必要がない、といううれしい状態になります。
FORTRAN 77 で導入された並び出力
WRITE(*,*) "I, J =", I, ",", J
に似ていますが、並び出力の結果が処理系依存
(改行が任意、隣接する文字型間以外の区切りの空白数が 1 以上で任意)なのに対して、
「(*(G0))
」での出力は実数型の桁数、
指数のあるなし以外ははっきり決まっています。
特に並び出力では整数値の出力で余分な空白がたくさん入る(値にかかわらず固定幅の)
処理系があったのに対して、G0
では普通に最小限の桁数になります。
値を空白で区切る習慣なら
WRITE(*,"(*(G0,1X))") "I, J =", I, J
も便利です。
普段使いはこの 2 つ「(*(G0,1X))
」「(*(G0))
」
を目的に応じて使い分ければ他の編集記述子を考えなくても十分でしょう。
問題は G0
ではうまく出ない実数値の出力ですが、その解決編は次節。
前回までで、Fortran 2008 以降、普段使いの書式仕様は
「(*(G0,1X))
」「(*(G0))
」で十分という話をしました。
残る問題は G0
ではうまく書けない実数値の扱いです。
実数値の部分だけ F か ES 形を使う、ということも考えられます。
WRITE(*,"(G0,1X,F0.2)") "X =", X
しかし、これだと出力項目と編集記述子の対応を考えなくて済む、
という「(*(G0,1X))
」の大きなメリットを失うことになります。
解決策は実数値を文字型に変換する関数を用意することでしょう。筆者は
WRITE(*,"(*(G0,1X))") "X, Y =", F_(X,"F0.2"), F_(Y)
のように使う、関数 F_ を用意しています。
これは書式(ここでは F0.2
)を与えるとその書式で、
書式を省略すると前回使った書式で書くようにしてあります。
また、配列構成子と組み合わせても使えるようにしてあります。
WRITE(*,"(*(G0,1X))") "X, Y =", F_([ X, Y ])
書式指定と出力項目を別々に指定する古来のやり方から、 その後のオブジェクト指向的な諸言語に見られる、 出力項目の指定の中に個々の書式を埋め込む方式に表面上は似た形になります。
こういった関数を用意してしまえば、使うのは簡単です。 しかしこの関数の実装は意外とややこしいです。 Fortran の本でもなかなか解説されない最新知識の応用が必要になります。 別の機会にこの関数の実装をネタに新旧 Fortran の特徴を紹介していこうかと思っています。
ところで、次の図は 77 以降の歴代 Fortran でのデータ編集記述子の導入をまとめたものです。 並べてみると、案外、毎回拡充されてきていたのがわかります。
0
で書いています。