🦾

Fortranのintegerの上限値は?オーバーフローって?メモリ消費の変化がすごかった!

2022/12/23に公開

この記事はFortranアドベントカレンダー 2022 23日目の記事です。

作者は何者

大学院の物理系でスパコンを使ったプラズマの粒子シミュレーションをしています。今年から博士課程として進学して再び物理の道を志すことになりました。こんな記事も書いてます。

https://bluepost69.hatenablog.com/entry/20221221/1671629346

1. integerって誰?調べてみました!

integer型はFortranで基本的な型の一つです。見たことのある方も多いと思います。

C/C++をやったことのある人なら知っているかもしれませんが、実はinteger型には何種類かちがいがあります。integer型の宣言時にはkindというオプションがあり、このkindによってメモリ消費の様子が変わるのです。

type サイズ 値の下限 値の上限 C/C++
integer(kind=2) 2 Byte -32,768 32,767 int16_t
integer(kind=4) 4 Byte -2,147,483,648 2,147,483,647 int32_t
integer(kind=8) 8 Byte -2^31 2^31-1 int64_t

デフォルトではinteger(kind=4)となることが多いようですが、処理系依存だそうです。ちなみに

integer(8)はコンパイラによっては8Byteを意味するとは限らない

といった記述がまれに見られますが、これはkindをつけなければ「長さ8の配列」を意味する可能性があるという意味で、kindを省略しなければByteを意味すると考えて差し支えないと思います。

補足:@curerice2014さんに教えてもらいましたが、ごく一部の処理系ではバイトと対応していないケースがあるそうです。Cと組み合わせる場合などにひっかけられそうですね。

手元の環境で試してみましたが、Cray/9.0.2, Intel/19.0.3, GNU/8.3.0では同じ結果になったので、バイトと対応しないケースは例外的だと思います。

2. オーバーフローって

integer型は上限があるのでそれを超えるとオーバーフローが生じます。

なんとCrayだとオーバーフローしない場合があります。下記のコードを見てください。

program main
    implicit none
  
    integer :: a
    integer :: b
    integer :: result

    a = 2**30
    b = 2**3
    result = a*b

    write(6,*) "a=",a
    write(6,*) "1) ",result/real(2**30)
    write(6,*) "2) ", real(a*2**3)/real(2**30)
end program main

手計算するとa*b = 2**33となるので当然resultはオーバーフローしておかしな値が入ります。1)と2)とアウトプットを設けていますが、どちらにしても同じインチキな値が入るはずで、事実intelやGNUのコンパイラではそうなります。

intel
 a=  1073741824
 1)   0.0000000E+00
 2)   0.0000000E+00

しかしCrayのコンパイラでは1)と2)は違う値を出力します。

cray
 a= 1073741824
 1)  0.
 2)  8.

理由ははっきりとはわかりませんが、おそらく式評価の順番によるものかと思います。ちなみにこの未定義動作は下記のように条件を変えると発動しません(0が出力される)。

  • real(2**5)を外すと2)の出力も0になる。
  • 2e0のようなrealのリテラル値で割ると正しい値が出力される。
  • 2d0のようにdoubleのリテラル値で割ると0になる。

未定義動作は奥が深いですね。

3. メモリ消費の変化がすごかった!

実際に消費メモリを計測してみましょう。

Fortranに限らず、Linuxではメモリの消費量は/proc/meminfoという特殊なファイルを読み込むことで取得することができます。

まとめ

いかがでしたか?

デフォルトであるkind=4のintegerは結構現実的な値でオーバーフローしてしまうので、使うときはちゃんと上限を超えないようにしましょう。特にかけ算とかで桁がどんどん上がって知らないうちにオーバーしてしまうことがあるので、不安であればinteger(kind=8)や厳密さが不要であればdouble precisionを使いましょう。

参考

Discussion