LoginSignup
35
26

More than 5 years have passed since last update.

Fortranにおける整数型・実数型・複素数型変数の宣言方法

Last updated at Posted at 2018-11-30

概要

Fortranの整数,実数,複素数型の型宣言についてまとめました.本当は文字型や配列なども含めた包括的な記事にしたかったのですが,型の指定の話だけでとても長くなったので,文字と配列については別の記事で言及することにしました.
結局どうしよう?にまとめがあるので,時間のない方はそこまで飛ばしてください.

使用環境

コンパイラ バージョン
intel Parallel Studio XE Composer Edition for Fortran 17.0.4.210
PGI Visual Fortran for Windows 18.7
gfortran 4.10.0 (for mingw32)
7.3.0 (for Ubuntu on WSL)

謝辞

光のインターネットの闇@no_maddo 氏に貴重なご意見を頂いた.ここに記して謝意を表する.

Fortranの型宣言の基本

([kind=]種別)[,属性,属性,...] :: 変数名[=初期値]

[]は省略可能であることを意味しています.
で整数か実数か複素数か(あるいは他の型)を指定し,種別で精度(表現できる数値の範囲)を指定します.定数や動的割付などの情報を,属性という形で型の後ろに追加していきます.
種別は,ほとんどの場合変数のサイズ(バイト数)です.それ以外の状況を見たことはありませんが,コンパイラによってはそうでないという情報があります1
kind==初期値は省略可能ですが,定数を宣言する場合は初期値の代入が必須です.

宣言時の初期値代入ができない場面もあります.
また,宣言時に初期値を代入すると変数が静的変数になるので,宣言時の初期値代入は原則としてしないよう習慣づけた方が無難です.宣言時に値を代入するのは以下の場合くらいでしょう.
1. 定数を宣言する場合
2. 静的変数を宣言する場合

古い書き方では型と変数名はスペースで区切りましたが,現在はコロン二つ::で区切ります.

属性

属性はこの記事の主眼ではないので,一覧表だけ置いておきます.この記事では,parameter属性を使用しています.

変数の基本的な属性

属性 機能
parameter 定数であることを示す
save 静的変数であることを示す
dimension(:[,:,...]) (多次元)配列であることを示す
allocatable 動的割付けする変数・配列であることを示す
pointer ポインタ変数であることを示す
target ポインタ変数によって参照可能な変数(pointee)であることを示す
volatile プログラム以外(例えばハードウェア)から変更される可能性のあることを示す2

仮引数の属性

属性 機能
intent({in,out,inout}) 関数・サブルーチンの仮引数が{読取り用,書込み用,読み書き可能}であることを示す
value 関数・サブルーチンの仮引数が値渡しであることを示す
optional 関数・サブルーチンの仮引数が省略可能であることを示す

アクセス制御

属性 機能
public 変数を派生型外部あるいはモジュール外部に公開されることを示す
private 変数を派生型外部あるいはモジュール外部に公開しないことを示す
protected 変数をモジュール外部に読取り専用で公開することを示す

C言語との相互利用

属性 機能
bind(c) C言語と相互利用可能であることを示す(列挙型もしくは派生型)

整数型

整数型を例に,Fortranにおける型宣言を見てみましょう.

範囲 表記 Intel PGI gfortran 備考
integer コンパイルオプションでサイズを変更可能
Intel /integer_size:{16,32,64}3
PGI -i{2,4,8}
gfortran -fdefault-integer-84
$-2^7$~$2^7-1$ integer(1) kind=を省略
integer*1 古い書き方
$-2^{15}$~$2^{15}-1$ integer(2)
integer*2
$-2^{31}$~$2^{31}-1$ integer(4)
integer*4
$-2^{63}$~$2^{63}-1$ integer(8)
integer*8

ところが,これらはいずれも推奨されない書き方のようです1.kindで指定する種別はバイト数と同じと思われますが,コンパイラによってはバイト数を意味しないことがあるようで,互換性の高いプログラムを書く上では推奨されません.
コンパイラ間の可搬性もないのに互換性の高いプログラムとは・・・

selected_int_kind()kind()で種別を指定するのが正しい方法とされています.また,Fortran 2008からは,型とビット数を名前に持つ定数が,種別を表すために追加されました.

