2000 年度「計算機基礎論3B」 関数を自作してみよう

C言語で、簡単な関数を自作してみましょう。

mod N の四則

N をひとつの自然数とするとき、 数学での mod N の四則については知っているものとします。 Z/NZの元を格納するための特別な型はC言語にはないので、 (さしあたっては)int で代用し、 簡単なプログラムを書いてみましょう。

== modadd.c ==========================================================
#include <stdio.h>

main() {
    int i, j, n;

    printf("mod N での足し算の表を出力するプログラムです.\n");
    printf("1 以上 25 以下の正の数 N を入力してください.\n");
    scanf("%d", &n);

    printf("   |");                     /* 罫線を引く */
    for (j=0; j<n; j++) {               /* 上の「見出し」*/
        printf("%3d", j);
    }
    printf("\n");                       /* putchar('\n'); でも同じ */
    printf("---+");                     /* 罫線を引く */
    for (j=0; j<n; j++) {
        printf("---");
    }
    printf("-\n");                      /* ここまで罫線 */

    for (i=0; i<n; i++) {               /* ここから計算 */
        printf("%2d |", i);             /* 左の「見出し」と罫線 */
        for (j=0; j<n; j++) {
            printf("%3d", (i+j) % n);
        }
        printf("\n");
    }
}
======================================================================
ユーザが 1 から 25 までの正の数 N を入力すると mod N の足し算の表を出力します。

※ プログラムの中では N を格納する変数は n にした。 変数名は小文字にするのが普通だから。

実際の計算をしているのは二重になった最後の for ループだけで、 それ以外の部分は罫線をひくことに費やされています。

実行すると次のようになります。

    mod N での足し算の表を出力するプログラムです.
    1 以上 25 以下の正の数 N を入力してください.
    5
       |  0  1  2  3  4
    ---+----------------
     0 |  0  1  2  3  4
     1 |  1  2  3  4  0
     2 |  2  3  4  0  1
     3 |  3  4  0  1  2
     4 |  4  0  1  2  3

※ 25 までなのは、 それ以上だと一行が 80 字以内に収まらないから、 というだけの理由。

上の出力結果では各行各列に数が重複なく並んでいますが、 「(i+j) % n」を「(i*j) % n」と変えて掛け算の表にしてみるとどうでしょうか?

※ というのは数学の問題として聞いている質問。

引き算の表がどうなるかは足し算と同じぐらい自明かもしれませんが、 プログラムを書く上では問題があります。 JIS のC言語の規格では / や % について

整数同士の除算で割り切れない場合、(中略) 一方のオペランドが負の値をもつ場合、 / 演算子の結果が代数的な商以下の最大の整数とするか、 又は代数的な商以上の最小の整数とするかは、処理系定義とし、 % 演算子の結果の符号も処理系定義とする
としているからです。

※「オペランド」とは a/b の a や b のこと、 「処理系定義」とは(簡単にいえば)コンピュータごとに決めてください、 ということ。

つまり、(-2) % 3 は -2 かも知れないし 1 かも知れない、 ということです。

実習室の WS では (-2) % 3 は -2 になるので、 答えを 0, 1, ..., N-1 の範囲にしようとすると一工夫必要です。 tmp を int 型の変数として

            tmp = (i-j) % n;
            if (tmp < 0) {
                tmp = tmp + n;
            }
            printf("%3d", tmp);   
とするか
            printf("%3d", ((i-j) % n + n) % n);  
とするか。 ほかにうまい方法があるかもしれません。 考えてみてください。

オプション課題

mod n で a を b で割った“商”を返す関数 int moddiv(int a, int b, int n) を書いてください。 言い方を変えれば、 この関数の返す値 x は 0 から n-1 までの整数であり b*x と a は mod n で等しい、 ということです。 n は自然数、 a は任意の整数、 b は 0 以外の任意の整数とします。 n が正であることおよび b が 0 でないことはこの関数を呼び出すほうが注意する事項と考えることにします。 関数の中でチェックするには及びません。 x に二つ以上の候補がある場合は最も小さいものを返してください。 また、もしもそのような x が存在しなければ -1 を返してください。

この問題は不定方程式 b*x + n*y = a を解くことにあたります。 うまい方法もありますが、 ここでは「x = 0 から x = n-1 まで順番に試す」 というやり方で構いません。

必ずお試し用 main() をつけて、 コンパイルして動作を確かめてから提出してください。 また、 main() がどんなことをするプログラムかは各自で違いますから、 私にわかるように

などしてください。どれか一つで結構です。 なお、 関数 moddiv() の仕様は上に書いた通りなので説明をつけなくてもいいですが、 興味のある人は簡単にコメントをつける練習もしてみてください。 上で説明した「x = 0 から x = n-1 まで順番に試す」 以外の方法を使った人は必ずそのやり方を書いてください。

できあがったら、プログラムをメールで送ってください。 出力例はいりません。 オプション課題なので、期限は特に決めません。 また、メールの Subject は適当につけて、 メール本文でどのオプション課題に対するレポートなのか説明してください。

おまけ

mod N で a, a*a, a*a*a, ... を出力するプログラムもおもしろいです。 「原始根」と関係がありますが、 「原始根」については適当な初等整数論の教科書を見てください。


岩瀬順一