2023年 春期 応用情報技術者試験 問3

多倍長整数の演算

コンピュータが一度に処理できる整数の最大桁には、CPUが一度に扱える情報量に依存した限界がある。一度に扱える桁数を超える演算を行う一つの方法として、10を基数とした多倍長整数(以下、多倍長整数という)を用いる方法がある。

【多倍長整数の加減算】

多倍長整数の演算では、整数の桁ごとの値を、1の位から順に1次元配列に格納して管理する。例えば整数123は、要素数が3の配列に(3,2,1)を格納して表現する。

多倍長整数の加算は、"桁ごとの加算"の後、"繰り上がり"を処理することで行う。

456+789を計算した例を図1に示す。

図1 456+789を計算した例

"桁ごとの加算"を行うと、配列の内容は{15,13,11}となる。1の位は15になるが、15は10×1+5なので、10の位である13に1を繰り上げて{5,14,11}とする。

これを最上位まで繰り返す。最上位で繰り上がりが発生する場合は、配列の要素数を増やして対応する。減算も同様に"桁ごとの減算"と"繰り下がり"との処理で計算できる。

【多倍長整数の乗算】

多倍長整数の乗算については、計算量を削減するアルゴリズムが考案されており、その中の一つにカラツバ法がある。ここでは、桁数が2のべき乗で、同じ桁数をもった正の整数同士の乗算について、カラツバ法を適用した計算を行うことを考える。桁数が2のべき乗でない整数や、桁数が異なる整数同士の乗算を扱う場合は、上位の桁を0で埋めて処理する。例えば、123×4は0123×0004として扱う。

【ツリー構造の構築】

カラツバ法を適用した乗算のアルゴリズムは、計算のためのツリー構造(以下、ツリーという)を作る処理と、ツリーを用いて演算をする処理から成る。ツリーは、多倍長整数の乗算の式を一つのノードとし、一つのノードは3個の子ノードをもつ。

M桁×M桁の乗算の式について、乗算記号の左右にある値を、それぞれM/2桁ずつに分けてA,B,C,Dの四つの多倍長整数を作る。これらの整数を使って、①A×C,②B×D,③(A+B)×(C+D)の3個の子ノードを作り、M/2桁×M/2桁の乗算を行う層を作る。(A+B),(C+D)は多倍長整数の加算の結果であるが、ここでは"桁ごとの加算"だけの処理を行い、"繰り上がり"の処理はツリーを用いて行う演算の最後でまとめて行う。生成した子ノードについても同じ手順を繰り返し、1桁×1桁の乗算を行う最下層のノードまで展開する。

1234×5678についてのツリーを図2に示す。図2の層2の場合、①は12×56,②は34×78,③は46×134となる。③の(C+D)は、"桁ごとの加算"だけの処理を行うと、10の位が5+7=12,1の位が6+8=14となるので、12×10+14=134となる。

図2 1234×5678についてのツリー

【ツリーを用いた演算】

ツリーの最下層のノードは、整数の乗算だけで計算できる。最下層以外の層は、子ノードの計算結果を使って、次の式で計算できることが分かっている。ここで、α,β,γは、それぞれ子ノード①,②,③の乗算の計算結果を、Kは対象のノードの桁数を表す。

α×10^K + (γ - α - β)×10^(K/2) + β ……(1)

図2のルートノードの場合、K=4,α=672,β=2652,γ=6164なので、計算結果は次のとおりとなる。

672×10000+(6164-672-2652)×100+2652=7006652

【多倍長整数の乗算のプログラム】

桁数が2のべき乗の多倍長整数val1,val2の乗算を行うプログラムを作成した。

プログラム中で利用する多倍長整数と、ツリーのノードは構造体で取り扱う。構造体の型と要素を表1に示す。構造体の各要素には、構造体の変数名,要素名でアクセスできる。また、配列の添字は1から始まる。

表1 構造体の型と要素
構造体の型要素名要素の型内容
多倍長整数N整数多倍長整数の桁数
values整数の配列桁ごとの値を管理する1次元配列。1の位の値から順に値を格納する。配列の要素は、必要な桁を全て格納するのに十分な数が確保されているものとする。
ノードN整数ノードが取り扱う多倍長整数の桁数。図2の1234×5678のノードの場合は4である。
val1多倍長整数乗算記号の左側の値
val2多倍長整数乗算記号の右側の値
result多倍長整数乗算の計算結果

多倍長整数の操作を行う関数を表2に、プログラムで使用する主な変数,配列及び関数を表3に,与えられた二つの多倍長整数からツリーを構築するプログラムを図3に,そのツリーを用いて演算を行うプログラムを図4に,それぞれ示す。表2,表3中のp,q,v1,v2の型は多倍長整数である。また,図3,図4中の変数は全て大域変数である。