表記 Intel PGI gfortran 備考
integer(selected_int_kind(r=桁数)) 桁数rに応じた種別が用いられる
integer(kind(x=整数)) 引数として渡した整数xを取り扱える種別が用いられる
標準の種別が優先される
integer(int8) iso_fortran_envモジュールをuseする
integer(int16)
integer(int32)
integer(int64)

selected_int_kind()kind()の実行例を以下に示します.コメントは実行結果で,IはIntel Fortran,PはPGI Fortran,gはgfortranを表しています.

program main
    implicit none
    print *,selected_int_kind(2)      !I 1 P 1 g 1
    print *,selected_int_kind(4)      !  2   2   2
    print *,selected_int_kind(8)      !  4   4   4
    print *,selected_int_kind(16)     !  8   8   8
    print *,kind(31)                  !  4   4   4
    print *,kind(127)                 !  4   4   4
    print *,kind(32767)               !  4   4   4
    print *,kind(2147483647)          !  4   4   4
    print *,kind(9223372036854775807) !  8   8   4
end program main

selected_int_kind()は引数で指定した桁数を正しく表現できる種別を返します.kind()は,引数で渡した数が標準となるintegerの種別で表現できるなら標準の種別を,表現できないのであれば,正しく表現できる種別を返してくるようです.
gfortranではコンパイルエラーが出ます.

   print *,kind(9223372036854775807) !  8   8   4
                                  1
Error: Integer too big for its kind at (1). This check can be disabled with the option -fno-range-check

そのエラーメッセージに沿って-fno-range-checkオプションを付与してコンパイル・実行した結果です.正確に記述するには,数値リテラルの種別を指定して,9223372036854775807_int64などと書く必要があります.数値リテラルの記述方法は後ほど説明します.

*?でバイト数を指定する古い書き方では,上記のように数字をselected_int_kind()などに置き換えて種別を指定することはできません.

表記 Intel PGI gfortran 備考
integer*selected_int_kind(r=桁数) × × × integer*?との類似性に基づく
integer*kind(x=整数) × × × integer*?との類似性に基づく

2進数,8進数,16進数

変数を2,8,16進数で表示することも,2,8,16進数で表現した数値リテラルを変数に代入することもできます.

数値リテラルとして取り扱う場合は,基数を表す記号の後ろに' 'あるいは" "で囲んだ数値を置きます.
16進数の場合,HEXに含まれるいずれの文字でもなくZを使っています.書式指定において,H, E, Xはそれぞれホレリス定数,実数の指数表示,空白の制御が割り当てられているためです.

基数 基数を表す記号 表記例
2 B B'1111100111'
8 O O'1747'
16 Z Z'3E7'

表示する場合には,print文もしくはwrite文の書式で指定します.

基数 書式例
2 '(B8)'
8 '(O4)'
16 '(Z4)'
program main
    implicit none
    integer :: e
    e = 127
    print '(B8)',e ! 1111111
    print '(O4)',e ! 177
    print '(Z4)',e !  7F

    print *,B'11',O'77',Z'FF' ! 3    63    255
end program main

実数型

整数型に続いて,実数(正確には浮動小数点数)型の型宣言を確認します.Fortranの実数型には,単精度,倍精度,4倍精度実数があります.
実数型は実数型でまたややこしい状況です.とりあえずは,正しいとされている,selected_real_kind()およびkind()を用いる方法を示します.

表記 Intel PGI gfortran 備考
real(selected_real_kind([p=桁数,r=指数範囲,radix=基数])) 桁数pあるいは指数範囲rに応じた種別が用いられる
PGIはradixをサポートしていない
real(kind(x=実数)) 引数の実数xを取り扱える種別が用いられる

selected_real_kind()の引数は,10進数での有効桁数を指定するp, 指数の範囲を指定するrのどちらか,あるいは両方を指定します.prで要求される精度が異なる場合は,精度の高い方が採用されます.radixは計算機が有している実数体系(数の内部表現)の基数のようですが,著者が扱える計算機を調べた範囲では,2しかありません5

実数の型 桁数p 指数の範囲r
単精度 6 37
倍精度 15 307
4倍精度 33 4931

kind()の引数に実数を渡しますが,ここでは0を渡すことが一般的です.このとき,単純に0を渡すと整数に対する標準の種別が返されてしまいます.一方で,0.0とすると単精度実数扱いになります.そこで,指数表記を使って精度を指定します.

