現在(&lastmod;)作成中です。 既に書いている内容も&color(#ff0000){大幅に変わる};のは間違いないので注意。 ------- #contents ------- * アンケート(その01): 前回の講義について [#x99181c7] #vote(簡単すぎた, 難しすぎた, ちょうどよかった) * アンケート(その02): C言語との比較による説明の仕方について [#bad5d153] #vote(Cとの比較で良い, CよりもJavaと比較して欲しい, 他の言語と比較はない方がわかりやすい) * 倍精度浮動小数点数の指定方法 [#b90f3b3c] ** kindパラメータ [#wbd486cb] 計算科学では実数の物理量を数値的に表現するのに単精度ではなく、 倍精度の浮動小数点数を使うのが普通である。 (単精度実数では精度が不十分なため。) まは、スーパーコンピュータは単精度浮動小数点ではなく、 倍精度浮動小数点数の演算(四則演算)を高速に処理できるよう設計されている。 従ってFortran90/95言語での倍精度浮動小数点数の表現方法に早めに慣れておくことは重要である。 Fortran90/95で倍精度浮動小数点数を表す方法は幾つかある。 最も簡単でportabilityの高い方法は以下の方法であろう。 まず、単精度浮動小数点数の精度に関係したある整数定数を定義する。 ここではその整数をSPという名前にする。 SPとはSingle Precision(単精度)の頭文字をとったものである。 integer, parameter :: SP = kind(1.0) 次に、このSPを使って単精度として指定した浮動小数点数の2倍の精度を持つ浮動小数点数、 つまり倍精度浮動小数点数に関係したある整数定数(ここではDPという名前)を以下のようにして定義する。 integer, parameter :: DP = selected_real_kind(2*precision(1.0_SP)) この整数定数DPはDouble Precisionを意味する。 こうして定義した二つの整数SPとDPを使って単精度浮動小数点や倍精度浮動小数点数を宣言する。 real(kind=SP) :: a real(kind=DP) :: b とすると、aは単精度浮動小数点、bは倍精度浮動小数点数である。 上のプログラムのkind=の部分は省略可能である。 つまり real(SP) :: a real(DP) :: b としてもよい。 数値データの精度指定にもこのSPやDPを使う。 a = 1.0_SP ! 単精度の1.0 b = 1.0_DP ! 倍精度の1.0 サンプルプログラムを見てみよう。 program double_precision_real implicit none integer, parameter :: SP = kind(1.0) integer, parameter :: DP = selected_real_kind(2*precision(1.0_SP)) real(SP) :: a real(DP) :: b a = 3.1415926535897323846264338327950288_SP b = 3.1415926535897323846264338327950288_DP print *,' a, b = ', a, b end program double_precision_real * &color(#0000ff){【演習】}; [#m1ea2638] 上のプログラムをdouble_precision_real.f95という名前のファイルに保存し、 コンパイル&実行せよ。 * &color(#0000ff){【演習】}; [#u0b459f3] double_precision_real.f95に現れる二つのkindパラメータSPとDPには実際にはどんな値が入っているか調べよ。 * 数字表現 [#x3e65148] - 浮動小数点数 -- 1. ! デフォルト精度 -- 0.1_SP ! kindパラメタSPの浮動小数点数 -- 3.141593_DP ! kindパラメタDPの浮動小数点数 -- 0.31415926535897932e-1_DP ! 上に同じ * アンケート:C言語について [#p6cfb1d3] #vote(Cはよく知っている, Cは分からない(忘れた)) * 型名 [#hca8af14] | 型 | C ( C99 ) | Fortran90/95 | 補註 | | 文字 | char | character | | | 文字列 | char[n+1] | character(len=n) | nは文字長| | 整数 | int | integer | integer(kind=4)でも可 | | 実数 | float | real | real(kind=SP)でも可 | | 倍精度実数 | double | real(kind=DP) | | | 「長い」整数 | long | integer(kind=8でも可) | kindの整数はシステム依存 | | bool | ( _Bool ) | logical | 値は .true. または .false. | | 複素数 | ( _Complex ) | complex | | | 構造体 | struct | type | 詳しくは後述 | * 構造体 (derived type) [#o99419e2] C言語の構造体とほとんど同じである。下の例をみよ。 program type implicit none type student character(len=20) :: first_name, last_name integer :: age end type student type(student) :: st st = student("Albert", "Einsten", 19) print *,st%first_name, st%last_name, st%age end program type 構造体のメンバにアクセスするのに%を使う。 構造体の入れ子も可能である。 構造体の配列も可能である。 構造体を構造体に代入(コピー)することも可能である。 * &color(#0000ff){【演習】}; [#na2ba0de] 上のプログラムをtype.f90に保存し、コンパイル&実行せよ。 * &color(#0000ff){【演習】}; [#od4538f0] student型の構造体変数をもう一つ(例えばst2という名前)を作り、 stのデータをst2にコピーした上で、要素の一部(例えばage)を変更し、st2を出力せよ。 * 宣言 [#q0368b92] C言語では変数の宣言はブロックの先頭にまとめて置く。(C99やC++はもっと自由だが。) Fortran90/95でも同じ。 * 暗黙の型宣言 [#tc2c33ff] Fortran90/95ではimplicit noneを省略すると「暗黙の型宣言」をしたことになる。 暗黙の型宣言とは、次の6つのアルファベット i,j,k,l,m,n で始まる変数は整数である等のルールである。 使わない方がいい機能なので詳細は知らなくて良いだろう。 バグが入りやすいので、暗黙の型宣言は使わない方が良い。 Fortran90/95プログラムでは&color(#ff0000){常にimplicit none宣言をすること。}; * 暗黙の型宣言はなぜバグが入りやすいか [#w75e86a6] 宣言したつもりのない変数を間違って使っていても気がつかない可能性がある。 例えば、次のプログラムにはバグがある。すぐに見つけられるか? program use_implicit_none integer :: fresh_meat flesh_meat = 100 ! yen print *, "today's price = ", fresh_meat end program use_implicit_none * &color(#0000ff){【演習】}; [#xd8471a3] 上のプログラムをuse_implicit_nene.f95というファイルに書き込み、コンパイル&実行せよ。 - バグがあるのにコンパイラは教えてくれないだけでなく、 何事もないかのように正常終了する。しかも、それらしい答えを返す。 つまりバグがあることにすら気がつかない! * &color(#0000ff){【演習】}; [#i9399e76] 今作ったuse_implicit_nene.f95の2行目(program ...の次の行)に implicit noneと書いてから、コンパイルせよ。 * 行の継続 [#u8a3a60c] 行末に&をつけると継続行 a = b + c + & d + f * セミコロン [#y0eac818] セミコロンは改行と同じ tmp = right right = left left = tmp と tmp = right; right = left; left = tmp は同じ。 * 1次元配列 [#z78a9b40] C言語では array01[NX]; が大きさNXの1次元配列である。 メモリ上に、array01[0], array01[1], array01[2], ..., array01[NX-1]と並んでいる。 Fortran90/95では integer, dimension(NX) :: array01 と宣言するとメモリ上に、array01(1), array01(2), array01(3), ..., array01(NX)と並ぶ。 &color(#ff0000){配列のインデックスは1から始まる。}; * &color(#0000ff){【演習】}; [#db21d215] 大きさNXの1次元整数配列array_01を作り、 各要素に1,2,3,...,NXを代入した上で、 全要素の和をとるFortran90/95プログラムを書け。 * 2次元配列 [#ncd4eeca] 厳密に言えばC言語には2次元配列はない。 あるのは「配列の配列」である。 つまり二つの整数インデックスiとjを使って array02[j][i] という形でアクセスできるものである。 このarray02を2次元配列とは呼べないということは、 array02のサイズ(インデックスiとjの範囲)は実行時に不定として、 array02をまるごと引数で受け取る関数がC言語では作れないことを考えれば納得出来るであろう。 * メモリ空間中での2次元配列の各要素の位置 [#a07128c6] 上記のarray02は、 array02[0][0], array02[0][1], array02[0][2], ..., array02[0][NX-1] と並び、そして (それに続く場合もあるし、 まったく別の場所にあるかもしれない)ある位置から、 array02[1][1], array02[1][1], array02[1][2], ..., array02[1][NX-1] という順番で並ぶ。 一方、 Fortran90/95ではarray02(i,j)という形でアクセスできる量は本物の2次元配列である。 サイズがわかっている場合には integer, dimension(NX,NY) :: array02 と宣言する。 不明の場合には、 integer, dimension(:,:) :: array02 と宣言し、後ほど(実行時に)メモリをallocateする。 メモリ上では、array02の要素は array02(1,1), array02(2,1), array02(3,1), ..., array02(NX,1), array02(1,2),... に全ての要素(NX*NY個)が連続して並ぶ。 配列の要素がメモリ空間中に連続してあることは、 メモリへのアクセス速度が極めて重要となるスーパーコンピューティングでは極めて重要である。 * 3次元配列 [#s8cc6177] C言語では array03[k][j][i] が上の意味での「擬似」3次元配列である。 Fortran90/95では array03[i][j][k] が3次元配列で、 宣言は integer, dimension(NX,NY,NZ) :: array03 または integer, dimension(:,:,:) :: array03 とする。 * if構文 [#i308f811] if文はC言語とほとんど同じ。下の例を見れば一目瞭然であろう。 if (criterion) then action end if '''action'''が1行で書けるなら if ( criteron ) action ともできる。 else-if構文も簡単である。 if (criterion_1) then action_1 else if (criterion_2 ) then action_2 else if (criterion_3 ) then action_3 end if * 関係演算子 [#g5e70b10] これも下の例を見ればわかるであろう。 if (a==b) then ! aとbが等しい if (a>b) then if (a>=b) then if (a<b) then if (a<=b) then if (a/=b) then ! aとbが等しくない * 関係演算子(古い書き方) [#r7c520aa] 上の関係演算子はそれぞれ下のようにも書ける。 if (a.eq.b) then ! aとbが等しい if (a.gt.b) then if (a.ge.b) then if (a.lt.b) then if (a.le.b) then if (a.ne.b) then ! aとbが等しくない * 論理値 [#f694601d] bool変数はlogicalという型名を持ち、 .true. か .false. の二つをとる。 logical :: a, b, c a = .true. b = .false. c = a .and. b c = a .or. b c = .not.a * 繰り返し構文(doループ) [#uedfc000] C言語でいうfor文である。 ** incrementが1の場合のdo-loop [#me73416e] program do_loop implicit none integer :: i do i = 1 , 100 print *, ' i = ', i end do end program do_loop ** incrementが2の場合のdo-loop [#d5230d82] program do_loop implicit none integer :: i do i = 1 , 100 , 2 print *, ' i = ', i end do end program do_loop * &color(#0000ff){【演習】}; [#zed411bf] - 上のプログラムをdo_loop.f95に保存し、increment値(上の場合2)を自由に変えて実行せよ。 - do_loop.f95のdo文を do i = 100 , -100 , -2 として実行せよ。 * exitとcycle [#zf2e6992] 整数のカウンターのないdo-loopも可能である。 do ... end do このままだと無限ループになるので、通常はある条件を満たせばループから抜け出すようにする。 C言語だとbreak文でループから抜け出す。(正確には最も内側のループから抜け出す。) Fortran90/95でbreakに相当するのはexitである。 do ... if (condition) exit !---+ end do ! | ループから抜け出す。 !<--+ C言語ではループの先頭に戻る時にはcontinue文を使うが、 それに相当するのはFortran90/95ではcycleである。 do !<--+ ... ! | 戻る if (condition) cycle !---+ ... end do * &color(#0000ff){【演習】}; [#l1794fba] if文、select case文、do文を使って自由にプログラムを作ってみよ。 * 関数 [#r1a5bee4] C言語と同様に関数(function)という手続き(procedure)は、 Fortran90/95プログラムの重要な構成部品である。 関数の使い方は以下の例を見ればわかるであろう。 * &color(#0000ff){【演習】}; [#u6abbb06] - 次のプログラムをfunction01.f95として保存し、実行せよ。 integer function next_int(i) implicit none integer, intent(in) :: i next_int = i+1 end function next_int program function01 implicit none integer, external :: next_int print *, 'ans = ', next_int(-100) end program function01 intent(in)は引数iが入力引数であり、関数next_intの内部で変更されることがないことを保証するものである。 - 関数を以下のように内部副プログラムとしてメインの中に含むこともできる。 次のプログラムをfunction02.f95として保存し、実行せよ。 //(内部副プログラムについては後述。) program function02 implicit none print *, 'ans = ', next_int(-100) contains integer function next_int(i) integer, intent(in) :: i next_int = i+1 end function next_int end program function02 * サブルーチン [#j858d884] 関数の本来の機能は入力に応じて出力を返すものであるが、 出力(返り値)が不要な場合も多い。 C言語ではこのような場合void関数とするが、 Fortran90/95ではvoid関数をsubroutineと呼ぶ。 サブルーチンを呼び出すにはcall '''subroutine名'''とする。 以下の例をみよ。 subroutine next_int(input, output) implicit none integer, intent(in) :: input integer, intent(out) :: output output = input + 1 end subroutine next_int program subroutine01 implicit none integer :: i, j i = 10 call next_int(i,j) print *, 'ans = ', j end program subroutine01 * 値渡しと参照渡し [#n02083fe] C言語は値渡し、Fortran90/95は参照渡しである。 C言語が値渡しであることは、以下の例でkの値が変わっていないことから確認できる。 void pass(int i) { i = 0; printf("i=%d\n", i); } main() { int k = 2010; printf("before: k = %d\n", k); pass(k); printf("after: k = %d\n", k); } これと'''似た'''プログラム(同じではない)をFortran90/95で書くと以下のようになる。 subroutine pass(i) implicit none integer :: i i = 0; print *, "i=", i end subroutine pass program call_by_reference implicit none integer :: k = 2010 print *, "before: k = ", k call pass(k) print *, "after: k = ", k end program call_by_reference このプログラムではkの値はサブルーチンpassを読んだ後に変更されている。 * &color(#0000ff){【演習】}; [#w00a63d7] 上の二つのプログラムをそれぞれcall_by_value.c、call_by_reference.f95という名前で保存し、実行せよ。 * 入出力属性 [#qc7a9554] 値渡しに比べて参照渡しは値をコピーする必要がないので実行が速い。 だが、上のcall_by_reference.f95プログラムで見たように、 引数として渡した変数がそのサブルーチン内部で勝手に(誤って)変更されると見つけにくいバグになり、危険である。 このような事故を防ぐためにFortran90/95には引数に入出力属性をつけることができる。 サブルーチンincrementの引数iは入力属性をもつと指定すると(下のプログラムの2行目)、 コンパイラがエラーを出してくれる。 subroutine increment(i) ! コンパイルエラーを出してくれる integer, intent(in) :: i i = 0; print *, "i=", i end subroutine increment program call_by_reference implicit none integer :: k = 2010 print *, "before: k = ", k call increment(k) print *, "after: k = ", k end program call_by_reference * &color(#0000ff){【演習】}; [#gd1ca64f] call_by_reference.f95を上のように変更してコンパイルせよ。 * 入出力属性の種類 [#ded0a362] 入出力属性には以下の3種類がある。 | intent(in) | 入力 | その手続き内で値は変更されない変数 | | intent(out) | 出力 | その手続き内で値が設定される変数 | | intent(inout) | 入出力 | 両者の混合。デフォルト。 | バグの混入を防ぐために、Fortran90/95プログラムでは&color(#ff0000){すべての引数に入出力属性をつける};ことを強く勧める。 * case 構文 [#pe0d97f8] C言語でいうswitch文である。 select case (case expression) case (case selector 1) action 1 case (case selector 2) action 2 case (case selector 3) action 3 . . case default ! なくても良い。 default action end select C言語では普通breakが必要だがFortran90/95のcase文には不要である。 * &color(#0000ff){【演習】}; [#e8234997] 次のプログラムをcase.f95に保存し、実行せよ。 program case implicit none integer :: month month = 6 select case (month) case (1) print *,'January.' case (2) print *,'February' case (3:5) print *,'Spring' case default print *,'Other season' end select end program case * 文字列の比較 [#qa2a3c2b] C言語では二つの文字列が等しいかどうかを判断するためにわざわざstrcmpを呼ぶ。 Fortran90/95では if (string1==string2) や if ( response == "what do you mean?" ) などと書ける。 従って長さの違う文字列もselect case構文で以下のように簡単に場合分けできる。 program case_string implicit none character(len=*), parameter :: color = 'white' select case (color) case ('aka') print *, 'red' case ('ao') print *, 'blue' case ('midori') print *, 'green' case default print *, "I don't know the color." end select end program case_string * &color(#0000ff){【演習】}; [#pecc14bd] 上のプログラムをcase_string.f95というファイルに保存し、実行せよ。 * 様々な文字列操作 [#gbeafb3a] ここでFortran90/95における文字列操作機能を紹介しておく。 参考までにCでの同様な操作をコメントに書いておいた。 program strings implicit none character(len=*), parameter :: string01 = 'Computational' character(len=*), parameter :: string02 = 'Science' character(len=30) :: message30 print *, ' length of string01 = ', len(string01) ! strlen(string01) print *, ' string01==string02 = ', string01==string02 ! strcmp(string01,string02) print *, ' string01//string02 = ', string01//string02 ! strcat(string01,string02) print *, ' string01//string02 = ', string01//string02 ! strcat(string01,string02) print *, ' string01//string02//string02 = ', string01//string02//string02 ! ? message30 = string01 ! strcpy(message,string01) print *, ' message in 30 characters = ', message30 print *, ' length of message30 is = ', len(message30) print *, ' length of message30 is = ', len_trim(message30) print *, ' message30(10:13) = ', message30(10:13) print *, ' message30//string02 = ', message30//string02 print *, ' trim(message30)//string02 = ', trim(message30)//string02 end program strings * 配列演算機能: スカラーの代入 [#q59330e0] まずはC言語版を見る。 /* array01.c */ #define NX 10 #define NY 12 #define NZ 14 main() { int i, j, k; double A[NZ][NY][NX]; for (k=0; k<NZ; k++) { for (j=0; j<NY; j++) { for (i=0; i<NX; i++) { A[k][j][i] = 0.0; } } } } 同じことをFortran90/95で「下手に」書けばこうなる。 ! ! array01.f95 ! module constants implicit none integer, parameter :: SP = kind(1.0) integer, parameter :: DP = selected_real_kind(2*precision(1.0_SP)) integer, parameter :: NX = 10 integer, parameter :: NY = 12 integer, parameter :: NZ = 14 end module constants program array01 use constants implicit none integer :: i, j, k real(DP), dimension(NX,NY,NZ) :: A do k =1 , NZ do j = 1 , NY do i = 1 , NX A(i,j,k) = 0.0 end do end do end do end program array01 普通はこう書く。module constantsは同じなのでmainプログラムだけ示す。 program array01 use constants implicit none integer :: i, j, k real(DP), dimension(NX,NY,NZ) :: A A = 0.0 end program array01 * 配列演算機能: 配列の要素毎の計算(その1) [#re2501a5] C言語で /* array02.c */ #define NX 10 #define NY 12 #define NZ 14 main() { int i, j, k; double A[NZ][NY][NX]; double B[NZ][NY][NX]; double C[NZ][NY][NX]; for (k=0; k<NZ; k++) { for (j=0; j<NY; j++) { for (i=0; i<NX; i++) { A[k][j][i] = 3*B[k][j][i]-C[k][j][i]; } } } } Fortran90/95ではこうなる。「下手」に書いた方法をコメントで示した。 (module constantsは同じ) program array02 use constants implicit none integer :: i, j, k real(DP), dimension(NX,NY,NZ) :: A, B, C !------------------------------------------ ! do k =1 , NZ ! do j = 1 , NY ! do i = 1 , NX ! A(i,j,k) = 3*B(i,j,k)-C(i,j,k) ! end do ! end do ! end do !------------------------------------------ A = 3*B-C end program array02 * 配列演算機能: 配列の要素毎の計算(その2) [#j62a06a1] 割り算でも同じである。 ! ! array03.f95 ! module constants implicit none integer, parameter :: SP = kind(1.0) integer, parameter :: DP = selected_real_kind(2*precision(1.0_SP)) integer, parameter :: NX = 10 integer, parameter :: NY = 12 integer, parameter :: NZ = 14 end module constants program array03 use constants implicit none integer :: i, j, k real(DP), dimension(NX,NY,NZ) :: A, B, C !------------------------------------------ ! do k =1 , NZ ! do j = 1 , NY ! do i = 1 , NX ! A(i,j,k) = B(i,j,k)/C(i,j,k) ! end do ! end do ! end do !------------------------------------------ A = B/C end program array03 ------------------------------ as of &_now; (&counter;)