この文書の URL は http://www.cc.kyoto-su.ac.jp/~mtkg/lecture/comp_B/2012/06.html です。

内積の計算

キーボードから2つのベクトルを入力し、その内積を計算するプログラムを考えてみましょう。 あらかじめ大きい配列(要素数 100)を用意しておき、その一部を利用することによって、任意の要素数のベクトルの内積が計算できるようにします。

!
! 内積の計算
!
program work0601
  implicit none
  integer :: dim
  integer, parameter :: dimmax = 100
  real(8) :: v1(dimmax), v2(dimmax)

  write(*,*) 'Input dimension:'
  read(*,*) dim                        ! 次元数を入力

  write(*,*) 'Input vector 1:'
  read(*,*) v1(1:dim)

  write(*,*) 'Input vector 2:'
  read(*,*) v2(1:dim)

  write(*,*) 'v1 . v2 = ', dot_product(v1(1:dim), v2(1:dim))

  stop
end program work0601

キーボードからベクトルを入力するには各要素を空白で区切って与えます。

% ./a.out
 Input dimension:
3
 Input vector 1:
1 2 3
 Input vector 2:
3 4 5
 v1 . v2 =    26.000000000000000

(1, 2, 3) . (3, 4, 5) = 1*3 + 2*4 + 3*5 = 26 ということで、正しく計算できているようです。

組込み関数 dot_product

組込み関数 dot_product は2つの1次元配列 vector_a, vector_b を引数にとり、それらの内積を計算します。

dot_product(vector_a, vector_b)

ただし、引数の型によって次のような違いがあるので注意しましょう。

vector_a が整数型または実数型の配列の場合

sum(vector_a * vector_b) を返します。 この場合、 dot_product(v1(1:dim), v2(1:dim)) は次の do ループと等価です。

real(8) :: tmp
  :
tmp = 0
do i = 1, dim
   tmp = tmp + v1(i) * v2(i)
end do

vector_a が複素数型の配列の場合

sum(conjg(vector_a) * vector_b) を返します。 組込み関数 conjg は複素共役を返す関数です。

行列の和

次のような行列 A, B の和 C = A + B を考えてみましょう。

A = ( 1 2 3 ),   B = ( 4 6 2 )
    ( 4 5 6 )        ( 3 1 5 )

2行3列の行列は (2, 3) 配列によって表すことができます。 2次元配列を初期化するには data 文を使うのが簡単です。 要素を並べる順番に注意して下さい(次の「配列要素順序」を参照)。

program work0602
  implicit none
  integer :: ma(2, 3), mb(2, 3), mc(2, 3), i
  data ma / 1, 4, 2, 5, 3, 6 /
  data mb / 4, 3, 6, 1, 2, 5 /

  mc = ma + mb

  do i = 1, 2
     write(*,*) mc(i, 1:3)
  end do

  stop
end program work0602

練習問題

  1. mc = ma + 1 の結果を確認せよ。
  2. mc = ma - mb の結果を確認せよ。
  3. mc = ma * mb の結果を確認せよ。
クリックして表示

配列要素順序

配列の要素は「配列要素順序」 (array element order) という特別な順序で扱われることがあります。 ma(1:2, 1:3) という2次元配列では

ma(1, 1)
ma(2, 1)
ma(1, 2)
ma(2, 2)
ma(1, 3)
ma(2, 3)

が配列要素順序となります。 これは早い次元(左の添字)ほど速く変化するような順番 (column-major order) になっています。

Fortran の規格として定められているわけではありませんが、 多くの処理系ではこの順序で連続したメモリが配列に割り当てられます。 このため、左の添字に関するループを内側にすると、メモリアクセスが連続になり、高速な動作が期待できるといわれています。

data 文で配列を初期化する場合、要素の並べ方は配列要素順序となります。 例えば、配列 ma(1:2, 1:3)

A = ( 1 2 3 )
    ( 4 5 6 )

という行列として初期化するには、次のように要素を並べます。

integer :: ma(2, 3)
data ma / 1, 4, 2, 5, 3, 6 /

行列の積

次の例はキーボードまたはファイルから n 次元の行列 A とベクトル x を読み込み、 両者の積 y = A x を計算するプログラムです。 内積の例と同様、あらかじめ大きい配列(要素数 nmax=100)を用意しておき、その一部を利用することによって、任意の次元の計算ができるようになっています。