実数の型 指数記号 指数表記
単精度 e 0e0
倍精度 d 0d0
4倍精度 q 0q0

数値リテラルの記述方法は後ほど説明します.

selected_real_kind()kind()の実行例を以下に示します.

program main
    implicit none
    print *,selected_real_kind(p=6,r=37)    !I 4 P 4 g 4
    print *,selected_real_kind(p=15,r=307)  !  8   8   8
    print *,selected_real_kind(p=33,r=4931) ! 16  -3  16
    print *,selected_real_kind(p=5,r=300)   !  8   8   8
    print *,kind(0e0)                       !  4   4   4
    print *,kind(0d0)                       !  8   8   8
    print *,kind(0q0)                       ! 16      16
end program main

selected_real_kind(p=5,r=300)では,有効桁数は単精度の範囲内ですが,指数範囲は倍精度でなければ表現できないので,戻り値として倍精度を表す8が得られています.
PGIコンパイラは4倍精度実数をサポートしないので,0q0を用いるとコンパイルエラーになります.同様の理由で,selected_real_kind(p=33,r=4931)の戻り値が-3になっています.radixは正しい6prの要件を満たすことができないときに出るエラーのようです.

このように種別を指定することで,取り扱いたい実数に適した精度を持つ変数を宣言できますが,個別の実数の宣言方法も確認しておきましょう.

単精度

単精度実数の宣言には以下の方法があります.

表記 Intel PGI gfortran 備考
real コンパイルオプションで精度を変更可能
/real_size:{32,64,128}
-r8
-fdefault-real-84
real(4)
real*4
single precision × × × 倍精度との比較のため
real(real32) iso_fortran_envモジュールをuseする

仕様の観点から種別を指定しないrealの使用が推奨されています1が,コンパイルオプションで精度を変更できてしまいます.これは移植性や互換性という観点からするとどうなんでしょう?計算機やコンパイラが変わっても精度を固定したいのであれば,種別を指定した方がよいでしょう.
gfortranの場合はさらに厄介で,コンパイルオプション次第でいくらでも精度を変更できます.

program main
    use,intrinsic :: iso_fortran_env
    implicit none
    real    :: a
    real(4) :: b
    real*4  :: c
    real(real32) :: d
    print *, sizeof(a), sizeof(b),sizeof(c),sizeof(d)
    !4 4 4 4
    !8 4 4 4     /real_size:64, -r8, -fdefault-real-8を付与
    !16 4 4 4    /real_size:128を付与
    !8 8 8 8     -freal-4-real-8を付与
    !12 12 12 12 -freal-4-real-10を付与
    !16 16 16 16 -freal-4-real-16を付与
end program main

single precisionという型は利用できませんが,次に紹介する倍精度にはdouble precisionという型宣言が存在するので,統一的な表記のために利用できないかを確認する目的で掲載しています.

倍精度

表記 Intel PGI gfortran 備考
real(8)
real*8
double precision コンパイルオプションで精度を変更可能
/double_size:{64,128}
-r47
-fdefault-real-88
real(real64) iso_fortran_envモジュールをuseする

仕様の観点から,double precisionの使用が推奨されています1.単精度と同様にコンパイルオプション次第で精度が変わります.精度を固定するには,種別を指定する必要があると思われます.

program main
    use,intrinsic :: iso_fortran_env
    implicit none
    double precision :: a
    real(8)          :: b
    real*8           :: c
    real(real64)     :: d
    print *, sizeof(a), sizeof(b),sizeof(c),sizeof(d)
    !8 8 8 8
    !16 8 8 8    /real_size:128, -fdefault-real-8を付与
    !4 4 4 4     -freal-8-real-4を付与
    !12 12 12 12 -freal-8-real-10を付与
    !16 16 16 16 -freal-8-real-16を付与
end program main

4倍精度

Fortranの一部のコンパイラでは,倍精度より高い精度の実数を利用できます.PGIコンパイラは4倍精度実数を利用できません.

表記 Intel PGI gfortran 備考
real(16) ×
real*16 ×
quad precision × × × 倍精度との比較のため
real(real128) × iso_fortran_envモジュールをuseする

single precisionと同様,quad precisionという型は利用できません.
4倍精度実数の場合,仕様的にどうするのが正しいのかはわかりませんが,種別を指定すれば問題ないでしょう.

