応用情報技術者 2023年 春期 午後 問03
多倍長整数の演算に関する次の記述を読んで、設問に答えよ。
コンピュータが一度に処理できる整数の最大桁には、CPUが一度に扱える情報量に依存した限界がある。一度に扱える桁数を超える演算を行う一つの方法として、10を基数とした多倍長整数(以下、多倍長整数という)を用いる方法がある。
〔多倍長整数の加減算〕
多倍長整数の演算では、整数の桁ごとの値を、1の位から順に1次元配列に格納して管理する。例えば整数123は、要素数が3の配列に{3, 2, 1}を格納して表現する。
多倍長整数の加算は、“桁ごとの加算”の後、“繰り上がり”を処理することで行う。456+789を計算した例を図1に示す。

“桁ごとの加算”を行うと、配列の内容は{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となる。
〔ツリーを用いた演算〕
ツリーの最下層のノードは、整数の乗算だけで計算できる。最下層以外の層は、子ノードの計算結果を使って、次の式で計算できることが分かっている。ここで、α、β、γ は、それぞれ子ノード①、②、③の乗算の計算結果を表す。Kは対象のノードの桁数を表す。
……(1)
図2のルートノードの場合、K=4、α=672、β=2652、γ=6164なので、計算結果は次のとおりとなる。
〔多倍長整数の乗算のプログラム〕
桁数が2のべき乗の多倍長整数val1、val2の乗算を行うプログラムを作成した。
プログラム中で利用する多倍長整数と、ツリーのノードは構造体で取り扱う。構造体の型と要素を表1に示す。構造体の各要素には、構造体の変数名.要素名でアクセスできる。また、配列の添字は1から始まる。

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


設問1:
問題文を見る模範解答
ア:3×7
イ:4×12
解説
解答の論理構成
-
図2の層2のノード「34×78(B×D)」を分割
【問題文】では、子ノード生成の手順を
「①A×C、②B×D、③(A+B)×(C+D) の3個の子ノードを作り…」
と示しています。また分割は “M/2桁” 単位で行い、桁ごとの加算では繰り上がりをしません。- 34のvalues配列は {4, 3}、左1桁が3、右1桁が4。
→ A=3、B=4 - 78のvalues配列は {8, 7}、左1桁が7、右1桁が8。
→ C=7、D=8
よって子ノード①は3×7、②は4×8、③は (3+4)×(7+8) となります。
図中で②・③は既に表示されているので、空欄 ア には①の3×7が入ります。
- 34のvalues配列は {4, 3}、左1桁が3、右1桁が4。
-
図2の層2のノード「46×134(((A+B)×(C+D)))」を分割
- 46のvalues配列は {6, 4}、左1桁が4、右1桁が6。
→ A=4、B=6 - 134は “繰り上がり前” の結果なのでvalues配列は {14, 12}
(1の位が14, 10の位が12で桁数は2のまま)。
→ C=12、D=14
子ノード①は4×12、②は6×14、③は (4+6)×(12+14)=10×26です。
図中で②・③は表示済みのため、空欄 イ には①の4×12が入ります。
- 46のvalues配列は {6, 4}、左1桁が4、右1桁が6。
-
以上より
ア:3×7
イ:4×12
誤りやすいポイント
- “桁ごとの加算”後に繰り上がりをしないため、134のように2桁のまま値が10以上になるケースを見落としやすいです。
- values配列は下位桁が添字1である点を忘れ、left(p, 1) とright(p, 1) を取り違えるとA・B、C・Dの割り当てが逆になります。
- Karatsuba法の子ノード①②③の順序(A×C、B×D、(A+B)×(C+D))を覚え違えると、図の空欄に別の式を書いてしまいます。
FAQ
Q: “134” が3桁に見えるのに桁数2のまま扱われるのはなぜですか?
A: 【問題文】にあるとおり、(C+D) は “桁ごとの加算” だけを行い “繰り上がり” を後回しにするため、10の位に12, 1の位に14を保持したまま2桁で管理します。
A: 【問題文】にあるとおり、(C+D) は “桁ごとの加算” だけを行い “繰り上がり” を後回しにするため、10の位に12, 1の位に14を保持したまま2桁で管理します。
Q: 子ノード③の (A+B)×(C+D) の計算結果はいつ繰り上がり処理をしますか?
A: ツリー全体の計算が終わったあと、図4の最後でcarry() を呼び出すタイミングで一括して繰り上げ・繰り下げを行います。
A: ツリー全体の計算が終わったあと、図4の最後でcarry() を呼び出すタイミングで一括して繰り上げ・繰り下げを行います。
Q: 左右で桁数が異なるときはどう考えますか?
A: 【問題文】に「上位の桁を0で埋めて処理する」とあるとおり、短い方を0埋めして同じ桁数(2のべき乗)にそろえてから分割します。
A: 【問題文】に「上位の桁を0で埋めて処理する」とあるとおり、短い方を0埋めして同じ桁数(2のべき乗)にそろえてから分割します。
関連キーワード: 多倍長整数, カラツバ法, 桁ごとの加算, 繰り上がり処理, ツリー構造
設問2:
問題文を見る図2中の層2にある46×134のノードについて、本文中の式(1)の数式は具体的にどのような計算式になるか。次の式の①~④に入れる適切な整数を答えよ。
( ① ) × 100 + (( ② ) - ( ③ ) - 84) × 10 + ( ④ )
模範解答
①:48
②:260
③:48
④:84
解説
解答の論理構成
-
カラツバ法の子ノード作成
【問題文】には
「乗算記号の左右にある値を、それぞれM/2桁ずつに分けて A、B、C、D の四つの多倍長整数を作る」
とある。
ノード「46×134」はM=2(values配列の要素数が2)なのでM/2=1。- 46(values={6,4})
- A=left(46,1)=4
- B=right(46,1)=6
- 134(values={14,12})
- 134は 56+78 の“桁ごとの加算”結果であり、繰り上がり未処理なので
下位桁が 14、上位桁が 12 で2桁扱いになる点に注意。 - C=left(134,1)=12
- D=right(134,1)=14
- 134は 56+78 の“桁ごとの加算”結果であり、繰り上がり未処理なので
- 46(values={6,4})
-
各子ノードの計算
同一ノード内での子ノードは
「① A×C、② B×D、③ (A+B)×(C+D)」
と定義されている(【問題文】より)。- ① …… これが α
- ② …… これが β
- ③
- (繰り上がりはまだ行わない)
- よって …… これが γ
-
ルート式への代入
ツリー計算では【問題文】の式
α×10^k + (γ-α-β)×10^{k/2} + β ……(1)
を各ノードに適用する。
今回はk=2(2桁)なので
。
したがって
に
α=48, γ=260, α=48, β=84を代入すると
となり、設問の空欄は
①48, ②260, ③48, ④84で確定する。
誤りやすいポイント
- 「桁ごとの加算」をした直後の多倍長整数では 要素が9を超えてもそのまま にするルールを忘れ、134を「1・3・4」と思い込んで分割を誤る。
- M/2桁で分けるときに 配列添字の大きい方が“左” になることを取り違え、A・BとC・Dを逆に設定してしまう。
- (γ-α-β) の計算順序を勘違いし、(α+β-γ) にしてしまう。
FAQ
Q: なぜ134の下位桁が14になるのですか?
A: 134は56と78を “桁ごとの加算” だけで足した結果です。1の位は6+8=14、10の位は5+7=12となり、繰り上がりは最後まで行わないためvalues配列は {14,12} になります。
A: 134は56と78を “桁ごとの加算” だけで足した結果です。1の位は6+8=14、10の位は5+7=12となり、繰り上がりは最後まで行わないためvalues配列は {14,12} になります。
Q: 子ノードの数が3個なのはなぜですか?
A: カラツバ法では2つの乗算を3つに分割して計算量を削減するため、各ノードは必ず①A×C、②B×D、③(A+B)×(C+D) の3子ノードを生成します。
A: カラツバ法では2つの乗算を3つに分割して計算量を削減するため、各ノードは必ず①A×C、②B×D、③(A+B)×(C+D) の3子ノードを生成します。
Q: kとk/2の10乗を掛ける理由は?
A: ①と②で求めた部分積は元の桁位置に戻す必要があります。高位の部分積 α は10^k倍、中央の調整項 (γ-α-β) は10^{k/2} 倍、低位の部分積 β はそのまま加えることで元の桁揃えを実現します。
A: ①と②で求めた部分積は元の桁位置に戻す必要があります。高位の部分積 α は10^k倍、中央の調整項 (γ-α-β) は10^{k/2} 倍、低位の部分積 β はそのまま加えることで元の桁揃えを実現します。
関連キーワード: カラツバ法, 桁ごとの加算, 多倍長整数, 繰り上がり処理
設問3:
問題文を見る模範解答
ウ:pow(3,i-1)
エ:3*(i-1)
オ:pe.val1
カ:pe.val2
解説
解答の導き方
まず問題文から次が読み取れます。ツリーは「一つのノードは3個の子ノードをもつ」とあり、elements[] 配列には「ルートノードを先頭に、各層の左側のノードから順に要素を格納する」とあります。さらにlayer_top[] は「ルートノードから順に、各層の左端のノードの、elements配列上での添字の値を格納する」とあります。これらを手がかりに各空欄を決めます。
- ウ:pow(3,i-1) の理由
- 各層のノード数はルート層が1個で、1つ上の層の各ノードが3個の子ノードを持つため、層番号を とすればその層の要素数は です。
- layer_top[i+1] に入れる値は「次の層の左端の添字」なので、現在の層の要素数を足す必要があります。ループ変数がiのとき、現在の層の要素数は です。
- pow(m,k) が整数のべき乗を返す関数であることから、コード表記ではpow(3,i-1) が適切です。
- 検算:図2の例ではlayer_topが {1,2,5} になるので、1 + pow(3,0) = 2、2 + pow(3,1) = 5と一致します。
- エ:3*(i-1) の理由
- 図3内の内側ループは親ノードごとに処理を行い、各親から3個の子ノードを生成します。elements[] は層ごとに左から順に格納するため、次の層でのi番目の親の子ノードは「次の層の左端」から過去の親が作った子ノード分だけ離れた位置に置かれます。
- 1番目の親(i=1)の子ノードはオフセット0から始まる必要があるので、i番目の親の子ノード開始オフセットは3*(i-1) です。したがってtidx = layer_top[dp+1] + 3*(i-1) が正しい位置を指します。
- 検算:図2でdp=1, i=1のときtidx = layer_top[2] + 0 = 2となり、elements配列上で12×56, 34×78, 46×134が続いて格納される配置に一致します。
- オ/カ:pe.val1, pe.val2の理由(順不同)
- 図3の直前で pe ← elements[layer_top[dp] + (i − 1)] として親ノードを取得しており、親ノード構造体の乗算対象は pe.val1 と pe.val2 に格納されています。さらに cn ← pe.N / 2 により分割幅が求まっています。
- left(p, k), right(p, k), lradd(p, k) の説明は「pについて、valuesの添字が大きい方のk個/小さい方のk個を返す」や「add(left(p,k), right(p,k)) の結果を返す」とあります。これらは親ノードのval1/val2に対して呼ばれており、分割(left/right/lradd)は呼び出し側で行われています。つまりnew_elemの引数は既にleft(...,cn) / right(...,cn) / lradd(...,cn) によって得られた部分値であって、new_elemの内部で分割されるわけではありません。
- したがってleft(...,cn) やright(...,cn) の第1引数として渡すのはpe.val1およびpe.val2である必要があり、オ/カにはpe.val1, pe.val2の二つが入ります(順不同)。
まとめ(検算を含む):
- ウ:pow(3,i-1) は各層の要素数 に対応する。図2で {1,2,5} が得られる。
- エ:3*(i-1) はi番目の親の子ノードが次層で先頭から3*(i-1) 個分離れていることを表す。
- オ:pe.val1、カ:pe.val2(left/right呼び出しはnew_elemの外側で行われるため、引数は親ノードのval1/val2である)。
最終解答(空欄への記入)
ウ:pow(3,i-1)
エ:3*(i-1)
オ:pe.val1
カ:pe.val2
ウ:pow(3,i-1)
エ:3*(i-1)
オ:pe.val1
カ:pe.val2
誤りやすいポイント
- powの指数を一つずらしてpow(3,i) やpow(3,dp) としてしまう(ルート層が1個であることを見落とす)。
- 子ノード開始オフセットを3*iとしてしまい、i=1のときオフセットが3になってしまう(オフセットは0始まり)。
- 分割処理をnew_elemの内部で行うと誤解し、new_elemに親ノード全体peを渡すように書く。実際はleft()/right()/lradd() が呼び出し側で適用される。
- left/rightの意味(leftは添字が大きい方=上位桁、rightは添字が小さい方=下位桁)を逆に解釈する。
- 配列・添字が1から始まる仕様を忘れて0ベースで考えてしまう(図中の添字計算でオフバイワンを起こす)。
FAQ
Q: ウでpow(3,i-1) のiはどのループ変数を指していますか?
A: 図3のそのループはfor (iを1からt_depth − 1まで) なので、そのiを指します。ルート層(i=1)の要素数はpow(3,0)=1である点が決め手です。
A: 図3のそのループはfor (iを1からt_depth − 1まで) なので、そのiを指します。ルート層(i=1)の要素数はpow(3,0)=1である点が決め手です。
Q: オとカは交換して書いてもよいですか?
A: 設問上「オとカは順不同」とある通り、どちらを先に書いても構いません。ただしコード内の呼び出しはleft(pe.val1,cn), left(pe.val2,cn) のようにpe.val1/pe.val2を明示しているため、実装としてはpe.val1とpe.val2を用いるのが自然です。
A: 設問上「オとカは順不同」とある通り、どちらを先に書いても構いません。ただしコード内の呼び出しはleft(pe.val1,cn), left(pe.val2,cn) のようにpe.val1/pe.val2を明示しているため、実装としてはpe.val1とpe.val2を用いるのが自然です。
Q: なぜ子ノードのインデックス増分が3*(i-1) であって (分割桁数)と無関係なのですか?
A: はノードが扱う桁数の半分で、ノード内での分割幅を表します。一方、子ノードをelements配列に並べる位置は「親ごとに何個の子を並べるか」に依存し、各親が作る子ノード数は常に3個なので、配列上のオフセットは3*(i-1) となります。
A: はノードが扱う桁数の半分で、ノード内での分割幅を表します。一方、子ノードをelements配列に並べる位置は「親ごとに何個の子を並べるか」に依存し、各親が作る子ノード数は常に3個なので、配列上のオフセットは3*(i-1) となります。
関連キーワード: 多倍長整数、カラツバ法、分割統治法、ツリー構造、繰り上がり
設問4:
問題文を見る模範解答
キ:mod(mul, 10)
ク:elements[cidx + 2]
ケ:elements[cidx]
解説
解答の論理構成
-
【問題文】の「最下層のノードは、整数の乗算だけで計算できる。」に従い、mulは1桁×1桁の積です。
1の位を取り出すには剰余演算が必要で、表3の関数
「mod(m, k) … mをkで割った剰余を整数で返す。」
が用意されています。したがって
el.result.values[1] ← mod(mul, 10)
が [キ] となります。 -
カラツバ法の再帰計算式 (1) で α, β, γ を「子ノード①, ②, ③の乗算の計算結果」と定義しています。
・cidxは「子ノード①へのインデックス」を指しているので
elements[cidx] が α
elements[cidx + 1] が β
elements[cidx + 2] が γ です。 -
s1 ← sub( [ク].result, [ケ].result ) は γ−α を作る処理、
続く s2 ← sub(s1, elements[cidx + 1].result) で −β を加えて
γ−α−β が完成します。よって
・[ク] には γ を表すelements[cidx + 2]
・[ケ] には α を表すelements[cidx]
を当てはめれば所望の差分が得られます。
以上より
- キ:mod(mul, 10)
- ク:elements[cidx + 2]
- ケ:elements[cidx]
が正答となります。
誤りやすいポイント
- shiftとsubの順序を取り違え、α×10^kと (γ−α−β)×10^{k/2} を逆に配置してしまう。
- 表3のインデックスは1始まり。cidx + 2を2と誤読し0起算で実装すると γ と β が入れ替わる。
- mod(mul, 10) を使わずmul − (mul / 10)*10などと書いてしまい、字句指定に反して減点される。
- sub(p, q) は “桁ごとの減算” を行うだけで繰り下げが未処理。carry() を呼ばないと中間値が負の桁を持つことに気付かずバグを出す。
FAQ
Q: どうしてelements[cidx + 2] が γ だと分かるのですか?
A: 図 3 のツリー生成で「elements[tidx + 2] ← new_elem(cn, lradd(...))」とあり、lradd は (A+B) と (C+D) の乗算、すなわち式 (1) の γ に対応すると明記されています。
A: 図 3 のツリー生成で「elements[tidx + 2] ← new_elem(cn, lradd(...))」とあり、lradd は (A+B) と (C+D) の乗算、すなわち式 (1) の γ に対応すると明記されています。
Q: 1桁×1桁の積が2桁になる根拠は?
A: 9×9=81 が最大ですから、必ず 2 桁以内で収まります。【問題文】にも「el.result.N ← 2 // 計算結果は2桁」と固定で確保しています。
A: 9×9=81 が最大ですから、必ず 2 桁以内で収まります。【問題文】にも「el.result.N ← 2 // 計算結果は2桁」と固定で確保しています。
Q: carry() を最後に一度だけ呼ぶ理由は?
A: カラツバ法の途中計算では“桁ごとの加算・減算”だけを実施し、繰り上がり・繰り下がりは全体をまとめて処理した方が回数を削減できるためです。【問題文】「“繰り上がり” の処理はツリーを用いて行う演算の最後でまとめて行う。」とあります。
A: カラツバ法の途中計算では“桁ごとの加算・減算”だけを実施し、繰り上がり・繰り下がりは全体をまとめて処理した方が回数を削減できるためです。【問題文】「“繰り上がり” の処理はツリーを用いて行う演算の最後でまとめて行う。」とあります。
関連キーワード: 多倍長整数, カラツバ法, 剰余演算, 繰り上がり処理, ツリー構造
設問5:
問題文を見るN桁同士の乗算をする場合、多倍長整数の構造体において、配列valuesに必要な最大の要素数は幾つか。Nを用いて答えよ。
模範解答
2 × N
解説
解答の論理構成
-
【問題文】では「多倍長整数の演算では、整数の桁ごとの値を、1の位から順に1次元配列に格納して管理する」とあります。
➜ 1つの桁につきvalues配列に1要素を割り当てる設計です。 -
N桁の最大値は です。同様にもう一方も だとすると、積の最大値は
となり、 未満です。 -
は ちょうど2N桁まで しか取りません。したがって、N桁同士を掛けた結果が2N+1桁へ繰り上がることはありません。
-
values配列には結果の全桁を格納できる必要があるので、最大要素数は
これが模範解答「2 × N」と一致します。
誤りやすいポイント
- 繰り上がりを1桁余分に見積もって「2N+1」と考えてしまう。
しかし でも を超えないので誤りです。 - 基数10の話を見落とし、ビット数換算で要素数を計算してしまう。
- 「ツリー構造の途中で一時的に桁が増えるのでは?」と心配するケース。
子計算の結果も最大2N桁以内に収まるため追加要素は不要です。
FAQ
Q: 最上位でのcarry処理でさらに1桁増えることはありませんか?
A: N桁×N桁の最大積は なので、carryを行っても2N桁以内に収まります。
A: N桁×N桁の最大積は なので、carryを行っても2N桁以内に収まります。
Q: 片方がN桁、もう片方がN−1桁の場合はどうなりますか?
A: 本問題では「桁数が2のべき乗で、同じ桁数をもつ正の整数同士の乗算」を前提にしています。桁数が異なるときは上位を0で埋め、結局N桁同士の乗算と同じ扱いになります。
A: 本問題では「桁数が2のべき乗で、同じ桁数をもつ正の整数同士の乗算」を前提にしています。桁数が異なるときは上位を0で埋め、結局N桁同士の乗算と同じ扱いになります。
Q: 配列を動的確保する実装なら要素数を気にしなくて良いのでは?
A: 試験の設問は「必要な最大の要素数」を理論上求めるものです。実装が動的か静的かに関わらず、上限を把握しておくことが重要です。
A: 試験の設問は「必要な最大の要素数」を理論上求めるものです。実装が動的か静的かに関わらず、上限を把握しておくことが重要です。
関連キーワード: 多倍長演算, 桁あふれ, 10進基数, 乗算アルゴリズム