program work0604
  implicit none
  integer :: i, j, n
  integer, parameter :: nmax = 100
  real(8) :: a(nmax, nmax), x(nmax), y(nmax)

  write(*,*) 'Input dimension:'
  read(*,*) n

  write(*,*) 'Input a vector:'
  read(*,*) x(1:n)

  write(*,*) 'Input a matrix:'
  do i = 1, n
     read(*,*) a(i, 1:n)
  end do

  y(1:n) = matmul(a(1:n, 1:n), x(1:n))

  write(*,*) y(1:n)

  stop
end program work0604

ファイルからの入力

ベクトルや行列の要素ををキーボードから毎回入力するのは面倒です。 そこで、シェルのリダイレクト機能を利用して、ファイルから入力してみましょう。 以下のような内容のファイルを作成しておきます。 ファイル名は input.dat としましょう。

4
 1.0  -2.0   3.0   4.0
 2.0   1.0  -3.0   2.0
 3.0   4.0   5.0   6.0
-1.0   2.0   0.0   3.0
 2.0  -6.0  -7.0   5.0

各行の意味は次のようになります。

キーボードの代わりにファイルから入力するにはリダイレクト記号 < を使います。

% ./a.out < input.dat
 Input dimension:
 Input a vector:
 Input a matrix:
  -1.0000000000000000  34.000000000000000   7.0000000000000000   13.000000000000000

cat というプログラムを利用して、次のようにすることもできます。

% cat input.dat | ./a.out

組込み関数 mutmul(matrix_a, matrix_b)

組込み関数 mutmul を使うと行列と行列、行列と列ベクトル、行ベクトルと行列の積が計算できます。 matrix_a, matrix_b は2次元または1次元の配列で、2次元配列の場合は行列、1次元配列の場合はベクトルと解釈されます。 列ベクトルと行ベクトルはどちらも同じ1次元配列で表されるので、文脈に応じて適切に解釈しましょう。

書式

数値の全体の桁数や小数点以下の桁数といった出力形式のことを「書式」といいます。 Fortran では書式を詳しく指定することができます。

program work0605
  implicit none
  integer :: n
  real(8) :: x, y

  do n = 1, 5
     x = dble(n)
     y = sqrt(x)
     ! 書式を指定した write 文の例
     write(*,'(I5,5X,F15.5,5X,E15.7)') n, x, y
  end do

  stop
end program work0605

このプログラムを実行すると次のような出力が得られます。 (注意:1行目は桁数をみやすくするための目盛りで、プログラムの出力ではありません。)

12345678901234567890123456789012345678901234567890
% ./a.out
    1             1.00000       0.1000000E+01
    2             2.00000       0.1414214E+01
    3             3.00000       0.1732051E+01
    4             4.00000       0.2000000E+01
    5             5.00000       0.2236068E+01

書式は write 文の2つ目の要素に指定します。 上の例では次のような書式が使われています。

個々の数字の桁数などを指定する I5F15.5 などを「編集記述子」と呼びます。 書式は一般に

'(s1,s2,...,sn)'

と書きます。 ここで s1, s2, ..., sn は個々の編集記述子です。

編集記述子

編集記述子には非常に多くの種類があります。 ここでは代表的なものだけを示します。

整数型の編集記述子

整数型データの書式指定には Iw または Iw.m 形の編集記述子を用います。 Iw は整数を w 桁に出力することを表します。 Iw.m も同様ですが、符号なしの整数が少なくとも m 桁になるように 0 が付加されます。 したがって w >= m でなければなりません。

整数値     編集記述子          出力欄
-----------------------------------------------------------------
 12345     I5                  12345
-12345     I5                  *****
 12345     I10                      12345
 12345     I10.6                   012345
-12345     I10.7                 -0012345

指定した書式で出力できない場合は * が出力されます。

実数型の編集記述子

実数型データの書式指定には Fw.d または Ew.d 形の編集記述子を用います。 w は欄の幅、 d は小数部の桁数を表します。 E 形の編集記述子は次のように実数を仮数部と指数部に分けて表示します(基数は 10 です)。

[±][0].x1x2...xdE±z1z2

例えば 0.12E+03 は 0.12×10^3 を表します。

実数値     編集記述子          出力欄
-----------------------------------------------------------------
 123.456   F7.2                 123.456
-123.456   F10.5               -123.456
 12.3456   F5.3                *****
0.012345   E12.5                0.12345e-01
-123.456   E12.5               -0.12346e+03
 1.23456   E5.2                *****

以下の点に注意しましょう。

A 形編集記述子

文字型データの書式指定には Aw 形編集記述子を用います。 w は欄の幅です。