モジュールを用いた種別の指定

selected_real_kind()あるいはkind()を変数宣言ごとに記述するのは煩わしいので,モジュールに種別を表す定数を定義することが行われているようです.

module floating_point_kinds
    implicit none
    integer,parameter :: sp = selected_real_kind(6)  !single precision
    integer,parameter :: dp = selected_real_kind(15) !double precision
end module floating_point_kinds

program main
    use floating_point_kinds, only : sp, dp
    implicit none
    real(sp) :: a
    real(dp) :: b
    a = 0 !暗黙の型変換を利用
    b = 0 !
    print *,a,b !0.0000000E+00  0.000000000000000E+000
end program main

このようなモジュールを使うことで,selected_real_kind()等で種別を定義するのが一度だけで済みます.
また,かなり小賢しい方法で精度を切り替えることができます.(このような方法が許されるのかはわかりませんが)

program main
    use floating_point_kinds, only : sp, dp=>sp !rename参照
    implicit none
    real(sp) :: a
    real(dp) :: b
    a = 0 !暗黙の型変換を利用
    b = 0 !
    print *,a,b !0.0000000E+00  0.0000000E+00
end program main

dp=>spは,モジュールの変数spをローカル変数dpとして参照することを意味しています.つまり,上のプログラムでは,dpfloating_point_kindsで定義された,倍精度実数の種別を表す定数ではなく,単精度実数の種別を指すローカル変数という扱いになります.そのため,わずかな変更でreal(dp)を倍精度実数から単精度実数に,同様の方法でreal(sp)を単精度から倍精度に切り替えることができます.

それ以外には,プリプロセッサディレクティブを用いて精度を切り替える方法もあります.

module floating_point_kinds
    implicit none
    integer,parameter :: sp = selected_real_kind(6)  !single precision
    integer,parameter :: dp = selected_real_kind(15) !double precision

#ifdef DOUBLE
    integer,parameter :: fp = dp
#else
    integer,parameter :: fp = sp
#endif
end module floating_point_kinds

program main
    use floating_point_kinds, only : fp
    implicit none
    real(fp) :: a
    a = 0
    print *,a
end program main

このような精度を切り替えは,アクセラレータで動作するプログラムを作成する際に利用されます.安価なGPUでは,倍精度実数の実効性能が単精度実数に対するそれと比較して非常に遅いことがあります.精度を切り替えることで,プログラムの実効性能を改善することができます.
プリプロセッサディレクティブを処理するためのコンパイルオプションは,以下の通りです.

コンパイラ オプション
intel /fpp /D識別子 /fpp /DDOUBLE
PGI -Mpreprocess -D識別子 -Mpreprocess -DDOUBLE
gfortran -cpp -D識別子 -cpp -DDOUBLE

複素数型

Fortranでは複素数を取り扱う型があります.宣言の方法は実数型と同じですが,*?でバイト数を指定する方法は倍精度と差異があり,統一的ではありません.

