読者です 読者をやめる 読者になる 読者になる

Folioscope

プログラミング/Unix系/デザイン/CG などのメモがもりもり

主婦でも出来る!? 算術演算子を使わない算術演算

算術演算子を使わない加算, 減算, そして乗算をご紹介いたします
まず初めに, ポインタの演算について説明すると
通常の算術演算の場合,

c = a + b

とすると, cには, aとbが加算された結果が代入されます.
さて, これがポインタのアドレス値になると少し違ってきます
pが何かの型のポインタとしたとき,

r = p + q

の場合, 加算されるのではなく, pからqだけポインタが進められます.
これはどういう事かというと, 例えばpがint型のポインタとしたとき, rにはint型の大きさとqが掛け合わされた数がpに加算された数が代入されます.
これを, 算術的に表すと, 次のとおりになります.

c = a + b * t    (tはaの型の大きさ)

それでは実際にこれを使用して, 算術演算の実装を見ていきましょう.

加算

まず加算から観ていきましょう

inline int add(int a, int b) {
    return &((char*)a)[b];
}

何やら奇妙なコードですね
まず内側から見ていきましょう

((char*)a)[b]

これは, aをcharのポインタ型として扱い, b番目の要素にアクセスするということです
そして配列参照演算子

foo[bar]

は,

 *(foo + bar)

で書き表すことができます
aをchar型のポインタとして扱うので, aからbをchar型のサイズ分だけ進めます.
また, char型は1バイトと決まっているので, a + bとなります.
そして, 先頭にアドレス演算子を付けます.

&((char*)a)[b]

アドレス(a + b)の参照先の変数のアドレス, つまり(a + b)となります.
これで無事に加算されました

減算

次は減算.
情報工学では有名な話で, 減算は2の補数表現を使うことで, 加算に書き換えることができます.

A - B = A + (-B) = A + (~B + 1)

ここで, チルダ'~'は, ビット反転演算子です.
これですべてを加算で表すことができました
これを先程の加算を使用すると, 次のようになります

inline int sub(int a, int b) {
    return &((char*)a)[(int)&((char*)1)[~b]];
}

乗算

続いて乗算です.
初めの説明で, ポインタ演算は

c = a + b * t    (tは型の大きさ)

という計算がなされていることがわかりました.
この式で, a = 0とすると, cにはbとtの乗算結果が代入されます.
それではコードを見ていきましょう

inline int mul(int a, int b) {
    return ((char(*)[a])0)[b];
}

まず内側から見ていくと

(char(*)[a])0

というのは, 0を要素数a個のchar型配列のポインタ型に変換するということです.
何のことかとさっぱりだと思うので, 具体的な例を

char array[2][3];
int c = (int)array[1] - (int)array[0];

この場合, cには3が代入されます.
C言語をある程度理解している人はご存知かもしれませんが, C言語には2次元配列を1次元配列で実装しています.
この場合配列arrayは, 長さ3の配列をさらに2つ並べた要素数6の1次元配列となります.
つまり, array[1][0]とarray[0][0]のアドレスの差異は, char型3つ分の, 3バイトとなります.
そして, 配列foo[bar]のfooは配列の先頭アドレスを示すように, array[0]はarray[0][0]の先頭アドレスを指します.
ここで, 一度int型にキャストしているのは, ポインタ的ではなく算術的にポインタの値を演算しているからです.


さて話は戻りまして, さきほどの例の

(char(*)[a])0

が, 要素数a個のchar型配列のポインタ型に変換されるというのが分かったかと思います.
そして, 要素数a個のchar型配列のポインタ型をbだけ進めると, 算術的には要素数a個のchar型配列の大きさだけ加算された数となり, char型の大きさは1なので, a * bとなります.
なので,

((char(*)[a])0)[b]

となると, 0から, bをaだけかけた値, つまりa * bとなります.

使用例

さて, これらの関数を使用して, 実際に使用してみましょう

int main() {
	int a, b;

	printf("a = "); scanf("%d", &a);
	printf("b = "); scanf("%d", &b);

	printf("%d + %d = %d\n", a, b, add(a, b));
	printf("%d - %d = %d\n", a, b, sub(a, b));
	printf("%d * %d = %d\n", a, b, mul(a, b));
	
	return 0;
}

実行結果は次のとおりとなります.

a = 4
b = 6
4 + 6 = 10
4 - 6 = -2
4 * 6 = 24

さいごに

除算だけが思いつかなかったで非常に残念です
誰か考えて.....