徒然

思いついたら書きます

AtCoderで始めるCOBOL入門 ~演算・関数編~

はじめに

前回の記事ではCOBOLの概要や入出力の方法を記載しました。
本記事はその続きで、COBOLの演算文法や関数を記載します。
前回記事同様、目的はAtCoderの問題を解くことなので、解くのに必要ない仕様についてはほぼ触れません。
また筆者はにわかコボラーなため、この記事には嘘も書いてあるかもしれません。ご承知おきください。

演算

代入

他の言語でいうANS = ACOBOLにおいて、以下の2通りで書くことができます。

           COMPUTE ANS = A.
           MOVE A TO ANS.

なおMOVE文は代入対象が複数存在しても良いです。

           MOVE A TO ANS1 ANS2 ANS3.

加算

他の言語でいうANS = A + Bは、ANSが0の場合*1以下の3通りで書くことができます。

           COMPUTE ANS = A + B.
           ADD A B TO ANS.
           ADD A TO B GIVING ANS.

他の言語に触れた方は1つ目の書き方がわかりやすいのではないかと思います。
いちいちCOMPUTEと書く必要があるのが癪ですが……
同様に他の言語でいうANS += Aは以下の通りになります。

           COMPUTE ANS = ANS + A.
           ADD A TO ANS.
           ADD A TO ANS GIVING ANS.

この場合は2つ目がわかりやすいかなと思いますが後は好みです。
なお3つ目の書き方はギャグなので*2極力避けましょう。

減算

減算の書き方は加算のときと同様です。

ANS = A - B

           COMPUTE ANS = A - B.
           SUBTRACT B FROM A GIVING ANS.

ANS -= A

           COMPUTE ANS = ANS - A.
           SUBTRACT A FROM ANS.

乗算

ANS = A * B

           COMPUTE ANS = A * B.
           MULTIPLY A BY B GIVING ANS.

ANS *= A

           COMPUTE ANS = ANS * A.
           MULTIPLY A BY ANS.

除算

除算はデフォルトで切り捨てになります。
切り捨てられる桁数は受け取り項目の有効桁の1つ下です。
なお書き方はBYINTOの2通りがあるのですが、BYが直感的っぽい代わりにGIVINGが必須となります。

ANS = A / B

           COMPUTE ANS = A / B.
           DIVIDE B INTO A GIVING ANS.
           DIVIDE A BY B GIVING ANS.

ANS /= A

           COMPUTE ANS = ANS / A.
           DIVIDE A INTO ANS.
           DIVIDE ANS BY A GIVING ANS.

なお四捨五入するときは、四捨五入する変数名の後ろにROUNDEDをつけます。
(後者ではANS2のみ四捨五入される)

           COMPUTE ANS ROUNDED = A / B.
           DIVIDE B INTO A GIVING ANS1 ANS2 ROUNDED ANS3.

余りも出したいときはREMAINDERをつけます。

           DIVIDE B INTO A GIVING ANS REMAINDER REM.
           DIVIDE A BY B GIVING ANS REMAINDER REM.

べき乗

大抵の言語と同じです。英字の算式は(多分)ありません。

ANS = A ** B

           COMPUTE ANS = A ** B.

IF文

           IF <条件> [THEN]
               <処理1>
           [ELSE
               <処理2>]
           END-IF.

条件ANDORなどで複数条件を連結可能です。また同値は=であり、==と書く必要はありません。
THENは書いても書かなくてもどちらでも良いです。
処理1は必須なため、ELSE部のみ動かしたい場合はCONTINUEまたはNEXT SENTENCEと書く必要があります。

なおIF文内は.書いてはいけません
なぜなら昔はEND-IF文がなく、ピリオド(.)があればIF文終了とみなされていたからです。
COBOLはBASICより昔から存在する言語であり、当初は多重IF文が考慮されていなかったため*3こんな仕様になっています。
そのため.を書くと、それより前に書かれていた全てのIF文(と後述するPERFORM文)が終了してしまいます。
こんなクソ仕様は早くどうにかしてほしいですがレガシー言語を切り捨てられない我々のほうが悪いです本当にありがとうございました。

またELSE IFという高尚な関数はCOBOLには存在しません(!)。
一応ELSEの直後にIFを書くことで擬似ELSE IFを作り上げることができるのですが、最後にネスト数に対応したEND-IFを書く必要があります。

           IF COND1 THEN
               DISPLAY "1"
           ELSE IF COND2 THEN
               DISPLAY "2"
           ELSE IF COND3 THEN
               DISPLAY "3"
           ELSE
               DISPLAY "4"
           END-IF
           END-IF
           END-IF.

ループ

COBOLでループをするときはPERFORM文を使います。
なおIF文同様に、PERFORM文内は.書いてはいけません
またEXIT PERFORMと記述することで直近のPERFORM文から抜け出すことができます。

回数指定ループ

繰り返したい回数だけPERFORM N TIMESと記述します。

           PERFORM <回数> TIMES
               <処理>
           END-PERFORM.

例:

           PERFORM 10 TIMES
               DISPLAY "pizza"
           END-PERFORM.

UNTIL文

