概要
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=
と=初期値
は省略可能ですが,定数を宣言する場合は初期値の代入が必須です.
宣言時の初期値代入ができない場面もあります.
また,宣言時に初期値を代入すると変数が静的変数になるので,宣言時の初期値代入は原則としてしないよう習慣づけた方が無難です.宣言時に値を代入するのは以下の場合くらいでしょう.
- 定数を宣言する場合
- 静的変数を宣言する場合
古い書き方では型と変数名はスペースで区切りましたが,現在はコロン二つ::
で区切ります.
属性
属性はこの記事の主眼ではないので,一覧表だけ置いておきます.この記事では,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} 3PGI -i{2,4,8} gfortran -fdefault-integer-8 4
|
|
$-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
のどちらか,あるいは両方を指定します.p
とr
で要求される精度が異なる場合は,精度の高い方が採用されます.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
は正しい6がp
とr
の要件を満たすことができないときに出るエラーのようです.
このように種別を指定することで,取り扱いたい実数に適した精度を持つ変数を宣言できますが,個別の実数の宣言方法も確認しておきましょう.
単精度
単精度実数の宣言には以下の方法があります.
表記 | Intel | PGI | gfortran | 備考 |
---|---|---|---|---|
real |
○ | ○ | ○ | コンパイルオプションで精度を変更可能/real_size:{32,64,128} -r8 -fdefault-real-8 4
|
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} -r4 7-fdefault-real-8 8
|
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
として参照することを意味しています.つまり,上のプログラムでは,dp
はfloating_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
の指定にint32
やreal32
などの定数を使ったとしても,その実体はただの数字でしかなく,型の情報を持たないためです.
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
となっています.このとき,見本として渡した0
はinteger
型であり,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_env
をuse
してint32
などの種別定数を使うのがよいと考えています.logical
は種別を指定せずに宣言し,整数型と種別が同じになるようにします.
この方法は以下の利点を持っています.
- 型の表記が統一的になる
- 数値リテラルの種別指定とも親和性が高い
- ユーザが自前で定数を定義しなくてもよい11
- コンパイルオプションでも型のサイズが変わらない(
logical
以外) -
selected_{int,real}_kind()
を用いて指定する必要が生じた場合に置換で対応できる
selected_{int,real}_kind()
を使うよりはマシとしても,real(real32)
など2回もrealと書くのは冗長であることは事実です.個人で開発する場合は,この冗長な記述を省略するために,直接種別を指定してもよいのではないかと考えています.ただし,sizeof()
関数,iso_fortran_env
で定義されている配列integer_kinds
とreal_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
で指定できるとよかったのですが.
まとめ
こういった基本的な型の宣言がややこしいのはよろしくないと思います.
-
それに伴ってその変数に対する最適化が抑制されるので,Fortranの場合はそれを目的に
volatile
属性を付与します. ↩ -
Linux, MacOSの場合は,-integer_size {16,32,64}とする. ↩
-
内部的に2進数以外で数を保持している計算機を使う機会はあるのか? ↩
-
PGIコンパイラではそもそも指定できない. ↩
-
-fdefault-real-8
で単精度と倍精度の両方を変化させていると思われます. ↩ -
https://jp.xlsoft.com/documents/intel/cvf/vf-html/pg/pg20_02.htm ↩
-
こういう基本的なところで任意性や進んだ知識を必要とするのはよいことではありません.C言語を学び初めた直後に
#include<stdio.h> //おまじない
で何千万人も挫折していることを鑑みると,避けられるのであれば避けるべきでしょう. ↩ -
あくまで著者の置かれた状況で検討した結果です.著者が対象とするアプリケーションでは,整数は4バイト以外をめったに使わない一方で,実数は倍精度を主に用いるので,単精度はrealとせず,単精度であることを明記しています. ↩
-
integer
が4バイトのときです.integer
が8バイトの計算機を使うことになれば,integer(8)
をinteger
と書きます. ↩