C言語でKaratsuba開平を利用して整数の平方根と余りを求める

math.hのsqrt(f)を使わずに整数の平方根を求めてみた。(intが4byteであるマシンでのみ動作)
sqrtmod0はアルゴリズム上0x40000000以上の値の平方根を計算でようなので、0x40000000以上の値はKaratsuba開平を利用して求めている。

↓は乱数で平方根と余りを求めてsqrtで求めたものとassertをし続けるプログラムです。

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <math.h>

#define BASE (sizeof (unsigned int) * 8 / 4)

int sqrtmod(unsigned int n, unsigned int *pmod);
int sqrtmod_karatsuba(unsigned int n, unsigned int *pmod);
int sqrtmod0(unsigned int n, unsigned int *pmod);

int sqrtmod(unsigned int n, unsigned int *pmod) {
	return sqrtmod_karatsuba(n, pmod);
}

int sqrtmod_karatsuba(unsigned int n, unsigned int *pmod) {
	int s, s1, s0, r, r1, r0;

	if ((0xc0000000 & n) == 0) {
		return sqrtmod0(n, pmod);
	}

	s1 = sqrtmod0(n >> (BASE * 2), &r1);

	r0 = (r1 << BASE) | ((n >> BASE) & 0x000000ff);
	s0 = r0 / (s1 << 1);
	r0 -= s0 * (s1 << 1);

	s = (s1 << BASE) + s0;
	r = ((r0 << BASE) | (n & 0x000000ff)) - s0 * s0;
	if (r < 0) {
		r += (s << 1) - 1;
		s--;
	}
	if (pmod) *pmod = r;
	assert(s * s + r == n);

	return s;
}

int sqrtmod0(unsigned int n, unsigned int *pmod) {
	int p = 0, q = 1, r = n, h = 0;

	while (q <= n) {
		q = q << 2;
	}

	while (1 != q) {
		q = q >> 2;
		h = p + q;
		p = p >> 1;
		if (r >= h) {
			p = p + q;
			r = r - h;
		}
	}
	if (pmod) *pmod = r;
	assert(p * p + r == n);

	return p;
}

int main(int argc, char **argv) {
	unsigned int n, s1, r1, s2, r2;

	srand((unsigned)time(NULL));
	while (1) {
		n = rand();
		s1 = sqrtmod(n, &r1);
		s2 = sqrt(n);
		r2 = n - s2 * s2;
		assert(s1 == s2 && r1 == r2);
	}

	return 0;
}

とりあえずassertエラーが出ないので、実装は出来ているらしい。。


参考:
2 Karatsuba$B7ONs$N%"%k%4%j%:%`(B
GNU MP 6.1.2: Square Root Algorithm