3. 変数・データ型・基本的な計算
ここでは計算機でデータを扱うには必須となる変数とデータ型および基本的な計算の仕方について学ぼう.
参考
sample1.f90 : 変数の基本
sample2.f90 : データ型と精度
sample3.f90 : 定数
sample4.f90 :
read
の使い方sample5.f90 : 算術演算,代入,組込み関数
sample6.f90 : 型変換
3.1. 変数
プログラム中で何らかの値を保持するためには変数を用いる必要がある.例えば一度には出来ないような複雑な計算の途中結果などは変数に格納することになる.要するにデータの入れ物である.変数を用いるにあたって,
変数は宣言しなければならないこと
変数には型があること
という2点に注意しなければならない.変数の宣言は
1データ型 :: 変数名
のような形で行う.(ここで" ::
"は必須ではないが,宣言と同時に初期化をしたり,後で出てくる変数の属性を指定する時には必要となる.従ってこの演習では変数宣言時には常に" ::
"を用いることにする.)
例えば以下のコードは n
と x
という2つの変数を宣言し,それぞれ値を代入している.ここで =
は数学で用いる記号とは異なり,=
の左側の変数に右側の値を代入する(データを格納する)という意味である.
1program sample
2 implicit none ! 暗黙の型宣言禁止
3
4 ! 変数を使う前には必ず以下のように宣言を行う
5 integer :: n ! 整数型の変数の宣言
6 real :: x ! 実数型の変数の宣言
7
8 ! 代入
9 n = 10
10 x = 3.14
11
12 ! 表示
13 write(*, *) 'integer => ', n
14 write(*, *) 'real => ', x
15
16 stop
17endprogram sample
上の例では n
は整数( integer
)を,x
は実数( real
)を表す変数であるが,これはそれぞれ5行目や6行目のように変数を宣言をした時点で確定する.面倒なように思われるかもしれないが,変数は使う前に用途に合わせて 宣言しなければならない.これはすぐ後に述べるように計算機が表現できる値に限界があり,また人間のように臨機応変に状況に対処できないからである.
なおFortranには暗黙の型宣言という悪しき慣習があり,宣言されていない変数でも変数の名前に応じて自動的に型を仮定して宣言されたものとみなす.詳細は省くがこれは明らかにバグの元であり,この機能は使わないことを強く推奨する.先ほどの例では program
文の直後(2行目)の
1implicit none
が暗黙の型宣言の禁止を意味する.このときは全ての変数を明示的に宣言しなければコンパイルエラーとなる.以降,本演習では必ず implicit none
を使うこととする [1].なおgfortranでは -fimplicit-none
というオプションを用いると,デフォルトで implicit none
を指定した状態にすることが出来る.
3.2. データ型と精度
標準のFortranで用いることができるデータ型として以下のようなものがある.
型名 |
キーワード |
用途 |
---|---|---|
整数型 |
|
整数(厳密な表現) |
実数型 |
|
実数(近似的な表現) |
複素数型 |
|
複素数(実部と虚部を表す2つの実数型の組み合わせ) |
文字型 |
|
文字を表す |
論理型 |
|
真偽値( |
注意しなければならないのは,特に実数型の real
(従って当然 complex
も)はあくまで実数の近似表現であるという点である.例えば 1.0
を代入したとしても,これが厳密に1を表しているわけではない.これは10進数を2進数で無理やり表そうとするために起きる問題であり,回避する手段は無い.そうは言っても多くの場合において十分な精度で実数を近似できているので問題が無いのである.これとは対照に,整数型 integer
は厳密に整数を表現することが出来る.
しかしながら整数型にしても実数型にしても,どんな値でも表現できるというわけではない.具体的には各データ型に何バイト [2] の領域を持たせるかによって表現できる値の範囲が変わる.Fortranでは確保する領域の大きさを変数の宣言時に明示的に指定することが出来る.すなわち
4 ! 整数型
5 integer(kind=4) :: i4 ! 4バイト(32ビット)の整数型
6 integer(kind=8) :: i8 ! 8バイト(64ビット)の整数型
7
8 ! 実数型
9 real(kind=4) :: r4 ! 4バイト(32ビット)の実数型(単精度)
10 real(kind=8) :: r8 ! 8バイト(64ビット)の実数型(倍精度)
11 real(kind=16) :: r16 ! 16バイト(128ビット)の実数型(4倍精度)
12
13 ! 複素数型(実数2つ分の領域が必要になる)
14 complex(kind=4) :: c4 ! 8バイト(64ビット)の複素数型 = 単精度
15 complex(kind=8) :: c8 ! 16バイト(128ビット)の複素数型 = 倍精度
のように型名の後に ()
でデータ領域の大きさを指定できる.これを kind
パラメータと呼ぶ.なお,real(8)
のように kind=
は省略して構わない.( kind
パラメータを用いると移植性の高いプログラムを作成することが出来るが,これは本演習の守備範囲を超えるので以降では kind=
は省略することとする.) ちなみに特に何も指定しない場合は integer
が4バイト,real
が4バイトとなっていることが多いが,これは処理系依存である.処理系依存などの細かいことはとりあえず忘れると,結果的に表すことのできる値の範囲は以下のようになっていると思えばよい.(実数の値の範囲については 実数の精度と誤差 でもう少し細かく説明する.)
型名 |
最小値 |
最大値 |
備考 |
---|---|---|---|
|
\(-2^{15}\) |
\(2^{15}-1\) |
|
|
\(-2^{31}\) |
\(2^{31}-1\) |
|
|
\(-2^{63}\) |
\(2^{63}-1\) |
|
|
\(\sim 10^{-38}\) |
\(\sim 10^{+38}\) |
値は絶対値, 精度は約7桁 |
|
\(\sim 10^{-308}\) |
\(\sim 10^{+308}\) |
値は絶対値, 精度は約16桁 |
なお real(4)
を単精度,real(8)
を倍精度,real(16)
を4倍精度と呼ぶのが通例である.特に実数型については,単精度の約7桁という精度では心もとないので現在では倍精度を用いるのが一般的である.本演習では特段の理由がない限り real(8)
,complex(8)
を用いる( complex(8)
では実部と虚部がそれぞれ real(8)
となる).
なお複素数型 complex
の定数は (実部,虚部)
という形で表す.例えば
43 c4 = (2.71, 0.99)
44 write(*, *) c4
は倍精度複素数型の変数 c4
に \(2.71 + 0.99 i\) を代入している.
また文字型( character
)では通常 kind=1
なので [3],kind
パラメータを指定する必要がない.character
で複数の文字(文字列)を表すには以下のように len=
で文字数を指定することになる.(この場合は len=
を省略することも出来る.)
20 ! 文字列型は少し特殊で通常はkind=1である。文字列の長さはlen=で指定する
21 character(len=256) :: char ! 256文字分
論理型( logical
)は真偽値を表すために用いるので,通常は kind
パラメータは指定する必要は無い.
3.3. 定数
数値などを直接ソースコードに記述するとそれは定数(定数リテラル)と呼ばれる.例えば 99
や 1.5
などのような表現である.定数に _4
や _8
などを付けることによって kind
パラメータを指定することも出来る.先ほどの例では 99_4
,1.5_8
などのように書くことが出来る.論理型の定数は .true.
もしくは .false.
のどちらかである.文字型の定数は既に最初のサンプルで見たように '
(シングルクォート)もしくは "
(ダブルクォート)で囲まれた文字列,例えば 'earth'
や "physics"
などである.
また parameter
属性を用いて変数のように名前付きの定数を使用することも可能である.
4 integer(4), parameter :: n = 8_4
5 real(8), parameter :: pi = 3.141592653589_8
6 integer(4) :: m
7 real(8) :: f, g
8
9 ! 普通の変数と同じように定数を参照出来る
10 m = n * 10
11 f = pi * 2
12 g = 3.0e+10_8
13
14 ! 次のような定数変数への代入を行うコードがあるとコンパイルエラーとなる
15 ! pi = 3.14
上の例では n
を4バイトの整数,pi
は8バイトの実数として,それぞれ値を指定している.これらの変数は parameter
が指定されているため定数として扱われ,プログラム中(15行目)で誤って pi = 3.14
などとして値を変更しようとするとコンパイルエラーとなる.プログラム中で絶対に変更されない値を扱う場合にはこのように名前付き定数として宣言しておくと値が変更される心配が無いので安心である.(信じられないかもしれないが,プログラムの規模が大きくなってくると,このようなミスによるバグに悩まされることがしばしば起こる.)
また実数で例えば \(3 \times 10^{10}\) を表現するには上の例の12行目のように 3.0e+10_8
のように書けば良い.ちなみにFortran 77の慣習では倍精度での定数値を表現するのに e
の代わりに d
を使っていたので,これを 3.0d+10
と書くと倍精度,すなわち 3.0e+10_8
と同じ意味となる.このように実数の定数に e
や d
を用いる表現は今でもかなり頻繁に見られるので知っておくと良い.
3.4. 標準入力から変数への値の代入
以下のように変数 x
を宣言しておいて read(*,*)
を用いると,プログラムの実行時にキーボードからの入力された内容を読み込み,変数(この場合は x
)に代入することが出来る.
1program sample
2 implicit none
3
4 real(8) :: x, y
5
6 write(*, *) 'Input two real numbers: '
7
8 ! 整数を読み込む
9 read(*, *) x, y
10
11 write(*, *) 'average = ',(x + y) / 2
12
13 stop
14endprogram sample
実行結果は以下のようになる.
1$ ./a.out
2 Input two real numbers:
32.0 # キーボード入力
43.0 # キーボード入力
5 average = 2.5000000000000000
ここで3行目と4行目はキーボードで入力した値である.この read(*,*)
の意味は後述するのでここでは再びオマジナイであると思っておこう.なお,read(*,*)
の場合も,write(*,*)
と同様に複数の変数を並べて指定することができる.
3.5. 算術演算
Fortranでは最も基本的な演算である四則演算およびべき乗を以下のように計算することが出来る.当然変数同士での演算も可能である.
13 write(*, *) 'addition => ', 2.0 + 3.0 ! 足し算
14 write(*, *) 'subtraction => ', 5.0 - 3.0 ! 引き算
15 write(*, *) 'multiplication => ', 2.0 * 3.0 ! 掛け算
16 write(*, *) 'division => ', 5.0 / 2.0 ! 割り算
17 write(*, *) 'power => ', 2.0**3.0 ! べき乗
演算実行の優先順位は べき乗 > 乗算 = 除算 > 加算,減算 の順となっているが,分かりにくい場合は,以下の例のように可読性のために ()
で明示的に演算の順番が分かるようにしておくと良い.
23 write(*, *) 'w/ parenthesis => ', 2.0 * (2.0 + 3.0) ! 括弧あり => 10.0
24 write(*, *) 'w/o parenthesis => ', 2.0 * 2.0 + 3.0 ! 括弧なし => 7.0
3.6. 代入
既に学んだように =
演算子を用いて左辺で指定する変数に値を代入することが出来る.この時,右辺には任意の演算を含んでも良い.例えば
29 a = 2.0 ! 変数aに代入
30 b = a + 3.0 ! 足し算(a + 3.0)の結果をbに代入
31 c = a - 1.0 ! 引き算 (a - 1.0) の結果をcに代入
32 d = a * b ! 掛け算(a * b)の結果をdに代入
33 e = a / b ! 割り算 (a / b)の結果edに代入
のようにすればよい.
3.7. 組込み関数
Fortranには標準で使える関数が多く用意されており,組込み関数と呼ばれる.関数というと数学の関数を思い浮かべるかもしれないが,必ずしも数学関数ばかりではない.関数というのは単に入力値を受け取り何らかの値を返す機能(function)のことである.例えば数学では \(f(x) = \sin(x)\) と書いた時には \(x\) という入力に対して \(\sin(x)\) という値を返すことを意味する.Fortranでも入力値 x
対して sin(x)
とすることで関数値を計算することが出来る.なお関数に渡すパラメータ(ここでは x
)のことを 引数 と呼び,関数が返す値のことを 返値 と呼ぶ.
41 !
42 ! 標準入力から値を読み込み変数xに代入
43 !
44 write(*, *)
45 write(*, *) 'Input a real number: '
46 read(*, *) x
47
48 !
49 ! 標準で様々な関数が用意されている
50 ! 以下はほんの一例
51 !
52 ! 平方根 => sqrt(x)
53 ! 絶対値 => abs(x)
54 ! 三角関数 => sin(x), cos(x), tan(x)
55 ! 指数関数 => exp(x)
56 ! 対数関数 => log(x), log10(x)
57 ! 双極関数 => sinh(x), cosh(x), tanh(x)
58 ! 逆三角関数 => asin(x), acos(x), atan(x)
59 !
60 write(*, *) sin(x) ! sin(x)を計算し表示
61 write(*, *) cos(x) ! cos(x)を計算し表示
この他にも様々な関数が用意されているので,必要に応じて調べて欲しい [4].なお,自分で独自の関数を定義して用いる方法は後に学ぶことになる.
3.8. 型変換
異なる型同士の演算を行う場合や,代入する際に左辺と右辺で型が異なる場合には より一般的な型へと変換された後に演算や代入が実行される.この機能は便利なようで時に注意が必要な場合がある.
例えば以下の例を考えよう.
17 ! 以下の代入文は意図した通りに動かない
18 ! 右辺は整数同士の演算なので0となり, それが代入時に実数型に変換される
19 z = 2 / 3
z = 0.666...
となるかと思いきや,実際には z = 0
となってしまう.これは左辺が整数同士の演算として行われるため( 2 / 3
は 0
)である.これを回避するには例えば z = 2.0_8 / 3
や z = 2 / 3.0_8
とすれば良い.どちらかが実数であればもう一方も実数に変換されてから計算されるので,演算結果も実数となる.ただし z = 2.0 / 3
のようにしてしまうと z
は倍精度( real(8)
)で宣言されているにも関わらず 2.0
は単精度の実数( real(4)
)と解釈され,右辺の計算結果も単精度実数となる.これが左辺の z
に代入される時に倍精度( real(8)
)に変換されるため,結果的には精度が失われることになってしまう.
以下の組込み関数を用いて明示的に型変換を行うことも出来る.例えば,real(1, kind=8)
によって整数 1
が倍精度実数の 1.0_8
に変換される.ここでも2番目の引数を指定し忘れると精度が失われるので注意が必要である.ただし kind=
は省略可能であり,real(1, 8)
とするだけでも良い.
関数名 |
説明 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
型変換関数を使った最も丁寧なやり方は
30 ! 明示的に変換する
31 z = real(2, kind=8) / real(3, kind=8)
のようなものである.
3.9. 第3章 演習課題
3.9.1. 課題1
サンプルプログラムをコンパイル・実行して動作を確認せよ.さらに,適宜修正してその実行結果を確認せよ.
3.9.2. 課題2
標準入力から三角形の底辺 \(l\) と高さ \(h\) を読み込み,三角形の面積を表示するプログラムを作成せよ.
例えば以下のような実行結果が得られる.
1$ ./a.out
23.0 # キーボード入力
35.0 # キーボード入力
4 Area of triangle: 7.5000000000000000
3.9.3. 課題3
単精度実数型 real(4)
および倍精度実数型 real(8)
で,それぞれ \(\tan(\pi/4) = 1\) なる関係式を用いて \(\pi\) の値を求めて表示し,その値の精度を確認せよ.(組み込み関数 atan(x)
が数学の \(\tan^{-1}(x)\) に対応している.)
なお,精度を確認する際には4倍精度実数型 real(16)
でも同様に \(\pi\) の値を求め,これを正確な値(真値)とみなし,それとの相対誤差を確認すること.
ただし相対誤差は \(|1 - 近似値/真値|\) で評価せよ.(絶対値を返す関数 abs(x)
を用いよ.)
例えば以下のような結果が得られる.
1$ ./a.out
23.14159274101257324218750000000000000 2.78275351528562301296177532160895165E-0008
33.14159265358979311599796346854418516 3.89817183251937544615814478114900845E-0017
43.14159265358979323846264338327950280
結果は上から,単精度の \(\pi\) とその精度,倍精度の \(\pi\) とその精度, 4倍精度の \(\pi\) をそれぞれ表している.ただし細かい数値は結果は環境依存である.
(ここで,例えば 2.7E-0008
は \(2.7 \times 10^{-8}\) を表すのでとても小さい値であることを意味する.)
3.9.4. 課題4
標準入力から複素数 \(z (= x + i y)\) を読み込み, \(e^z\) および \(e^x \left( \cos y + i \sin y \right)\) をそれぞれ計算し,その結果が等しいことを確認せよ
(組み込み関数 exp(x)
および sin(x)
, cos(x)
を用いればよい).ただし倍精度の複素数型 complex(8)
を用いること.なお複素数 z
の実部は real(z)
,虚部は aimag(z)
という組み込み関数でそれぞれ求めることができる.またキーボードから複素数の入力は (実部, 虚部)
という形式となることに注意せよ.例えば \(z = 1 + i\) について \(\exp(z)\) を求めるには
1$ ./a.out
2(1.0, 1.0) # キーボード入力
3( 1.4686939399158851 , 2.2873552871788423 )
4( 1.4686939399158851 , 2.2873552871788423 )
のように (実部, 虚部)
という形式で入力すれば良い.
3.9.5. 課題5
キーボード入力で与えられた実数 \(x\) について,テイラー展開の公式
を適当な次数(例えば2次とか3次)で打ち切りることで \(\sin x\) の近似値を求め,組み込み関数 sin(x)
で求めた値と比較するプログラムを作成せよ.例えば \(x = 0.01,0.1,0.2\) などについて,実行して結果を確かめること.
例えば以下のような結果が得られればよい
1$ ./a.out
20.2 # キーボード入力
3 0.20000000000000001 # 1次近似
4 0.19866666666666669 # 3次近似
5 0.19866933333333336 # 5次近似
6 0.19866933079365082 # 7次近似
7 0.19866933079506122 # 組み込み関数