Brainfuckで多倍長整数 (2)
この記事は 朝活部 Advent Calender 2024 18日目の記事です。
はじめに
前回に引き続き、brainfuck による多倍長整数の実装例についてご紹介します。
今回は多倍長整数同士の加減算や比較の実装例を見ていきましょう。
加減算と比較
桁ごとに加減算を計算してから、全体の桁上がりをするという順序で加減算を実装します。
極論インクリメント・デクリメントを繰り返せば同等のことができますが、計算量が膨大になるので今回は不採用とします。
加算
ここでは2つの多倍長整数
以下は実装例です。せっかくなので非破壊的な実装をご紹介します。
01: >>>[>]>>>[ # βの先頭からループ開始
02: -<<+[<]<[<]>[-<+>] # αの足される桁を用意
03: >[>]>[>]>[-<<+[<]<[<]<+>>[>]>[>]>] # 一桁足しつつコピー
04: >] # 次の桁 (2行目へ)
05: <<<[[->>+<<]<] # βを2マス右へ
06: <<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[ # 桁が10(B)以上なら
07: >[-]<[->+<]<[+>]>[<]>[-<+>]]]]]]]]]]] # 桁上がり
08: <] # 次の桁
お気持ち
先ずは足し合わせる部分について。
2つの多倍長整数の桁数が等しいことが保証されているので、
また、そのついでにコピーを作ることで非破壊を実現します。
次に桁上がりについてですが、10(B)
以上かという判定をしつつ、元の数値を保存しておきます。10(B)
以上でかつ上の桁が存在するなら (最上位の桁でなければ)、1つ桁上がりして更に 10(B)
以上か確認します。10(B)
未満であれば次の桁を見ていきます。
表を用いた説明
↓ 例:
番地 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1行目末尾 | 0 |
0 |
0 |
3 |
A |
8 |
0 |
0 |
0 |
*2 |
5 |
4 |
0 |
0 |
0 |
4行目末尾 | 0 |
0 |
4 |
E |
B |
0 |
0 |
2 |
5 |
4 |
0 |
0 |
*0 |
0 |
0 |
5行目末尾 | 0 |
0 |
4 |
E |
B |
0 |
*0 |
0 |
0 |
2 |
5 |
4 |
0 |
0 |
0 |
↓ 次に、
番地 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
7行目末尾 | 0 |
0 |
4 |
F |
1 |
*0 |
0 |
0 |
0 |
2 |
5 |
4 |
0 |
0 |
0 |
〃(2) | 0 |
0 |
4 |
F |
*0 |
1 |
0 |
0 |
0 |
2 |
5 |
4 |
0 |
0 |
0 |
〃(3) | 0 |
0 |
5 |
5 |
*0 |
1 |
0 |
0 |
0 |
2 |
5 |
4 |
0 |
0 |
0 |
〃(4) | 0 |
0 |
5 |
*0 |
5 |
1 |
0 |
0 |
0 |
2 |
5 |
4 |
0 |
0 |
0 |
〃(5) | 0 |
0 |
*0 |
5 |
5 |
1 |
0 |
0 |
0 |
2 |
5 |
4 |
0 |
0 |
0 |
徐々に桁上がりしているのが理解していただけたでしょうか。
というわけで、計算結果の 440(551)
が
詳細な解説
雑に分割して解説します
01: >>>[>]>>>[
02: -<<+[<]<[<]>[-<+>]
03: >[>]>[>]>[-<<+[<]<[<]<+>>[>]>[>]>]
04: >]
今回のループで加算される -<<+
によって「お気持ち」で説明したコピーが2マス左に作られます。
また、今まで作られてきたコピーが左に連なっていることを利用して、<<[<]<[<]>
で [>]>[>]>
で今見ている
あとは >]
で
05: <<<[[->>+<<]<]
06: <<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[
10(B)
以上か 10(B)
未満かで分岐しますが、今見ている桁の数値を保存しておくために1マス右へ最大10まで移動します。
(厳密にはこのコードは 0
~ A
とそれ以外で分岐します。)
07: >[-]<[->+<]<[+>]>[<]>[-<+>]]]]]]]]]]]
08: <]
10(B)
以上の場合には今見ている桁から10を引き、上位の桁に (存在するなら) 1を足します。
10(B)
未満の場合は6行目によって数値が既に右に移動しているため、次の桁を見ます。
ここでは、>[-]
で最大の10まで溜まった右のマスをクリアすることで10を引きます。
[->+<]<[+>]>[<]>[-<+>]
とすることで、上位の桁が存在しない場合にも桁数を維持します。上位の桁に相当する位置が 0
の場合に桁を追加しないために [+>]
としますが、これによって現在地がずれます。これを揃えるために、見ていた数値を利用して >[<]>[-<+>]
とします。
減算
2つの多倍長整数
以下は実装例です。
01: >>>[>]>>>[ # βの先頭からループ開始
02: -<<+[<]<[<]+++++ ++++>[-<+>] # αの引かれる桁を用意
03: >[>]>[>]>[-<<+[<]<[<]<->>[>]>[>]>] # 一桁引きつつコピー
04: >] # 次の桁 (2行目へ)
05: <<<[[->>+<<]<] # βを2マス右へ
06: <<+[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[ # 桁が10(B)以上なら
07: >[-]<[->+<]<[+>]>[<]>[-<+>]]]]]]]]]]] # 桁上がり
08: <] # 次の桁
お気持ち
前回の実装方針でも述べた通り負数は2の補数で表現するので、右の多倍長整数
表を用いた説明
↓ 例:
番地 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1行目先頭 | 0 |
0 |
0 |
1 |
5 |
8 |
0 |
0 |
0 |
*2 |
A |
6 |
0 |
0 |
0 |
4行目末尾 | 0 |
0 |
9 |
5 |
C |
0 |
0 |
2 |
A |
6 |
0 |
0 |
*0 |
0 |
0 |
5行目末尾 | 0 |
0 |
9 |
5 |
C |
0 |
*0 |
0 |
0 |
2 |
A |
6 |
0 |
0 |
0 |
↓ 次に、
番地 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
7行目末尾 | 0 |
0 |
9 |
6 |
3 |
*0 |
0 |
0 |
0 |
2 |
A |
6 |
0 |
0 |
0 |
〃(2) | 0 |
0 |
9 |
6 |
*0 |
3 |
0 |
0 |
0 |
2 |
A |
6 |
0 |
0 |
0 |
〃(3) | 0 |
0 |
9 |
*0 |
6 |
3 |
0 |
0 |
0 |
2 |
A |
6 |
0 |
0 |
0 |
〃(4) | 0 |
0 |
*0 |
9 |
6 |
3 |
0 |
0 |
0 |
2 |
A |
6 |
0 |
0 |
0 |
というわけで、計算結果の -148(963)
が
ポイント
各桁を計算する際 9(A)
からその桁の値を引いた数を足しますが、これだけでは1の補数を足していることになります。ここで6行目行頭 <<+
によって
比較
2つの桁数の等しい多倍長整数
上位から1桁ずつ大小を見ていくのも手ですが、負数が絡むのが厄介です。
以下は実装例です。
減算
>>>[>]>>>[-<<+[<]<[<]+++++ ++++>[-<+>]>[>]>[>]>[-<<+[<]<[<]<->>[>]>[>]>]>]
<<<[[->>+<<]<]
<<+[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[
>[-]<[->+<]<[+>]>[<]>[-<+>]]]]]]]]]]]<]
正負判定
<+>>>[-<+>[-<+>[-<+>[-<+>[-<+>[[-<+>]<<<->>>]]]]]]<[->+<]
加算
>>>[>]>>>[-<<+[<]<[<]>[-<+>]>[>]>[>]>[-<<+[<]<[<]<+>>[>]>[>]>]>]
<<<[[->>+<<]<]
<<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[
>[-]<[->+<]<[+>]>[<]>[-<+>]]]]]]]]]]]<]
お気持ち
一応解説
雑に分割して解説します
減算
>>>[>]>>>[-<<+[<]<[<]+++++ ++++>[-<+>]>[>]>[>]>[-<<+[<]<[<]<->>[>]>[>]>]>]
<<<[[->>+<<]<]
<<+[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[
>[-]<[->+<]<[+>]>[<]>[-<+>]]]]]]]]]]]<]
正負判定
<+>>>[-<+>[-<+>[-<+>[-<+>[-<+>[[-<+>]<<<->>>]]]]]]<[->+<]
1
を、負なら 0
を0番地 (スタート地点) に格納します。
このとき、最上位の桁が 5(6)
以上なら負数とみなします。
加算
>>>[>]>>>[-<<+[<]<[<]>[-<+>]>[>]>[>]>[-<<+[<]<[<]<+>>[>]>[>]>]>]
<<<[[->>+<<]<]
<<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[
>[-]<[->+<]<[+>]>[<]>[-<+>]]]]]]]]]]]<]
このとき正負判定の結果が邪魔になる気もしますが、問題ありません。
まとめ
-
加算
各桁足し合わせて桁上がり -
減算
2の補数を足して桁上がり -
比較
加減算を用いて正負判定
>>>[>]>>>[-<<+[<]<[<]>[-<+>]>[>]>[>]>[-<<+[<]<[<]<+>>[>]>[>]>]>]
<<<[[->>+<<]<]
<<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[
>[-]<[->+<]<[+>]>[<]>[-<+>]]]]]]]]]]]<]
>>>[>]>>>[-<<+[<]<[<]+++++ ++++>[-<+>]>[>]>[>]>[-<<+[<]<[<]<->>[>]>[>]>]>]
<<<[[->>+<<]<]
<<+[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[
>[-]<[->+<]<[+>]>[<]>[-<+>]]]]]]]]]]]<]
>>>[>]>>>[-<<+[<]<[<]+++++ ++++>[-<+>]>[>]>[>]>[-<<+[<]<[<]<->>[>]>[>]>]>]
<<<[[->>+<<]<]
<<+[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[
>[-]<[->+<]<[+>]>[<]>[-<+>]]]]]]]]]]]<]
<+>>>[-<+>[-<+>[-<+>[-<+>[-<+>[[-<+>]<<<->>>]]]]]]<[->+<]
>>>[>]>>>[-<<+[<]<[<]>[-<+>]>[>]>[>]>[-<<+[<]<[<]<+>>[>]>[>]>]>]
<<<[[->>+<<]<]
<<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[
>[-]<[->+<]<[+>]>[<]>[-<+>]]]]]]]]]]]<]
なんか解いてみる
数値の入出力に加えて、加減算ができると解ける問題の幅が一気に広がります。
ということで、AtCoder 上で簡単な問題を解いてみましょう。
今回はこれまで紹介した演算が使えれば解けるであろう問題を4問選出してみました。
1問目 - Two Coins
数値入力、加算、比較。これまで紹介してきた演算ばかりです。
解説 + 実装例
テキトー解説
- 入力 A, B を受け取って和を取る
- 入力 C を受け取って和 S と比較
- 和 S が C 以上のとき "Yes", 未満のとき "No" を出力
実装例
memory layout: __fA(S)___B(C)___
read num: A
>>>+>+>+>+>+
>>,+[----- ----- -[<+++++[->----<]>--[
[-<+>]+++++[-<--->]<[<]>->[[-<+>]>],+<]]>]<<[>]
read num: B
>>>+>+>+>+>+
>>,+[----- ----- -[<+++++[->----<]>--[
[-<+>]+++++[-<--->]<[<]>->[[-<+>]>],+<]]>]<<<[<]
add B: A
>[-<<+[<]<[<]>[-<+>]>[>]>[>]>[-<<[<]<[<]<+>>[>]>[>]>]>]
<<<[[->>+<<]<]
<<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[
>[-]<[->+<]<[+>]>[<]>[-<+>]]]]]]]]]]]<]
read num: C
>>[>]>>>[>]>,+[----- ----- -[<+++++[->----<]>--[
[-<+>]+++++[-<--->]<[<]>->[[-<+>]>],+<]]>]<<<[<]
compare(S ge C): f
>[-<<+[<]<[<]+++++ ++++>[-<+>]>[>]>[>]>[-<<[<]<[<]<->>[>]>[>]>]>]
<<<[[->>+<<]<]
<<+[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[
>[-]<[->+<]<[+>]>[<]>[-<+>]]]]]]]]]]]<]
>+>[-[-[-[-[-[[-]<->]]]]]]
print(f ? "Yes" : "No")
+<[+++++ +++++[->+++++ +++<<+++++ ++++<+++++ +++++>>]>.<<++.<+++++.>[-]]
>[+++++ +++++[-<+++++ ++<+++++ +++++>>]<+.<+.<]
余談ですが、筆者は普段こんな風にコメントを入れながら brainfuck を書いてます。
2問目 - Theater
加減算の良い練習になるでしょう。
解説 + 実装例
テキトー解説
- N は必要ないので改行まで飛ばす
- L を読み取って 0 でなければ (
EOF
でないなら) ループ - L を Sum から引く
- R を読み取って Sum に足す
- Sum をインクリメントする
- 2に戻る
- Sum を出力
実装例
memory layout: ___S___L(R)___
skip until LF
+[,----- -----]
set 0: S
>>>+>+>+>+>+ >+
read num: L
>>>>+>+>+>+>+ >+
>>,+[----- ----- -[<+++++[->----<]>--[
[-<+>]+++++[-<--->]<[<]>->[[-<+>]>],+<]]>]<<<[<]
while: L
>[-[+[<]<]+>]>[[>]>]<<[<]<[-
subtract L: S
>>[-<<+[<]<[<]+++++ ++++>[-<+>]>[>]>[>]>[-<<[<]<[<]<->>[>]>[>]>]>]
<<<[[->>+<<]<]
<<+[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[
>[-]<[->+<]<[+>]>[<]>[-<+>]]]]]]]]]]]<]
read num: R
>>[>]>>>[>]>,+[----- ----- -[<+++++[->----<]>--[
[-<+>]+++++[-<--->]<[<]>->[[-<+>]>],+<]]>]<<<[<]
add R: S
>[-<<+[<]<[<]>[-<+>]>[>]>[>]>[-<<[<]<[<]<+>>[>]>[>]>]>]
<<<[[->>+<<]<]
<<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[
>[-]<[->+<]<[+>]>[<]>[-<+>]]]]]]]]]]]<]
increment: S
>>[>]+[+++++ ++++<[->->+<<]+>[[-]<->]<]>>[+[-<<+>>]]>[[[-]<<+>>]>]
read num: L
>[>]>,+[----- ----- -[<+++++[->----<]>--[
[-<+>]+++++[-<--->]<[<]>->[[-<+>]>],+<]]>]<<<[<]
end while: L
>[-[+[<]<]+>]>[[>]>]<<[<]<]
write num: S
<<[<]>[-[+<<]>]>[<+[+++++>+<]>----.[-]>]
3問目 - Can you buy them all?
(工夫すれば偶奇判定せずとも解けますが。)
解説 + 実装例
テキトー解説
- N は必要ないので空白まで飛ばす
- X を読み取る
- A を読み取って 0 でなければ (
EOF
でないなら) ループ - flag が立っているなら A をデクリメント (最初は false)
- flag を反転
- A を Sum に足す
- 3行目に戻る
- X が Sum 以上なら "Yes", 未満なら "No" を出力
実装例
memory layout: __aX___S___A___f
skip until space
+[,>+++++ +++[-<---->]<]
read num: X
>>>+>+>+>+>+
>>,+[----- ----- -[<+++++[->----<]>--[
[-<+>]+++++[-<--->]<[<]>->[[-<+>]>],+<]]>]<<[>]
set 0: S
>>>+>+>+>+>+
read num: A
>>>>+>+>+>+>+
>>,+[----- ----- -[<+++++[->----<]>--[
[-<+>]+++++[-<--->]<[<]>->[[-<+>]>],+<]]>]<<[>]
while: A
<[-[+[>]>]+<]<[[<]<]>>[>]>[-
if(f) decrement: A
>+>[-<-<<+[<[->->+<<]+>[[+]<->]<]>>[-[-<<+>>]]>[[[-]<<+++++ +++++>>]>]>]
set !f: f
<[->+<]
add A: S
<<<[<]>[-<<+[<]<[<]>[-<+>]>[>]>[>]>[-<<[<]<[<]<+>>[>]>[>]>]>]
<<<[[->>+<<]<]
<<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[
>[-]<[->+<]<[+>]>[<]>[-<+>]]]]]]]]]]]<]
read num: A
>>[>]>>>[>]>,+[----- ----- -[<+++++[->----<]>--[
[-<+>]+++++[-<--->]<[<]>->[[-<+>]>],+<]]>]<<[>]
end while: A
<[-[+[>]>]+<]<[[<]<]>>[>]>]
compare(X ge S): a
<<[<]<<<[<]>[-<<+[<]<[<]+++++ ++++>[-<+>]>[>]>[>]>[-<<[<]<[<]<->>[>]>[>]>]>]
<<<[[->>+<<]<]
<<+[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[
>[-]<[->+<]<[+>]>[<]>[-<+>]]]]]]]]]]]<]
>+>[-[-[-[-[-[[-]<->]]]]]]
print(a ? "Yes" : "No")
+<[+++++ +++++[->+++++ +++<<+++++ ++++<+++++ +++++>>]>.<<++.<+++++.>[-]]
>[+++++ +++++[-<+++++ ++<+++++ +++++>>]<+.<+.<]
4問目 - Sum of Three Integers
大変ですが、前回と今回で紹介した内容をフル活用することで解けます。
解説 + 実装例
テキトー解説
頑張って
memory layout: ___X___K___S_f_A___
read num: X
>>+>+>+>+>+
>>,+[----- ----- -[<+++++[->----<]>--[[-<+>]+++++[-<--->]<[<]>->[[-<+>]>],+<]]>]<<[>]
copy X: K
<[[->+>>>>> >>>+<<<<< <<<<]<]
increment: X
>>[>]+[+++++ ++++<[->->+<<]+>[[-]<->]<]>>[+[-<<+>>]]>[[[-]<<+>>]>]
read num: S
>[>]>>>+>+>+>+>+
>>,+[----- ----- -[<+++++[->----<]>--[[-<+>]+++++[-<--->]<[<]>->[[-<+>]>],+<]]>]<<[>]
set 0: A
>>>+>+>+>+>+ >+>+>+>+>+[<]
while: X
<<<[<]<<<[<]<+[-
decrement: X
<+[<[->->+<<]+>[[+]<->]<]>>[-[-<<+>>]]>[[[-]<<+++++ +++++>>]>]
if(S lt 0): *__S
>[>]>->>-[+<]+<[+>-]+<+[->-
set 0: X & end if: *__S
<<<[<]<<<[[-]+<]>[>]>>>[>]>]
else: *_S
>[-
subtract K: S
>[+++++ +++++>]<[<]<<<[[->+>>>>> >>-<<<<< <<<]<]>>[>]>>[[-<+>]>]
<<+[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[
[->+<]<[+>]>[<]>----- -----[-<+>]]]]]]]]]]]<]
set (S lt 0): f
+>>-[+<]+<[>->[>]>+<<[<]]<-
add K: S
<<<<<[-<+>[-<+>>>>> >>>+<<<<< <<]>]>>[[-<+>]>]
<<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[
>[-]<<[+>>]<[<]>>[>]]]]]]]]]]]<]
if(S lt K): f
>>[>]+>[-<-
add S & increment: A
<[->+<[->+>>>>> >>>>> >>+<<<<< <<<<< <<<]<]>>[[-<+>]>]>>[[-<+>]>]
<<+[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[
>[-]<<[+>>]<[<]>>[>]]]]]]]]]]]<]
end if: f
]
else: S*_f
<[-
subtract 2K & decrement: S
<[+++++ +++++ +++++ +++++ +++++ ++++<]
<<<[[->+>>>>> >>--<<<<< <<<]<]>>[[-<+>]>]>>[[-<+>]>]
<<++[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[
>[-]<<[+>>]<[<]>>[>]]]]]]]]]]]<]
if(S lt 0): *__S
->>-[+<]+<[+>-]<+[-
subtract S: A
>>[>]>>>[+++++ +++++>]<[<]<<<[[->+>>>>> >>>>> >>-<<<<< <<<<< <<<]<]
>>[[-<+>]>]+++++[[>]>[-<+>]<[<]>-]>[----- ----->]>[[-<+>]>]
<<+[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[
>[-]<<[+>>]<[<]>>[>]]]]]]]]]]]<]
end if: *__S
<<[<]<]
add 2K & increment: S
<<[->+<[->+>>>>> >>++<<<<< <<<]<]>>[[-<+>]>]>>[[-<+>]>]
<<+[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[
>[-]<<[+>>]<[<]>>[>]]]]]]]]]]]<]
end else: S*_f
>>[>]]
end else: *_S
<[<]<]
decrement: S
>>[>]+[<[->->+<<]+>[[+]<->]<]>>[-[-<<+>>]]>[[[-]<<+++++ +++++>>]>]
end while: X
<<<[<]<<<[<]<<<[-[+[>]>]+<]<[[<]<]>>[>]>]
write num: A
>>[>]>>>[>]>>>[-[+<<]>]>[<+[+++++>+<]>----.[-]>]+++++ +++++.
おわりに
以上、今回は brainfuck による多倍長整数同士の加減算と比較について実装例をご紹介しました。
次回は乗算について書きたいですね。(非負整数に限らない場合の数値入出力など、まだ紹介していない何かしらについて書くかもしれません)
いずれにせよ、ここまで読んでいただいて本当にありがとうございました。
Discussion