Fortranで整数を文字列に変換する

# 概要
`1234`などの整数を，文字列`"001234"`に変換する方法をまとめました．

ネットでよく見る既存の方法では，文字列の長さをあらかじめ決めておく必要があります．また，そのようにすると，文字列の長さが整数の桁数より大きい場合に空白が詰められるので，それを文字列操作関数で消去する必要があります．

一方で，数値計算では，計算結果を連番`result0001.dat, result0002.dat`等として，0埋めして出力することもよくあります．

そこで，整数の桁数に依存せず，0埋めにも対応した変換方法を作る事にしました．

# 変換の手順
変換は以下の手順で行います．少し手間ですが，何故このような手順になっているかは，以降の節で説明します．

1. 整数`num`の桁数を取得する
2. 1.で取得した「整数`num`の桁数」の桁数を取得する
3. 「整数`num`の桁数」を文字列に変換する
4. 3.で変換した文字列を用いて，`write`文の書式を作成する
5. 整数`num`を文字列に変換する

# 前提知識
## 内部ファイル
Fortranには，整数（あるいは実数）と文字列の相互変換を行う関数は存在していませんが，内部ファイル，特に内部`write`文とよばれる機能を利用すると，簡単に変換できます．

Fortranの`write`文は，装置番号と書式を指定し，変数あるいはリテラルを指定書式で装置へ出力します．

```Fortran
write(装置番号,書式) 変数あるいはリテラル
```

装置番号という呼称は，おそらく過去の状況を引き継いでいるだけで，実際にはファイル等と装置番号を対応付けて利用します．この装置番号に文字列変数を与えることで，整数（あるいは実数）が文字列に変換されます．このような変数の使い方を内部ファイルとよび，変数へデータを出力する`write`文を内部`write`文とよびます．

```Fortran
program main
    implicit none

    character(3) :: str

    write(str,"(I3)") 456
    print *,str ! 456
end program main
```

このことから，`write`文を画面/ファイル出力機能と捉えるより，データの移動を行う機能と捉えた方が理解しやすいように思います．

## 書式
0埋めをするには，`write`文の書式指定において，書式指定子`I`，桁数に続いて，0埋めする桁数を`.`で結合します．

```Fortran
program main
    implicit none

    character(5) :: str

    write(str,"(I5.4)") 456 !456を5桁で表示し，1～4桁まで（当該桁に数がなければ）0埋めする
    print *,str ! ␣0456
end program main
```

このとき，書式を指定する桁数に変数を用いることはできません．書式は文字列で指定するので，桁数を文字列として文字列変数に代入しておけば，桁数を実行時に制御できます．

```Fortran
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`属性を付与することで，文字列リテラルあるいは変数が代入されたときに，文字列の長さに応じて自動で割り付けられるようになります．

```Fortran
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`文に自動再割付文字列を与えれば，整数の桁数に応じて自動で割り付けられるように思うでしょうが，そうは問屋が卸しません．

再自動割付文字列が自動で再割付されるのは，**変数あるいはリテラルが`=`で代入されるときのみ**です．

```Fortran
program main
    implicit none

    character(:),allocatable :: str

    write(str,"(I3)") 456 ! 実行時エラー
    print *,str
end program main
```

そのため，内部`write`文に用いる文字列変数は，事前に整数の桁数を取得して，その桁数に応じた長さで割り付けておく必要があります．

# 整数から文字列への変換手法

上述の手順に沿って，整数を受取り，それを文字列に変換し，その文字列を返す関数`int32_to_string()`を作成します．引数は4バイト整数，戻り値は文字列とします．戻り値の文字列の長さは，整数の桁数に応じて動的に変化するようにします．

```Fortran
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するだけです．

```Fortran
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`などのように，連番の長さを制御するために用いるだけなので，必須ではありません．

```Fortran
    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はその頃まで生き残っていそうなので），決め打ちはしないようにしました．

```Fortran
    integer(int32) :: dgt_digits  ! 整数numの桁数num_digitsの桁数

    dgt_digits = get_digits_of(num_digits)
```

## 整数の桁数を文字列に変換する
整数`num`の桁数を文字列に変換します．このとき，書式を`I0`としておけば，自動的に`num`の桁数を認識してくれます．
内部`write`文に用いる文字列変数は，あらかじめ割り付けておく必要があります．

```Fortran
    character(:),allocatable :: str_digits

    allocate(character(dgt_digits)::str_digits)
    write(str_digits,'(I0)') num_digits
```

## 書式を作成する
整数`num`の桁数の文字列を連結して，書式を作ります．これは代入演算ができるので，書式用の文字列`fmt`を`allocate`しておく必要はありません．

```Fortran
    character(:),allocatable :: fmt

    fmt = "(I"//str_digits//"."//str_digits//")"
```

## 整数`num`を文字列に変換する
ようやく準備ができたので，内部`write`文で整数を文字列に変換します．

```Fortran
    allocate(character(num_digits)::str)
    write(str,fmt) num
```

## 実行結果
作成した関数に4バイト整数の最大値を渡し，2桁追加して文字列に変換したところ，正しく実行されました．

```Fortran
print *, int32_to_string(huge(0_int32)) ! 002147483647
```

# 関数全景

```Fortran
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`文でも（代入以外でも）自動で割り付けるようになってほしいですね．