文字列     編集記述子          出力欄
-----------------------------------------------------------------
'ABCDE'    A                   ABCDE
'ABCDE'    A7                    ABCDE
'ABCDE'    A3                  ABC

X 形編集記述子

X 形編集記述子は空白を意味します。 一般に nX (n は整数)という形式で用い、 出力においては n 個の空白の出力、 入力においては n 桁だけ読み飛ばすことを意味します。

反復可能編集記述子

I 形、F 形、E 形、X 形などの編集記述子は 10I5 のように左側に反復回数を指定することができます。

練習問題

次のプログラムの出力を確認してみましょう。

program work0606
  implicit none
  integer :: i

  write(*,'(4I10)') (i, i = 1, 10)

  stop
end program work0606

素数を求める

今後は素数を求めるプログラムを考えてみましょう。 素数は1と自分自身以外の約数を持たない正整数のことです。 整数 n が素数か判定するには、n 未満のすべての素数で n が割り切れるかチェックします。 割り切れなければ n は素数、割り切れれば合成数となります。 以下のプログラムではみつかった素数を prime(:) という配列に記憶し、チェックのために利用しています。 何をやっているか、がんばって解読してみましょう。

!-----------------------------------------------------------------
! 1000 以下の素数を求める
!-----------------------------------------------------------------
program work0607
  implicit none
  integer, parameter :: nmax = 1000
  integer :: np = 0             ! みつかった素数の個数
  integer :: prime(nmax)        ! 素数を記憶する配列(最大 nmax 個)
  integer :: n, i

  ! 2 は素数
  np = 1
  prime(1) = 2

  ! 3 以上 nmax 以下の奇数 n だけをチェックする。
  do n = 3, nmax, 2
     ! n がこれまでにみつかった素数で割り切れるか調べる。
     do i = 1, np
        if (mod(n, prime(i)) == 0) exit
     end do
     ! n がそれまでに見つかった素数で一度も割り切れなければ素数である。
     ! その場合は exit が実行されないので、do ループが終了した時点で i
     ! の値は np + 1 になる。したがって、次の if 文の中身は n が素数の
     ! ときだけ実行される。
     if (i > np) then
        np = np + 1             ! みつかった素数の個数を 1 増やす
        prime(np) = n           ! n を素数表に登録する
     end if
  end do

  ! 結果表示
  write(*,'(I4,X,A)') np, 'primes found.'
  write(*,'(10I5)') prime(1:np)

  stop
end program work0607

exit 文

prime(i) にはそれまでにみつかった素数が記憶されています。

書式の指定

write(*,'(10I5)') prime(1:np) では「各行に5桁の整数を10個ずつ出力する」という書式が指定されています。

本日の課題

1. 平均値

キーボードから5つの整数を読み込み、平均値を求めるプログラムを作成せよ。

クリックして表示

2. 平均値 (2)

キーボードから n 個の整数を読み込み、平均値を求めるプログラムを作成せよ。

クリックして表示

3. 平均値 (3)

ファイルから次のような形式のデータを読み込み、身長と体重の平均値を求めるプログラムを作成せよ。 1行目は人数、2行目以降が身長と体重の数値である。

5
155.8   40.8
178.3   67.1
167.2   50.6
162.8   45.7
170.9   63.9
クリックして表示

4. 平均と分散

キーボードから n 個の整数 x1, x2, ..., xn を読み込み、平均 xmean と分散 sigma を求めよ。 分散の2乗は次のように定義される。

            Σ (xi - xmean)**2
sigma**2 = --------------------     (総和 Σ は i = 1, 2, .., n について取る)
                    n
クリックして表示

チャレンジ問題

1. 覆面算

次の覆面算を解くプログラムを作成せよ。 5つの未知数バ、ナ、シ、モ、ンを do ループで回して総当たりでチェックすればよい。 覆面算を知らない人はこちらをチェック。 ちなみに、正解は 877+877=1754 である。

バナナ+バナナ=シナモン
クリックして表示

2. 公約数(ユークリッドの互除法)

ユークリッドの互除法を用いてキーボードから入力した2整数 a, b の最大公約数を求めるプログラムを作成せよ。 ユークリッドの互除法のアルゴリズムは次のようなものである。

step 4 → step 2 のループには繰り返し回数を指定しない do 文を用いる。

クリックして表示

3. 組み合わせ

n 個の整数 (1, 2, .., n) から r 個の整数を取り出す方法は nCr 通りある。 例えば、n = 4, r = 2 の場合は次の 6 通りである。

(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)

キーボードから n と r を入力し、すべての組み合わせを出力するプログラムを作成せよ。

クリックして表示