表記 Intel PGI gfortran 備考
complex(selected_real_kind([p=桁数,r=指数範囲,radix=基数]) 桁数あるいは指数範囲に応じた種別が用いられる
complex(kind(x=実数)) 引数の実数を取り扱える種別が用いられる

実数型変数2個をそれぞれ実部と虚部に充てているので,実数型のサイズを変化させるコンパイルオプションを付与するとそれに併せてサイズが変化します.

単精度

表記 Intel PGI gfortran 備考
complex コンパイルオプションで精度を変更可能
/real_size:{32,64,128}
-r8
-fdefault-real-8
complex(4)
complex*4 × × × 単精度実数の型宣言との比較のため
complex*8
single complex × × × 倍精度との比較のため

種別を指定すると,種別を表す数字と精度が実数型変数と同じになりますが,*?でバイト数を指定する場合は ?に入れる数字は精度を指定する種別の2倍 になります.実数型変数が2個あるためです.

complexの使用が推奨されています1.コンパイルオプションによってサイズが変わるため,精度を固定する場合には種別を指定する必要があります.

program main
    use,intrinsic :: iso_fortran_env
    implicit none
    complex         :: a
    complex(4)      :: b
    complex*8       :: c
    complex(real32) :: d
    print *, sizeof(a), sizeof(b),sizeof(c),sizeof(d)
    !8 8 8 8 
    !16 8 8 8   /real_size:64, -r8, -fdefault-real-8を付与
    !16 16 16 16 -freal-4-real-8を付与
    !24 24 24 24 -freal-4-real-10を付与
    !32 32 32 32 -freal-4-real-16を付与
end program main

倍精度

表記 Intel PGI gfortran 備考
complex(8)
complex*16
double complex コンパイルオプションで精度を変更可能
/double_size:{64,128}
-r4
-fdefault-real-8
complex(real64) iso_fortran_envモジュールをuseする

double complexはコンパイルオプションで倍精度実数のサイズを変えると,それに伴って変化します.
倍精度複素数については,double complexではなく,complex(kind(0d0))で種別を取得することが推奨されています1.この統一感のなさは何でしょうか.

program main
    use,intrinsic :: iso_fortran_env
    implicit none
    double complex  :: a
    complex(8)      :: b
    complex*16      :: c
    complex(real64) :: d
    print *, sizeof(a), sizeof(b),sizeof(c),sizeof(d)
    !16 16 16 16 
    !32 16 16 16 /double_size:128, -fdefault-real-8を付与
    !8 8 8 8     -freal-8-real-4を付与
    !24 24 24 24 -freal-8-real-10を付与
    !32 32 32 32 -freal-8-real-16を付与
end program main

4倍精度

PGIコンパイラは4倍精度実数を利用できないので,同様に4倍精度複素数は利用できません.

表記法 Intel PGI gfortran 備考
complex(16) ×
complex*32 ×
quad complex × × × 倍精度との比較のため
complex(real128) × iso_fortran_envモジュールをuseする

数値リテラル

kind()で種別を指定する場合,渡す数値を正しく指定しないと想定外の種別を得てしまうことになります.また,プログラム中に数値リテラルを記述するにしても,整数型のところで見たように,種別を指定しないと正しく表現できません.あるいは暗黙の型変換によって実行時間が長くなる可能性もあります.わずかでも実行時間が遅くなるなら余計なものはいらない!とおっしゃるFORTRAN使いの皆さんはもちろん気をつけていますよね?

整数

数値リテラルは,数値[_kind]として記述します._kindはなくても構いません(というか基本的につけません)が,種別を明記したい場合には,_kindで種別を指定します._kindは数値か定数です.

program main
    use,intrinsic :: iso_fortran_env
    implicit none

    integer(int8),parameter :: k=4
    print *,kind(0)                 !4
    print *,kind(0_1),kind(0_int8)  !1 1
    print *,kind(0_2),kind(0_int16) !2 2
    print *,kind(0_4),kind(0_int32) !4 4
    print *,kind(0_8),kind(0_int64) !8 8
    print *,0_4     !数値で種別を指定
    print *,0_int32 !定数で種別を指定
    print *,0_k     !ユーザ定義の定数でも種別を指定可能
end program main

整数の型宣言のところでも説明しましたが,整数型は標準の種別を返すので,1バイトの整数型が欲しいのにinteger(kind(0)) ::としてしまうと,1バイトより大きな整数型になってしまいます.

実数

実数も整数と同様に数値[_kind]で指定します.このとき,数値は実数で書く必要があります._kindの指定にint32real32などの定数を使ったとしても,その実体はただの数字でしかなく,型の情報を持たないためです.

program main
    use,intrinsic :: iso_fortran_env
    implicit none

    integer(int8),parameter :: k=4
    print *,0_real32,0.0_real32    !0  0.0000000E+00
    print *,0.0_real64,0.0_real128 !0.000000000000000E+000  0.000000000000000000000000000000000E+0000
    print *,0.0_k                  !0.0000000E+00
end program main

他には,指数表記する際に指数記号を型に応じて変化させる書き方があります.ただし,これらはコンパイルオプションによって精度が変化するので注意が必要です.

実数の型 指数記号
単精度 e
倍精度 d
4倍精度 q

指数を指定しますが,仮数部は整数である必要はなく,$0.1\times 10^{2}$のような記述も可能です.

program main
    use,intrinsic :: iso_fortran_env
    implicit none

    print *,0e0,0d0,0q0 !0.0000000E+00  0.000000000000000E+000 0.000000000000000000000000000000000E+0000
    print *,1e-14,1d2,0.314q-33 ! 9.9999998E-15   100.000000000000 3.140000000000000000000000000000000E-0034

    !/real_size:64 /double_size:128, -fdefault-real-8を付与して実行
    print *,0e0,0d0 !0.000000000000000E+000 0.000000000000000000000000000000000E+0000
end program main

複素数

複素数の数値リテラルを記述する場合は,実部と虚部に相当する実数をカンマで区切って書き,それらを丸括弧で囲みます.実数を代入すると,虚部を0と扱うようです.

program main
    use,intrinsic :: iso_fortran_env
    implicit none

    complex(real64) :: c
    c = (1d0,0.5d0)
    print *,c ! (1.00000000000000,0.500000000000000)
    c = 0.5d0
    print *,c ! (0.500000000000000,0.000000000000000E+000)
end program main

実行結果にある( , )は自動で表示されます.

整数,実数の型変換

型変換に使用する関数を示しておきます.

関数 機能 備考
int(a=実数[,kind=整数の種別]) 整数に変換 小数点以下は切り捨て
nint(a=実数[,kind=整数の種別]) 整数に変換 同符号の最も近い整数へ丸める
real(a=整数もしくは実数もしくは複素数[,kind=実数の種別]) 実数に変換 kindで精度を指定
sngl(a=倍精度実数) 単精度実数へ変換
dble(a=整数,実数もしくは複素数) 倍精度実数へ変換
cmplx(x=実部,y=虚部[,kind=実数の種別]) 複素数に変換 実部と虚部は実数
real(z=複素数) 複素数から実部を取り出す
aimag(z=複素数) 複素数から虚部を取り出す

例を示しておきます.

program main
    use,intrinsic :: iso_fortran_env
    implicit none

    real(real64) :: one,r,i
    complex(real64) :: c

    one = 1d0-epsilon(0d0) ! 1より小さい1に最も近い倍精度実数
    print *,one            !I 1.00000000000000 (print時の四捨五入により1と表示)
                           !P 0.9999999999999998
                           !g 0.99999999999999978
    print *,int(one)       ! 0 小数点以下を切り捨て
    print *,nint(one)      ! 1 最も近い整数(1)へ丸める

    print *,real(0,real128) !0.000000000000000000000000000000000E+0000
    print *,sngl(0d0)       !0.0000000E+00
    print *,dble(0e0)       !0.000000000000000E+000

    r = 1d0; i = 0.5d0
    c = cmplx(r,i)             !変数の場合は,(r,i)では複素数へ変換できない
    print *,c,real(c),aimag(c) !(1.00000000000000,0.500000000000000)   1.00000000000000   0.500000000000000
end program main

論理型

数値を取り扱う型以外に,真または偽の2値のみを扱う論理型が存在します.

表記 Intel PGI gfortran 備考
logical integerのサイズに応じて変化
logical(1)
logical*1
logical(2)
logical*2
logical(4)
logical*4
logical(8)
logical*8

Intelコンパイラおよびgfortranでは,論理型の種別を表す定数が定義されていません.PGIコンパイラでは,論理型の種別を表す定数として,logical{8,16,32,64}が定義されています.いずれのコンパイラにおいても,どのような種別があるかは,logical_kindsという配列を表示することで調べることができます.

標準では,最下位ビットが1なら真,0なら偽と扱います9.真偽の2値しか取らないので,logical(1)でよさそうですが,Intel Fortranの古いマニュアル10には,

ia64 システムでは,性能を向上させるために,LOGICAL(2) や LOGICAL(1) ではなく,LOGICAL(4) (または LOGICAL(8)) を使用するようにしてください。

とあるので,Intelコンパイラを利用している場合は,他の計算機環境でもlogicalを使っておけば間違いはないと推察されます.

リテラル

リテラルは,真を表す.true.と偽を表す.false.の二つのみです..not.を前に置くことで否定を表現できます.

program main
    implicit none

    logical :: a
    logical(1) :: b
    logical(2) :: c
    logical(4) :: d
    logical(8) :: e
    print *,sizeof(a),sizeof(b),sizeof(c),sizeof(d),sizeof(e)
    !4 1 2 4 8
    !8 1 2 4 8  /integer_size:64を指定したとき

    a = .true.
    print *, a, .false.           !T F
    print *, .not.a, .not..false. !F T
end program main

print文で論理型の変数やリテラルを表示すると,T, Fのように省略形で表されます.

型変換

IntelおよびPGIコンパイラでは,.true.は整数の-1.false.0に相当します.gfortranでは,.false.0であることは共通ですが,.true.1のようです.
論理型から整数型への変換は,関数int()ではできません.ビット列を異なる型に変換する関数transfer(source=値,mold=見本[,size=配列要素数])を使って変換します.

program main
    use,intrinsic :: iso_fortran_env
    implicit none

    logical(int32) :: a

    a = (*1)
    print *,a                      !出力1
    print *,transfer(a,0_int32  )  !出力2
    print *,transfer(a,0.0_real32) !出力3
    print '(B32.32)', a            !出力4
end program main
(*1)の値 コンパイラ 出力1 出力2 出力3 出力4
.ture. Intel T -1 NaN 11111111111111111111111111111111
PGI T -1 NaN 11111111111111111111111111111111
gfortra T 1 1.40129846E-45 00000000000000000000000000000001
.false. Intel F 0 0.0000000E+00 00000000000000000000000000000000
PGI F 0 0.000000 00000000000000000000000000000000
gfortra F 0 0.00000000 00000000000000000000000000000000

IntelおよびPGIコンパイラでは,a.true.を代入した後,ビット列を整数型に変換すると-1となっています.このとき,見本として渡した0integer型であり,logical型のaと同じサイズであるため,ビット列がinteger型に変換されます.単精度実数0.0を見本として単精度実数型に変換すると,最上位ビットから9ビットが1のため,NaNと判断されました..false.を代入するとすべてのビットが0になっていることが確認できます.

IntelおよびPGIコンパイラでは,論理型変数には整数を代入することができます.どのように論理値として取り扱われるかを見てみましょう.

(*1)の値 コンパイラ 出力1 出力2 出力3 出力4
0 Intel F 0 0.0000000E+00 00000000000000000000000000000000
PGI F 0 0.000000 00000000000000000000000000000000
1 Intel T -1 NaN 11111111111111111111111111111111
PGI T 1 0.000000 00000000000000000000000000000001
2 Intel F 0 0.0000000E+00 00000000000000000000000000000000
PGI F 2 0.000000 00000000000000000000000000000010
-1 Intel T -1 NaN 11111111111111111111111111111111
PGI T -1 NaN 11111111111111111111111111111111

0が偽,最下位ビットが1の数が真9であることは共通ですが,Intelコンパイラでは,偽あるいは真となる整数を0あるいは-1に変更しているようです.

コンパイルオプションによる論理値表現の変更

論理型変数に整数を代入できるコンパイラは,コンパイルオプションで表現を切り替えることができます.その場合は値が0以外なら真,0なら偽と解釈されます.

コンパイラ 最下位ビットの0,1で判別 数値(非ゼロ,ゼロ)で判別
Intel オプションなし(標準) /fpscomp:logicals
PGI オプションなし(標準) -Munixlogical7

上記オプションをつけて実行したところ,Intelコンパイラしか効果がなく,PGIコンパイラでは相変わらず最下位ビットで判別されていました.

(*1)の値 コンパイラ 出力1 出力2 出力3 出力4
2 Intel T 1 1.4012985E-45 00000000000000000000000000000001
PGI F 2 0.000000 00000000000000000000000000000010
-1 Intel T 1 1.4012985E-45 00000000000000000000000000000001
PGI T -1 NaN 11111111111111111111111111111111

IntelコンパイラはIntelコンパイラで,/fpscomp:logicalsオプションを付与すると,真を1に変更しているようです.このことから,論理型を数値に変換して使っているプログラムがあって,それをgfortranからIntelコンパイラに移植する場合は,Intelコンパイラ側で上記オプションをつけて真を1とすることで,移植性を確保できることがわかります.

暗黙の型変換

IntelおよびPGIコンパイラでは,論理型変数を演算に組み込むことで,暗黙的に型変換が行われます..true.-1.false.0であることは変わりません.マスクなどの処理に利用できますが,素直に場合分けを書いたときや,同様の処理をおこなう組み込み関数と比較して効率的かどうかは,さらなる調査が必要です.

program main
    implicit none

    logical :: a,b

    a = .true.
    b = .not.a
    print *,1d0*a, 1d0*b !-1.00000000000000       0.000000000000000E+000
end program main

結局どうしよう?

iso_fortran_envuseしてint32などの種別定数を使うのがよいと考えています.logicalは種別を指定せずに宣言し,整数型と種別が同じになるようにします.
この方法は以下の利点を持っています.

  • 型の表記が統一的になる
  • 数値リテラルの種別指定とも親和性が高い
  • ユーザが自前で定数を定義しなくてもよい11
  • コンパイルオプションでも型のサイズが変わらない(logical以外)
  • selected_{int,real}_kind()を用いて指定する必要が生じた場合に置換で対応できる

selected_{int,real}_kind()を使うよりはマシとしても,real(real32)など2回もrealと書くのは冗長であることは事実です.個人で開発する場合は,この冗長な記述を省略するために,直接種別を指定してもよいのではないかと考えています.ただし,sizeof()関数,iso_fortran_envで定義されている配列integer_kindsreal_kindsを利用して,用意されている型種別とその数字,変数のバイト数の対応を確認し,かつコンパイルオプションの管理に責任を持つことが前提です.

著者が適切と考える型の表記 著者が個人開発で使う簡略表記12
1バイト整数 integer(int8) integer(1)
2バイト整数 integer(int16) integer(2)
4バイト整数 integer(int32) integer 13
8バイト整数 integer(int64) integer(8)
単精度実数 real(real32) real(4)
倍精度実数 real(real64) real(8)
4倍精度実数 real(real128) real(16)
単精度複素数 complex(real32) complex(4)
倍精度複素数 complex(real64) complex(8)
4倍精度複素数 complex(real128) complex(16)
論理型 logical logical

実数の精度を切り替える必要があるときは,モジュールを用いた種別の指定にある方法で切り替えるようにしますが,そもそも精度を変更するかどうかは作るアプリケーションの用途によるので,そこは確認が必要です.

整数に関してはinteger, integer(int64),単精度・倍精度・4倍精度の種別はモジュールを用いて指定するのがグッドプラクティスであるという意見もいただきました.著者の考えもこれらの大部分を支持しています.

*?でバイト数を指定する方法は,?の部分に種別定数を使うことができませんし,モジュールを利用した精度の制御もできません.複素数型では型種別の2倍にするなど罠があるので,これといった利点がみられない以上使わない方がよいでしょう.古いコードには未だに現れるので,そういう書き方もあるということだけ知っておけばよいと思います.

double precisionも,real(real64)という表記ができる以上,統一的な表記という観点からは使わなくてもよいと思います.精度をsingle, double, quadで指定できるとよかったのですが.

まとめ

こういった基本的な型の宣言がややこしいのはよろしくないと思います.


  1. http://www.nag-j.co.jp/fortran/FI_4.html 

  2. それに伴ってその変数に対する最適化が抑制されるので,Fortranの場合はそれを目的にvolatile属性を付与します. 

  3. Linux, MacOSの場合は,-integer_size {16,32,64}とする. 

  4. gfortran 7.3.0では,iso_fortran_evnをuseしている場合にWarningが出る. 

  5. 内部的に2進数以外で数を保持している計算機を使う機会はあるのか? 

  6. PGIコンパイラではそもそも指定できない. 

  7. ドキュメントには書かれていますが,付与しても効果がありません. 

  8. -fdefault-real-8で単精度と倍精度の両方を変化させていると思われます. 

  9. コンパイルオプションで表現を切り替えることができ,その場合は値が0以外なら真,0なら偽と解釈されます. 

  10. https://jp.xlsoft.com/documents/intel/cvf/vf-html/pg/pg20_02.htm 

  11. こういう基本的なところで任意性や進んだ知識を必要とするのはよいことではありません.C言語を学び初めた直後に#include<stdio.h> //おまじないで何千万人も挫折していることを鑑みると,避けられるのであれば避けるべきでしょう. 

  12. あくまで著者の置かれた状況で検討した結果です.著者が対象とするアプリケーションでは,整数は4バイト以外をめったに使わない一方で,実数は倍精度を主に用いるので,単精度はrealとせず,単精度であることを明記しています. 

  13. integerが4バイトのときです.integerが8バイトの計算機を使うことになれば,integer(8)integerと書きます. 

35
26
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
35
26