概要
1234
などの整数を,文字列"001234"
に変換する方法をまとめました.
ネットでよく見る既存の方法では,文字列の長さをあらかじめ決めておく必要があります.また,そのようにすると,文字列の長さが整数の桁数より大きい場合に空白が詰められるので,それを文字列操作関数で消去する必要があります.
一方で,数値計算では,計算結果を連番result0001.dat, result0002.dat
等として,0埋めして出力することもよくあります.
そこで,整数の桁数に依存せず,0埋めにも対応した変換方法を作る事にしました.
変換の手順
変換は以下の手順で行います.少し手間ですが,何故このような手順になっているかは,以降の節で説明します.
- 整数
num
の桁数を取得する - 1.で取得した「整数
num
の桁数」の桁数を取得する - 「整数
num
の桁数」を文字列に変換する - 3.で変換した文字列を用いて,
write
文の書式を作成する - 整数
num
を文字列に変換する
前提知識
内部ファイル
Fortranには,整数(あるいは実数)と文字列の相互変換を行う関数は存在していませんが,内部ファイル,特に内部write
文とよばれる機能を利用すると,簡単に変換できます.
Fortranのwrite
文は,装置番号と書式を指定し,変数あるいはリテラルを指定書式で装置へ出力します.
write(装置番号,書式) 変数あるいはリテラル
装置番号という呼称は,おそらく過去の状況を引き継いでいるだけで,実際にはファイル等と装置番号を対応付けて利用します.この装置番号に文字列変数を与えることで,整数(あるいは実数)が文字列に変換されます.このような変数の使い方を内部ファイルとよび,変数へデータを出力するwrite
文を内部write
文とよびます.
program main
implicit none
character(3) :: str
write(str,"(I3)") 456
print *,str ! 456
end program main
このことから,write
文を画面/ファイル出力機能と捉えるより,データの移動を行う機能と捉えた方が理解しやすいように思います.
書式
0埋めをするには,write
文の書式指定において,書式指定子I
,桁数に続いて,0埋めする桁数を.
で結合します.
program main
implicit none
character(5) :: str
write(str,"(I5.4)") 456 !456を5桁で表示し,1~4桁まで(当該桁に数がなければ)0埋めする
print *,str ! ␣0456
end program main
このとき,書式を指定する桁数に変数を用いることはできません.書式は文字列で指定するので,桁数を文字列として文字列変数に代入しておけば,桁数を実行時に制御できます.
program main
implicit none
character(5) :: str
character(:),allocatable :: fmt
fmt = "(I5.4)" ! 書式 5桁で表示し,1~4桁まで(当該桁に数がなければ)0埋めする
write(str,fmt) 456
print *,str ! ␣0456
fmt(5:5) = "5" ! 書式の5文字目を5に置き換える "(I5.4)" →"(I5.5)"
write(str,fmt) 456
print *,str ! 00456
end program main
自動再割付文字列
Fortranの文字列変数は,allocatable
属性を付与することで,文字列リテラルあるいは変数が代入されたときに,文字列の長さに応じて自動で割り付けられるようになります.
program main
implicit none
character(:),allocatable :: str
print *,len(str) ! 0 ※gfortranは32767
str = "dynamic string"
print *, str, len(str) ! dynamic string 14
str = "string is not an array of characters"
print *, str, len(str) ! string is not an array of characters 36
end program main
allocatable
な文字列が自動で(再)割付されるのであれば,内部write
文に自動再割付文字列を与えれば,整数の桁数に応じて自動で割り付けられるように思うでしょうが,そうは問屋が卸しません.
再自動割付文字列が自動で再割付されるのは,変数あるいはリテラルが=
で代入されるときのみです.
program main
implicit none
character(:),allocatable :: str
write(str,"(I3)") 456 ! 実行時エラー
print *,str
end program main
そのため,内部write
文に用いる文字列変数は,事前に整数の桁数を取得して,その桁数に応じた長さで割り付けておく必要があります.
整数から文字列への変換手法
上述の手順に沿って,整数を受取り,それを文字列に変換し,その文字列を返す関数int32_to_string()
を作成します.引数は4バイト整数,戻り値は文字列とします.戻り値の文字列の長さは,整数の桁数に応じて動的に変化するようにします.
pure function int32_to_string(num) result(str)
use, intrinsic :: iso_fortran_env
implicit none
integer(int32),intent(in) :: num
character(:),allocatable :: str ! retval
end function int32_to_string
整数num
の桁数を取得する
まず,引数として与えられる整数の桁数を取得します.Fortranにはそういう関数がないようなので,作成します.十進整数の桁数を取得するには,log10
を計算して小数点以下を打ち切り,+1するだけです.
pure function get_digits_of(num) result(num_digit)
implicit none
integer(int32),intent(in) :: num
integer(int32) :: num_digit
num_digit = int(log10(dble(num))) + 1
end function get_digits_of
作成した関数を使い,引数num
の桁数を得ます.Extra_Digits
は追加する桁です.result01000.txt
などのように,連番の長さを制御するために用いるだけなので,必須ではありません.
integer(int32),parameter :: Extra_Digits = 2 ! 追加する桁
integer(int32) :: num_digits ! 整数numの桁数
num_digits = get_digits_of(num) + Extra_Digits
整数num
の桁数の桁数を取得する
整数num
を文字列に変換する書式文字列fmt
を作る前準備として,「num
の桁数」の桁数を取得します.
例えば,整数2147483647
を文字列"002147483647"
として出力するには,書式として"(I12.12)"
という文字列が必要です.書式の12
を作るために,整数の12
から2桁の文字列"12"
を作る必要があります.
8バイト整数でも最長20桁なので,わざわざ桁数の桁数を取得しなくても,2と決め打ちしておけば問題ないように思います.しかし,将来的に100桁の整数を取り扱う可能性がないわけではないので(そして,Fortranはその頃まで生き残っていそうなので),決め打ちはしないようにしました.
integer(int32) :: dgt_digits ! 整数numの桁数num_digitsの桁数
dgt_digits = get_digits_of(num_digits)
整数の桁数を文字列に変換する
整数num
の桁数を文字列に変換します.このとき,書式をI0
としておけば,自動的にnum
の桁数を認識してくれます.
内部write
文に用いる文字列変数は,あらかじめ割り付けておく必要があります.
character(:),allocatable :: str_digits
allocate(character(dgt_digits)::str_digits)
write(str_digits,'(I0)') num_digits
書式を作成する
整数num
の桁数の文字列を連結して,書式を作ります.これは代入演算ができるので,書式用の文字列fmt
をallocate
しておく必要はありません.
character(:),allocatable :: fmt
fmt = "(I"//str_digits//"."//str_digits//")"
整数num
を文字列に変換する
ようやく準備ができたので,内部write
文で整数を文字列に変換します.
allocate(character(num_digits)::str)
write(str,fmt) num
実行結果
作成した関数に4バイト整数の最大値を渡し,2桁追加して文字列に変換したところ,正しく実行されました.
print *, int32_to_string(huge(0_int32)) ! 002147483647
関数全景
pure function int32_to_string(num) result(str)
use, intrinsic :: iso_fortran_env
implicit none
integer(int32),intent(in) :: num
character(:),allocatable :: str ! retval
integer(int32),parameter :: Extra_Digits = 2 ! 追加する桁数
integer(int32) :: num_digits ! 整数numの桁数
integer(int32) :: dgt_digits ! 整数numの桁数num_digitsの桁数
character(:),allocatable :: str_digits
character(:),allocatable :: fmt
num_digits = get_digits_of(num) + Extra_Digits ! 整数numの桁数を取得
dgt_digits = get_digits_of(num_digits) ! 整数numの桁数num_digitsの桁数を取得
! 整数の桁数を文字列に変換
allocate(character(dgt_digits)::str_digits)
write(str_digits,'(I0)') num_digits
! 書式を作成
fmt = "(I"//str_digits//"."//str_digits//")"
! 整数numを文字列に変換
allocate(character(num_digits)::str)
write(str,fmt) num
end function int32_to_string
pure function get_digits_of(num) result(num_digit)
implicit none
integer(int32),intent(in) :: num
integer(int32) :: num_digit
num_digit = int(log10(dble(num)))+1
end function get_digits_of
まとめ
内部write
文を使わない方が楽かもしれません.
Fortran 202Xでは,内部write
文でも(代入以外でも)自動で割り付けるようになってほしいですね.