他言語でよく使われるであろうwhile文とは逆で、条件を満たすまでループします。

           PERFORM UNTIL <条件>
               <処理>
           END-PERFORM.

例:

           PERFORM UNTIL I > N
               ADD 1 TO I
           END-PERFORM.

カウンタを使うループ

一番よく使うであろう、カウンタをインクリメント/デクリメントしていくタイプのループです。

           PERFORM VARYING <カウンタ> FROM <初期値> 
           BY <変動値> UNTIL <条件>
               <処理>
           END-PERFORM.

例:

           PERFORM VARYING I FROM 1 BY 1 UNTIL I > N
               DISPLAY A(I)
           END-PERFORM.

SORT

SORT文は以下の通りに書きます。
ASCENDINGを指定すると昇順で、DESCENDINGを指定すると降順でソートされます。

           SORT <配列> ON (ASCENDING|DESCENDING) KEY <ソート基準データ名>
           [GIVING <出力対象>].

例:

           01 AL.
               03 AI OCCURS 100 TIMES.
                   05 A PIC 9(5).
           (中略)
           SORT AI ON DESCENDING KEY A.

文字列切り抜き

COBOLで文字列から文字を切り出すには、文字(開始文字位置:文字数)と書きます。
配列と同様1-indexedです。

           MOVE "AtCoder" TO S.
           DISPLAY S(2:3).
*>    *    -> "tCo"

FUNCTION

COBOLで関数を使う場合はFUNCTION <関数名>(対象)と記載します。

TRIM

両端のスペースを削除します。

           MOVE FUNCTION TRIM("  TRIM     ") TO S.
           DISPLAY S.
*>    *    -> "TRIM"

LENGTH

LENGTHは文字列の長さを返します。
ただし、そのまま変数に使うと変数の長さがそのまま返されます*4

           01 S PIC X(20).
*>    *    (中略)
           MOVE FUNCTION TRIM("AtCoder") TO S.
           DISPLAY S.
*>    *    -> "AtCoder             "
           DISPLAY FUNCTION LENGTH(S).
*>    *    -> 100

対策としてSTORED-CHAR-LENGTHを適用するか、TRIM関数を適用したものにLENGTH関数を適用する必要があります。

           DISPLAY FUNCTION STORED-CHAR-LENGTH(S).
*>    *    -> 00000007
           DISPLAY FUNCTION LENGTH(FUNCTION TRIM(S)).
*>    *    -> 00000007

その他

他にもAtCoderで使えそうな関数を、こちらのサイトから独断と偏見で抜粋しました。

関数 引数 意味
ABS number 絶対値を返す
ACOS cosine アークコサインを返す(ラジアン)
ASIN sine アークサインを返す(ラジアン)
ATAN tangent アークタンジェントを返す(ラジアン)
CHAR integer integerに対応した文字を返す
CONCATENATE string-1 [, string-2 ]... 結合した文字列を返す
COS angle コサインを返す(angleはラジアン)
E ネイピア数を返す
EXP10 number 10 ** numberを返す
FRACTION-PART number 小数部を返す
FACTORIAL number 階乗を返す
INTEGER number number以下の最大の整数を返す
LENGTH string 文字列の長さを返す
LOG number 自然対数を返す
LOG10 number 常用対数を返す
LOWER-CASE string stringをすべて小文字にしたものを返す
MAX number-1 [, number-2 ]... 最大値を返す
MEAN number-1 [, number-2 ]... 平均値を返す
MEDIAN number-1 [, number-2 ]... 中央値を返す
MIN number-1 [, number-2 ]... 最小値を返す
MOD value, modulus value/modulusの余りを返す
NUMVAL string 文字列を数値に変換して返す
ORD char charに対応した文字コードを返す
PI 円周率を返す
RANGE number-1 [, number-2 ]... MAX - MIN を返す
REM number, divisor number/divisorの余りを返す
REVERSE string stringの反転を返す
SIN angle サインを返す(angleはラジアン)
SQRT number 正の平方根を返す
STORED-CHAR-LENGTH string 末尾スペースを覗いたstringの長さを返す
SUBSTITUTE string, from-1, to-1 [, from-n, to-n ]... stringを置換する
SUBSTITUTE-CASE string, from-1, to-1 [, from-n, to-n ]... 大文字小文字を区別しないSUBSTITUTE
SUM number-1 [, number-2 ]... 合計を返す
TAN タンジェントを返す(angleはラジアン)
TRIM string [, LEADING | TRAILING ] 両端(または第2引数)のスペースを削除したstringを返す
UPPER-CASE string stringをすべて大文字にしたものを返す

最後に

ここまでCOBOLの概要や入出力について触れてきました。
ここまでの知識でAtCoderの問題をある程度解けるようになっているため、次の記事ではAtCoder Beginners Selectionの問題を解いていきます

*1:ANS!=0のとき2つ目が異なる計算結果になる

*2:https://ja.wikipedia.org/wiki/COBOL#COBOL%E3%81%AE%E3%82%A8%E3%83%94%E3%82%BD%E3%83%BC%E3%83%89

*3:https://ksmakoto.hatenadiary.com/entry/2015/06/27/181700

*4:COBOLは文字列の長さに満たない分をスペースで埋めるため