符号付き整数の代数的構造
数学の勉強に行き詰まったので、プログラミング関連で思い付いた小ネタについて書いてみる。
C言語でプログラミングをしたことがあれば、符号付き整数というものは扱ったことがあるだろう。例えばint型の変数とか、そういうものである。符号付き整数では負の数を扱う必要があるため、ビット列をそのまま2進数で表現された整数として解釈するわけにはいかず、表現したい整数との間に何らかの対応付けが必要となる。
先日、この符号付き整数の背後には何らかの代数的構造が潜んでいるのではないかと思い立った。本稿ではそれについて私が考えたことをまとめる。
準備
記号の導入
本稿では0, 1の並びにより表現されるビット列の集合について考える。ビット列に言及するとき、それがビット列であることが分かるように添字を付ける。例えば、と書いたら、これは1, 0, 1, 1というビット列を意味する。ただし、変数に対して添字は付けない。
本稿で考えるビット列の長さは有限である。特に断らない限り、長さは全てであるとする。
全てのビットが0であるようなビット列は、簡潔のため、単にと書くことにする。また、最下位ビットだけ1で他が全て0であるようなビット列は、簡潔のため、単にと書くことにする。
ビット列に対して、その全てのビットを反転させて得られるビット列をと書く。特に、全てのビットが1であるようなビット列はと書く。
0と1の割り当てについて
符号無し整数の場合、はそのまま整数の0, 1に対応する。符号付き整数の場合はこの後の議論を待たないとどのビット列をどの整数に割り当てるか決められないところだが、だけは符号無し整数の場合と同じく0, 1に割り当てることにする。
環としての構造
長さのビット列を全て集めた集合を考える。符号のことは一旦忘れて、ビット列を2進数で表現された整数であると見なせば、の任意の2元に対して足し算を行うことが出来る。その和をさらにで割った余りを2進数で表せば、これはの元になる。このようにしてに加法の演算を与えることが出来る。
乗法についても同様に与えることが出来る。結局、このようにして加法と乗法が与えられた集合はと同型な可換環になる。
イデアル
はどんなイデアルを持つだろうか?それを知るために、と同型な環であるのイデアルについて考えてみよう。
対応定理[1]により、のイデアルのうちを含むものと、のイデアルは一対一に対応する。を含むのイデアルは以下の個である。
これらに対応するのイデアルは以下の通りである。
特筆すべきは、これら以外の部分集合はイデアルにならないということである。例えば、の元のうち3の倍数の集合はイデアルではない。
ただし、これはビット列をそのまま2進数で表現された整数であると見なした場合の話である。そのようなビット列と整数の間の対応関係の与え方はまさに符号無し整数そのものであるため、符号無し整数についてはと全く同じことが言える。
では、符号付き整数の場合はどうであろうか?この時点ではまだ符号付きの場合のビット列と整数の対応付けについて述べていないため詳細は割愛するが、結論としてはやはり2の冪乗以外はイデアルの元とならない。
これは実際にプログラミングを行う際に注意が必要である。以下にこの重要性を示すためのサンプルコードと実行結果を示す。
サンプルコード:
#include <stdio.h> int main() { int a = 3; int b = 2000000000; int ab = a*b; printf("a = %d, b = %d, a*b = %d\n", a, b, ab); if(ab % 3 == 0){ printf("ab can be divided by 3.\n"); }else{ printf("ab cannot be divided by 3.\n"); } return 0; }
実行結果:
a = 3, b = 2000000000, a*b = 1705032704 ab cannot be divided by 3.
ご覧の通り、3と他の整数の積を計算しているのに、その結果が3で割り切れないケースが存在する。
零因子
の時、はと同型な体になる。一方、の時は体どころか整域にすらならない。これも実際にプログラミングをする際には注意を要する事実である。例えば、2つのint型整数に対して、どちらも0でないことを確かめるためにが0かどうかチェックするのは誤りになる。
ここでの議論は符号付き整数・符号無し整数どちらについても成り立つ。符号の有無とはビット列の解釈の仕方の問題なのであって、環としての構造に違いはないためである。また、準備の章では符号の有無に関わらず0に割り当てると決めたので、ビット列として考えても、符号化された整数として考えても、零因子を持つということに変わりはない。
このことを具体的に動くプログラムで確かめた結果を以下に示す。
サンプルコード:
#include <stdio.h> int main() { int a = 65536; int b = 196608; int ab = a*b; printf("a = %d, b = %d, a*b = %d\n", a, b, ab); if(ab == 0){ printf("ab is 0.\n"); }else{ printf("ab is not 0.\n"); } return 0; }
実行結果:
a = 65536, b = 196608, a*b = 0 ab is 0.
ご覧の通り、0でない2つの整数の積が0になるケースがあることが分かる。
ビット列と符号付き整数の対応関係
ここからはいよいよ符号付き整数でのビット列と整数の対応付けの議論に踏み込んでいく。そのために、まずは以下のような写像を考える。
以下でこの写像の性質について調べてみよう。
それぞれの式の意味
上で述べた4つの式にはそれぞれ重要な意味がある。
まず、式1はの加法による逆元がであることを述べている。つまり、を符号付き整数として見なしたとき、はその符号を反転したものだと解釈することができる。
次に式2だが、これは写像を2回施すと元に戻るということを述べている。このことからも、が符号を反転させるための写像として相応しい性質を持っていることが分かるだろう。
続いて式3であるが、これは和を取ってから符号を反転したものは、符号を反転してから和を取ったものと一致することを述べている。これはを加法について群と見なした時に、が群準同型になっていることを示している。
最後に、式4は符号を反転した2つの整数の積が符号を反転する前の2つの整数の積に等しいことを示している。これにより、さまざまな符号の組み合わせでの掛け算をうまく定義できるようになる。
全単射性
不動点
符号付き整数の特異な構造を理解するために、の不動点について理解しておくことは重要である。どのような点が不動点になるか、具体的に計算で確かめてみよう。
この条件を満たすビット列としてはの2つがある。後述するが、例えば1バイトの符号付き整数の場合、は-128に対応する。これの符号を反転しようとしても、残念ながら符号は変わらず-128のままとなる。
これについても念のため実際のプログラムで確かめておこう。
サンプルコード:
#include <stdio.h> int main() { int a = (1 << 31); printf("a = %d, -a = %d\n", a, -a); return 0; }
実行結果:
a = -2147483648, -a = -2147483648
考察の通り、符号を反転しても値が変わらないという結果になった。
は環自己同型か?
ここまでがの群自己同型であることを見た。しかし、は環なのだから、環準同型にはならないのか?という疑問は当然湧いてくる。結論から言うと、の時に限りは環準同型になるが、それ以外の場合にはそうならない。
実際、環準同型であるためにはとなる必要があるが、がの不動点になるのはの時だけである。
ビット列と符号付き整数の対応
これまでの議論を元に、ビット列と符号付き整数の対応関係を以下に示す。
ビット列 | 符号付き整数 | の不動点 | 備考 |
---|---|---|---|
0 | ○ | 加法の単位元 | |
1 | 乗法の単位元 | ||
2 | |||
... | ... | ||
○ | |||
... | ... | ||
-2 | |||
-1 |
0以上の整数は最上位ビットが0、負の整数は最上位ビットが1になっていることが分かる。これより、最上位ビットは符号ビットと呼ばれる。
結局、符号ビットが0の整数に対してビット反転して1を足したものを、その整数の符号を反転して得られる負の整数としているわけである。こんなよく分からない規則で負の数を作り出している割には、足し算・掛け算はビット列を符号無し整数とみなした時と同じ規則で行えるというのは、なんとも不思議な気持ちになる。しかし、それは全て写像の性質に裏打ちされてのことなのである。
まとめ
本稿では符号付き整数の背後に潜む代数的構造について考察した。符号付き整数は可換環の構造を成しており、符号反転のための写像をうまく与えることで、綺麗にビット列と符号付き整数との間の対応関係を与えられることが分かった。
情報科学の多くの分野には数学が密接に関わっており、我々は普段それを意識せずに使っていることが多い。たまにはその背景にある数学的理論に思いを馳せるのも面白いだろう。
参考
[1] 対応定理 - Wikipedia