表2 多倍長整数の操作を行う関数
名称内容
add(p, q)多倍長整数pとqについて、"桁ごとの加算"を行う。
carry(p)多倍長整数pについて"繰り上がり","繰り下がり"の処理を行う。
left(p, k)多倍長整数pについて、valuesの添字が大きい方のk個の要素を返す。pのvaluesが{4, 3, 2, 1}、kが2であれば、valuesが{2, 1}の多倍長整数を返す。
right(p, k)多倍長整数pについて、valuesの添字が小さい方のk個の要素を返す。pのvaluesが{4, 3, 2, 1}、kが2であれば、valuesが{4, 3}の多倍長整数を返す。
tradd(p, k)多倍長整数add(left(p, k), right(p, k))の結果を返す。
shift(p, k)多倍長整数pを10^k倍する。
sub(p, q)多倍長整数pとqについて、"桁ごとの減算"を行いp-qを返す。
表3 使用する主な変数,配列及び関数
名称種類内容
elements[]配列ノードツリーのノードを管理する配列。ルートノードを先頭に、各層の左側のノードから順に要素を格納する。図2の場合は、{1234×5678, 12×56, 34×78, 46×134, 1×5, 2×6, ...}の順に格納する。
layer_top[]配列整数ルートノードから順に、各層の左端のノードの、elements配列上での添字を格納する。図2の場合は1234×5678, 12×56, 1×5の添字に対応する{1, 2, 5}が入る。
mod(m, k)関数整数mをkで割った剰余を整数で返す。
new_elem(k,v1,v2)関数ノード取り扱う多倍長整数の桁数がkで、v1×v2の乗算を表すノード構造体を新規に一つ作成して返す。
pow(m, k)関数整数mのk乗を整数で返す。kが0の場合は1を返す。
t_depth変数整数ツリーの層数。図2の場合は3である。
val1, val2変数多倍長整数乗算する対象の二つの値。図2の場合、ルートノードの二つの値で、val1は1234, val2は5678である。
answer変数多倍長整数乗算の計算結果を格納する変数
// ツリーの各層の、elements配列上での先頭インデックスを算出する
layer_top[1] ← 1                               // ルートノードは先頭なので1を入れる
for (iを1からt_depth − 1まですずつ増やす)
    layer_top[i + 1] ← layer_top[i] + 
endfor
// ツリーを構築する
elements[1] ← new_elem(val1.N, val1, val2)      // ルートノードを用意。桁数はval1の桁数を使う
for (dpを1からt_depth − 1まですずつ増やす)     // ルートノードの層から、最下層以外の層を順に処理
  for (iを1からpow(3, dp − 1)まですずつ増やす) // 親ノードになる層の要素数だけ繰り返す
    pe ← elements[layer_top[dp] + (i − 1)]  // 親ノードを取得
    cn ← pe.N / 2                          // 子ノードの桁数を算出 
    tidx ← layer_top[dp + 1] +     // 子ノード①へのインデックス
    elements[tidx    ] ← new_elem(cn, left(, cn), left(, cn))
    elements[tidx + 1] ← new_elem(cn, right(, cn), right(, cn))
    elements[tidx + 2] ← new_elem(cn, tradd(, cn), tradd(, cn))
  endfor
endfor
図3 与えられた二つの多倍長整数からツリーを構築するプログラム
// 最下層の計算
for (iを1からpow(3, t_depth − 1)まですずつ増やす)     // 最下層の要素数は3^(t_depth-1)個
    el ← elements[layer_top[t_depth] + (i − 1)]       // 最下層のノード
    mul ← el.val1.values[1] * el.val2.values[1]       // 最下層の乗算
    el.result.N ← 2                                   // 計算結果は2桁の多倍長整数
    el.result.values[1] ←                // 1の位
    el.result.values[2] ← mul / 10                    // 10の位
endfor
// 最下層以外の計算
for (dpをt_depth − 1から1まですずつ減らす)          // 最下層より一つ上の層から順に処理
    for (iを1からpow(3, dp − 1)まですずつ増やす)     // 各層の要素数だけ繰り返す
        el ← elements[layer_top[dp] + (i − 1)]        // 計算対象のノード -->
        cidx ← layer_top[dp + 1] +       // 子ノード①へのインデックス
        s1 ← sub(, el.result, .result )  // γ − α − β を計算
        s2 ← sub(s1, elements[cidx + 1].result)        // α × 10^K を計算
        p1 ← shift(elements[cidx], result, el.N)       // β を計算
        p2 ← shift(s2, el.N / 2)                       // (γ − α − β) × 10^(K/2) を計算
        p3 ← elements[cidx + 1].result                 // β を計算
        el.result ← add(add(p1, p2), p3)
    endfor
endfor
// 繰り上がり処理
answer ← carry(elements[1].result)                   // 計算結果をanswerに格納
図4 ツリーを用いて演算を行うプログラム
注記:図4中のエには、図3中のエと同じ字句が入る。
出典:令和5年度 春期 応用情報技術者試験 午後 問3