/* big int minilib
   Copyright (C) 2017, 2024 Szabolcs Nagy and Tibor 'Igor2' Palinkas
   This file is licensed under Creative Commons CC0 version 1.0, see
   https://creativecommons.org/publicdomain/zero/1.0/
   Source code: svn://svn.repo.hu/genfip/trunk
*/

#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <limits.h>
#include <stdarg.h>

/* this can be overridden by defininig BIG_HAVE_STATIC_INLINE */
#ifndef BIG_HAVE_STATIC_INLINE
#	if __STDC_VERSION__ > 199901L
#		define BIG_HAVE_STATIC_INLINE 1
#	else
#		define BIG_HAVE_STATIC_INLINE 0
#	endif
#endif

#ifndef big_local
#	if BIG_HAVE_STATIC_INLINE
#		define big_local static inline
#	else
#		define big_local static
#	endif
#endif

/* Numbers not larger than this can be stored on the stack; larger numbers
   will be allocated on heap */
#ifndef BIG_STACK_TEMP_LEN
#	define BIG_STACK_TEMP_LEN 16
#endif

#ifndef BIG_BITS
#	if ULONG_MAX == 0xffffffff
#		define BIG_BITS 32
#		define BIG_DECIMAL_DIGITS 9
#		define BIG_DECIMAL_BASE 1000000000UL
#		define BIG_NEG_BASE 0x80000000UL
#	elif ULONG_MAX == 0xffffffffffffffff
#		define BIG_BITS 64
#		define BIG_DECIMAL_DIGITS 19
#		define BIG_DECIMAL_BASE 10000000000000000000UL
#		define BIG_NEG_BASE 0x8000000000000000UL
#	else
#		error "unsupported system: long int has to be 32 or 64 bits wide (checked: ULONG_MAX)"
#	endif

/* A big number is an array of words.  */
typedef unsigned long big_word;

#endif

/* Can hold bits, bytes or words of a number.  */
typedef unsigned big_size;

/* Useful for local array declaration.  */
#define big_words(bits) (((bits)+BIG_BITS-1)/BIG_BITS)

#define BIG_BITS2 (BIG_BITS/2)
#define BIG_MASK2 ((big_word)-1>>BIG_BITS2)

big_local void big_zero(big_word *a, big_size len)
{
	memset(a, 0, len*sizeof(big_word));
}
big_local void big_copy(big_word *dst, const big_word *src, big_size len)
{
	memmove(dst, src, len*sizeof(big_word));
}

big_local void big_swap(big_word *a_, big_word *b_, big_size len)
{
	int i;
	big_word tmp, *a, *b;

	for(i = 0, a = a_, b = b_; i < len; i++, a++, b++) {
		tmp = *a;
		*a = *b;
		*b = tmp;
	}
}

big_local void big_init(big_word *a, big_size len, unsigned long v)
{
	assert(len > 0);
	big_zero(a, len);
	a[0] = v;
}

#ifndef big_alloc
big_local big_word *big_alloc(big_size len)
{
	big_size alloc_len = len * sizeof(big_word);

	if (alloc_len < len)
		return NULL; /* integer overflow */

	return malloc(alloc_len);
}
#endif

#ifndef big_free
big_local void big_free(big_word *a)
{
	free(a);
}
#endif

/* *_temp(): helpser to use a stack temp num for small numbers instead of heap allocation */
big_local big_word *big_alloc_temp(big_word *temp_stack, big_size temp_stack_len, big_size want_len)
{
	if (want_len <= temp_stack_len)
		return temp_stack;
	return big_alloc(want_len);
}

big_local void big_free_temp(big_word *returned, big_word *temp_stack)
{
	if (returned != temp_stack)
		free(returned);
}

#define BIG_ALLOC_TEMP(tmp_stack, len) big_alloc_temp((tmp_stack), sizeof(tmp_stack)/sizeof(big_word), (len))
#define BIG_FREE_TEMP(tmp, tmp_stack)  big_free_temp((tmp), (tmp_stack))



/* word count: returns the number of words without leading zeros */
big_local big_size big_len(const big_word *a, big_size len)
{
	while (len && a[len-1] == 0) len--;
	return len;
}

/* return whether a number is negative (assumung it's signed) */
big_local int big_is_neg(const big_word *a, big_size len)
{
	return (a[len-1] >= BIG_NEG_BASE);
}

/* return whether a number is zero */
big_local int big_is_zero(const big_word *a, big_size len)
{
	big_size i;
	for(i = 0; i < len; i++)
		if (a[i] != 0)
			return 0;
	return 1;
}

/* Returns sign: -1 for negative, 0 for zero and +1 for positiver */
big_local int big_sgn(const big_word *a, big_size len)
{
	if (big_is_neg(a, len))
		return -1;
	if (big_is_zero(a, len))
		return 0;
	return +1;
}


/* cmp a,b with same len (top word set); unsigned */
big_local int big_cmpn(const big_word *a, const big_word *b, big_size len)
{
	while (len--)
		if (a[len] != b[len])
			return a[len] > b[len] ? 1 : -1;
	return 0;
}

/* cmp a,b with different len; unsigned */
big_local int big_cmp(const big_word *a, big_size alen, const big_word *b, big_size blen)
{
	alen = big_len(a, alen);
	blen = big_len(b, blen);
	if (alen != blen)
		return alen > blen ? 1 : -1;
	return big_cmpn(a, b, alen);
}

/* same as cmpn but assumes signed numbers */
big_local int big_signed_cmpn(const big_word *a, const big_word *b, big_size len)
{
	int sa = big_sgn(a, len), sb = big_sgn(b, len);

	if ((sa == 0) && (sb == 0)) return 0;
	if ((sa >= 0) && (sb < 0)) return +1;
	if ((sa < 0) && (sb >= 0)) return -1;

	/* both are positive or both are negative (0 is treated as positive
	   because it works as expected with unsigned compare) */
	return big_cmpn(a, b, len);
}


#define big_add_word(carry, result, x, y) \
do { \
	big_word _x = (x); \
	big_word _y = (y); \
	big_word _r = _x + _y; \
	(result) = _r; \
	(carry) = _r < _x; \
} while (0)

#define big_sub_word(carry, result, x, y) \
do { \
	big_word _x = (x); \
	big_word _y = (y); \
	big_word _r = _x - _y; \
	(result) = _r; \
	(carry) = _r > _x; \
} while (0)

/* *r = x + y + z, return carry */
big_local big_word big_add111(big_word *r, big_word x, big_word y, big_word z)
{
	big_word t, c1, c2;
	big_add_word(c1,  t, x, y);
	big_add_word(c2, *r, t, z);
	return c1 + c2;
}

/* *r = x - y - z, return carry */
big_local big_word big_sub111(big_word *r, big_word x, big_word y, big_word z)
{
	big_word t, c1, c2;
	big_sub_word(c1,  t, x, y);
	big_sub_word(c2, *r, t, z);
	return c1 + c2;
}

/* r[] = x[] + y, return carry, r maybe same as x */
big_local big_word big_add1(big_word *r, const big_word *x, big_size len, big_word y)
{
	big_size i;
	for (i = 0; i < len; i++)
		big_add_word(y, r[i], x[i], y);
	return y;
}

/* r[] = x[] - y, return carry, r maybe same as x */
big_local big_word big_sub1(big_word *r, const big_word *x, big_size len, big_word y)
{
	big_size i;
	for (i = 0; i < len; i++)
		big_sub_word(y, r[i], x[i], y);
	return y;
}

/* r[] = x[] + y[] + c, return carry, r maybe same as x and y */
big_local big_word big_addn(big_word *r, const big_word *x, const big_word *y, big_size len, big_word c)
{
	big_size i;
	for (i = 0; i < len; i++)
		c = big_add111(r+i, x[i], y[i], c);
	return c;
}

/* r[] = x[] - y[] - c, return carry, r maybe same as x and y */
big_local big_word big_subn(big_word *r, const big_word *x, const big_word *y, big_size len, big_word c)
{
	big_size i;
	for (i = 0; i < len; i++)
		c = big_sub111(r+i, x[i], y[i], c);
	return c;
}

/* a = -b, a maybe same as b */
big_local big_word big_neg(big_word *a, const big_word *b, big_size len)
{
	big_size i;
	big_word carry = 0;

	for (i = 0; i < len; i++)
		carry = big_sub111(a+i, 0, b[i], carry);
	return carry;
}

/* *lo = a*b, return hi */
big_local big_word big_mul11(big_word *lo, big_word a, big_word b)
{
	big_word alo,ahi,blo,bhi,t0,t1,t2;

	alo = a & BIG_MASK2;
	ahi = a >> BIG_BITS2;
	blo = b & BIG_MASK2;
	bhi = b >> BIG_BITS2;
	t0 = alo*blo;
	t1 = ahi*blo + (t0 >> BIG_BITS2);
	t2 = ahi*bhi + (t1 >> BIG_BITS2);
	t1 = alo*bhi + (t1 & BIG_MASK2);
	t2 += t1 >> BIG_BITS2;
	*lo = (t0 & BIG_MASK2) + (t1 << BIG_BITS2);
	return t2;
}

/* *lo += a*b, return hi */
big_local big_word big_addmul11(big_word *lo, big_word a, big_word b)
{
	big_word t, hi;

	hi = big_mul11(&t, a, b);
	*lo += t;
	hi += *lo < t;
	return hi;
}

/* a += b*c; only fill a[0..len-1], return a[len] */
big_local big_word big_iaddmul1(big_word *a, const big_word *b, big_size len, big_word c)
{
	big_size i;
	big_word t=0;
	for (i = 0; i < len; i++) {
		a[i] += t;
		t = a[i] < t;
		t += big_addmul11(a+i, b[i], c);
	}
	return t;
}

/* a = b*c+d, a may alias b, return a[len] (overflow word) */
big_local big_word big_mul1(big_word *a, const big_word *b, big_size len, big_word c, big_word d)
{
	big_word hi, lo = d;
	big_size i;
	for (i = 0; i < len; i++) {
		hi = big_addmul11(&lo, b[i], c);
		a[i] = lo;
		lo = hi;
	}
	return lo;
}

/* a = b*c where a!=b, a!=c, alen>=len, faster if b>c, returns overflow */
big_local int big_mul(big_word *a, big_size alen, const big_word *b, const big_word *c, big_size len)
{
	big_size i, n, blen=len, clen=len;
	big_word t;
	int oflow = 0;

	big_zero(a, alen);
	if (len == 0)
		return 0;
	while (b[blen-1] == 0)
		if (--blen == 0)
			return 0;
	while (c[clen-1] == 0)
		if (--clen == 0)
			return 0;
	/* assumes alen >= blen, alen >= clen */
	n = clen;
	if (alen - blen < n)
		n = alen - blen;
	for (i = 0; i < n; i++)
		a[i+blen] = big_iaddmul1(a+i, b, blen, c[i]);
	for (; i < clen; i++) {
		t = big_iaddmul1(a+i, b, alen-i, c[i]);
		if (i > n || t)
			oflow = 1;
	}
	return oflow;
}

big_local int big_signed_mul(big_word *a_, big_size alen, const big_word *b_, const big_word *c_, big_size len)
{
	big_word *a, atmp[BIG_STACK_TEMP_LEN], *b, btmp[BIG_STACK_TEMP_LEN], *c, ctmp[BIG_STACK_TEMP_LEN];
	int res, nega = 0, freeb = 0, freec = 0;

	if (alen == len)
		return big_mul(a_, alen, b_, c_, len);

	if (big_is_neg(b_, len)) {
		b = BIG_ALLOC_TEMP(btmp, len);
		big_neg(b, b_, len);
		freeb = 1;
		nega = 1;
	}
	else
		b = (big_word *)b_;

	if (big_is_neg(c_, len)) {
		c = BIG_ALLOC_TEMP(ctmp, len);
		big_neg(c, c_, len);
		freec = 1;
		nega = !nega;
	}
	else
		c = (big_word *)c_;

	if (nega) {
		a = BIG_ALLOC_TEMP(atmp, alen);
		big_zero(a, alen);
	}
	else
		a = a_;

	res = big_mul(a, alen, b, c, len);

	if (freeb)
		BIG_FREE_TEMP(b, btmp);
	if (freec)
		BIG_FREE_TEMP(c, ctmp);
	if (nega) {
		big_neg(a_, a, alen);
		BIG_FREE_TEMP(a, atmp);
	}

	return res;
}


big_local big_size big_word_bitlen(big_word a)
{
	big_size n = 0;
	while (a >= 0x8000) a>>=16, n+=16;
	if (a >= 0x80) a>>=8, n+=8;
	if (a >= 8) a>>=4, n+=4;
	if (a >= 2) a>>=2, n+=2;
	if (a) n++;
	return n;
}

big_local big_size big_word_leadingzeros(big_word a)
{
	return sizeof(big_word)*8 - big_word_bitlen(a);
}

/* Compute (hi,lo)/d as q*d+r = (hi<<BITS)+lo, where hi<d, return r */
big_local big_word big_div11(big_word *q, big_word hi, big_word lo, big_word d)
{
	big_word dhi,dlo,thi,tlo,tlohi,tlolo,q0,q1,r,t;
	int k;

	if (d >> (BIG_BITS-1)) {
		k = 0;
		thi = hi;
		tlo = lo;
	} else {
		k = big_word_leadingzeros(d);
		d <<= k;
		thi = hi << k | lo >> (BIG_BITS-k);
		tlo = lo << k;
	}
	dlo = d & BIG_MASK2;
	dhi = d >> BIG_BITS2;
	tlohi = tlo >> BIG_BITS2;
	tlolo = tlo & BIG_MASK2;
	/* (thi*B + tlohi*B2 + tlolo) / (dhi*B2 + dlo) */
	q1 = thi/dhi;
	r = thi - q1*dhi;
	for (;;) {
		if (q1 > BIG_MASK2 || q1*dlo > (r<<BIG_BITS2)+tlohi) {
			q1--;
			r += dhi;
			if (r <= BIG_MASK2)
				continue;
		}
		break;
	}
	t = (thi<<BIG_BITS2) + tlohi - q1*d;
	q0 = t/dhi;
	r = t - q0*dhi;
	for (;;) {
		if (q0 > BIG_MASK2 || q0*dlo > (r<<BIG_BITS2)+tlolo) {
			q0--;
			r += dhi;
			if (r <= BIG_MASK2)
				continue;
		}
		break;
	}
	*q = (q1<<BIG_BITS2) + q0;
	return ((t<<BIG_BITS2) + tlolo - q0*d) >> k;
}

/* a = b<<shift, where shift<BIG_BITS, len(a)=len, return bits shifted out,
   a maybe same as b */
big_local big_word big_shl1(big_word *a, const big_word *b, big_size len, big_size shift)
{
	big_word r;
	if (len == 0 || shift == 0)
		return 0;
	r = b[len-1]>>(BIG_BITS-shift);
	while (--len > 0)
		a[len] = b[len]<<shift | b[len-1]>>(BIG_BITS-shift);
	a[len] = b[len]<<shift;
	return r;
}

/* a = b>>shift, where shift<BIG_BITS, len(a)=len, return bits shifted out,
   a maybe same as b */
big_local big_word big_shr1(big_word *a, const big_word *b, big_size len, unsigned shift)
{
	big_size i;
	big_word r;
	if (len == 0)
		return 0;
	r = b[0]<<(BIG_BITS-shift);
	for (i = 0; i < len-1; i++)
		a[i] = b[i]>>shift | b[i+1]<<(BIG_BITS-shift);
	a[i] = b[i]>>shift;
	return r;
}

/* a = b/c, c!=0, len(a)=len, return r so a*c+r == b */
big_local big_word big_div1_(big_word *a, const big_word *b, big_size len, big_word c)
{
	big_word r = 0;
	big_size i = len;
	while (i--)
		r = big_div11(a+i, r, b[i], c);
	return r;
}

/* (x1,x0) < (y1,y0) */
big_local int big_less2(big_word x1, big_word x0, big_word y1, big_word y0)
{
	return x1 < y1 || (x1 == y1 && x0 < y0);
}

/* Compute u/v as q*v+r = u, where len(tmp) >= 2*vlen+ulen+2,
   vlen >= 2, ulen >= vlen, len(q) = ulen-vlen+1, len(r) = vlen */
big_local void big_div_(big_word *q, big_word *r, big_word *u, big_size ulen, const big_word *v, big_size vlen, big_word *tmp)
{
	big_word *qhv;
	big_size j;
	int n;

	/* knuth vol.2 4.3.1 algorithm D */

	/* D1 normalize */
	n = big_word_leadingzeros(v[vlen-1]);
	if (n > 0) {
		big_shl1(tmp, v, vlen, n);
		v = tmp;
		tmp += vlen;
		tmp[ulen] = big_shl1(tmp, u, ulen, n);
	} else {
		big_copy(tmp, u, ulen);
		tmp[ulen] = 0;
	}
	u = tmp;
	tmp += ulen+1;
	qhv = tmp;
	/* D2 init j */
	j = ulen - vlen;
	for (;;) {
		big_word oldrh, rh, qh, hi, lo, carry;
		/* D3 calc qh ~= u/(v<<(j*BIG_BITS)) */
		if (u[j+vlen] != v[vlen-1]) {
			rh = big_div11(&qh, u[j+vlen], u[j+vlen-1], v[vlen-1]);
			for (;;) {
				hi = big_mul11(&lo, qh, v[vlen-2]);
				if (!big_less2(rh, u[j+vlen-2], hi, lo))
					break;
				qh--;
				oldrh = rh;
				rh += v[vlen-1];
				if (rh < oldrh)
					break;
			}
		} else
			qh = -1;
		/* D4 mul (qhv=qh*v) and sub (u-=qhv<<(j*BIG_BITS)) */
		{
			big_size i;
			qhv[0] = 0;
			for (i = 0; i < vlen; i++)
				qhv[i+1] = big_addmul11(qhv+i, v[i], qh);
		}
		carry = big_subn(u+j, u+j, qhv, vlen+1, 0);
		/* D5 test remainder */
		if (carry) {
			/* D6 add back */
			carry = big_addn(u+j, u+j, v, vlen, 0);
			u[j+vlen] += carry;
			qh--;
		}
		q[j] = qh;
		/* D7 loop */
		if (j == 0)
			break;
		j--;
	}
	/* D8 unnormalize */
	if (n) {
		big_shr1(r, u, vlen, n);
		r[vlen-1] |= u[vlen]<<(BIG_BITS-n);
	} else
		big_copy(r, u, vlen);
}

/* b/c as a*c+r = b, where a may alias b, c!=0, len(a) = len, return r */
big_local big_word big_div1(big_word *a, big_word *b, big_size len, big_word c)
{
	/* optimize power-of-two: shr */
	if ((c&(c-1))==0) {
		big_word r;
		if (c==1 || len==0) {
			if (a!=b)
				big_copy(a, b, len);
			return 0;
		}
		r = b[0]&(c-1);
		big_shr1(a, b, len, big_word_bitlen(c-1));
		return r;
	}
	return big_div1_(a, b, len, c);
}

/* u/v as q*v+r = u, return alloc failure
   len(tmp) >= 2*vlen+ulen+2, or tmp=NULL for automatic/local allocation
   v != 0
   len(q) = ulen-vlen+1
   len(r) = vlen
 */
big_local int big_div(big_word *q, big_word *r, big_word *u, big_size ulen, const big_word *v, big_size vlen, big_word *tmp)
{
	big_word tmp_stack[BIG_STACK_TEMP_LEN];
	int n;
	ulen = big_len(u, ulen);
	vlen = big_len(v, vlen);
	n = big_cmp(u, ulen, v, vlen);
	if (n < 0) {
		big_copy(r, u, ulen);
		big_zero(r+ulen, vlen-ulen);
		if (ulen == vlen)
			q[0] = 0;
		return 0;
	}
	if (n == 0) {
		big_zero(r, vlen);
		q[0] = 1;
		return 0;
	}
	if (vlen == 1) {
		r[0] = big_div1(q, u, ulen, v[0]);
		return 0;
	}
	if (tmp == NULL) {
		tmp = BIG_ALLOC_TEMP(tmp_stack, 2*vlen+ulen+2);
		if (tmp == NULL) return -1;
	}
	big_div_(q, r, u, ulen, v, vlen, tmp);
	BIG_FREE_TEMP(tmp, tmp_stack);
	return 0;
}

big_local int big_signed_div(big_word *q_, big_word *r_, const big_word *u_, big_size ulen, const big_word *v_, big_size vlen, big_word *tmp)
{
	big_word *u, utmp[BIG_STACK_TEMP_LEN], *v, vtmp[BIG_STACK_TEMP_LEN];
	big_word *q, qtmp[BIG_STACK_TEMP_LEN], *r, rtmp[BIG_STACK_TEMP_LEN];
	int res, negq = 0, negr = 0, freeu = 0, freev = 0;
	int qlen = ulen, rlen = vlen;


	if (big_is_neg(u_, ulen)) {
		u = BIG_ALLOC_TEMP(utmp, ulen);
		big_neg(u, u_, ulen);
		freeu = 1;
		negq = !negq;
		negr = 1;
	}
	else
		u = (big_word *)u_;

	if (big_is_neg(v_, vlen)) {
		v = BIG_ALLOC_TEMP(vtmp, vlen);
		big_neg(v, v_, vlen);
		freev = 1;
		negq = !negq;
	}
	else
		v = (big_word *)v_;

	if (negq) {
		q = BIG_ALLOC_TEMP(qtmp, qlen);
		big_zero(q, qlen);
	}
	else
		q = q_;

	if (negr) {
		r = BIG_ALLOC_TEMP(rtmp, rlen);
		big_zero(r, rlen);
	}
	else
		r = r_;

	res = big_div(q, r, u, ulen, v, vlen, tmp);

	if (freeu)
		BIG_FREE_TEMP(u, utmp);
	if (freev)
		BIG_FREE_TEMP(v, vtmp);
	if (negq) {
		big_neg(q_, q, qlen);
		BIG_FREE_TEMP(q, qtmp);
	}
	if (negr) {
		big_neg(r_, r, rlen);
		BIG_FREE_TEMP(r, rtmp);
	}

	return res;
}


big_local big_size big_size_log2(big_size n)
{
	big_size r=0;
	while (n >>= 1) r++;
	return r;
}

/* TODO: opt, error handling */
/* Returns number of words used in a, or 0 on error (overflow or invalid
   character). If endptr is not NULL, load it with the first character
   that could not be converted (and do not return 0 because of that character) */
big_local big_size big_fromstr(big_word *a, big_size len, const char *s, char **endptr, int base)
{
	big_size i;
	big_size alen = 0;
	big_word w;
	big_word c;

	if (base == 10) {
		for (i=0; s[i]-'0' < 10U; i++) {
			w = big_mul1(a, a, alen, 10, s[i]-'0');
			if (w) {
				if (alen < len)
					a[alen++] = w;
				else
					return 0;
			}
		}

		if (endptr != NULL)
			*endptr = (char *)s+i;

		if (s[i] == 0) {
			for (i = alen; i < len; i++)
				a[i] = 0;
			if ((alen == 0) && (i > 0)) /* corner case: converting 0 or 00 */
				alen = 1;
			return alen;
		}

		if (endptr != NULL)
			return alen;
	}

	if (base == 16) {
		for (i = 0; ; i++) {
			c = s[i]-'0';
			if (c >= 10U) {
				c = (s[i]|32)-'a' + 10;
				if (c >= 16U)
					break;
			}
			w = big_shl1(a, a, alen, 4);
			if (alen == 0 && c != 0)
				a[alen++] = c;
			else
				a[0] |= c;
			if (w) {
				if (alen < len)
					a[alen++] = w;
				else
					return 0;
			}
		}

		if (endptr != NULL)
			*endptr = (char *)s+i;

		if (s[i] == 0) {
			for (i = alen; i < len; i++)
				a[i] = 0;
			return alen;
		}

		if (endptr != NULL)
			return alen;
	}
	return 0;
}

/* TODO: overflow error handling, return value is not useful now */
big_local big_size big_signed_fromstr(big_word *a, big_size len, char *s, char **endptr, int base)
{
	int isneg = 0;
	big_size res;

	if (*s == '-') {
		s++;
		isneg = 1;
	}
	else if (*s == '+')
		s++;

	res = big_fromstr(a, len, s, endptr, base);

	if (isneg)
		big_neg(a, a, len);

	return res;
}


/* convert a to str, dlen is base: dlen <= 256 && dlen >= 2 */
big_local big_size big_tostr(char *s, big_size slen, const big_word *a, big_size alen, const char *digits, int dlen)
{
	big_size basebits, i, j;

	alen = big_len(a, alen);
	if (alen == 0) {
		if (slen >= 2) {
			s[0] = digits[0];
			s[1] = 0;
		}
		return 1;
	}
	basebits = big_size_log2(dlen);

	/* power-of-2 */
	if (dlen == 1<<basebits) {
		unsigned mask = dlen-1;
		unsigned nbits = 0;
		big_word w;
		for (i = j = 0; ; i++) {
			if (nbits == 0) {
				w = a[i];
				nbits = BIG_BITS;
			} else {
				w |= a[i] << nbits;
				if (j < slen) s[j] = digits[w&mask];
				j++;
				w = a[i] >> (basebits-nbits);
				nbits += BIG_BITS-basebits;
			}
			if (i == alen-1)
				break;
			while (nbits >= basebits) {
				if (j < slen) s[j] = digits[w&mask];
				j++;
				w >>= basebits;
				nbits -= basebits;
			}
		}
		while (w) {
			if (j < slen) s[j] = digits[w&mask];
			j++;
			w >>= basebits;
		}
end:
		if (j < slen) {
			s[j] = 0;
			/* reverse */
			for (i = 0; i < j/2; i++) {
				char c = s[i];
				s[i] = s[j-i-1];
				s[j-i-1] = c;
			}
		}
		return j;
	}

	if (dlen == 10) {
		big_word r, *q, tmp_[BIG_STACK_TEMP_LEN], b = BIG_DECIMAL_BASE;
		big_size n;
		/* TODO: divide and conquer opt */
		j = 0;
		n = alen;
		q = BIG_ALLOC_TEMP(tmp_, n);
		if (!q)
			return 0;
		big_copy(q, a, n);
		for (;;) {
			r = big_div1(q, q, n, b);
			n = big_len(q, n);
			if (n == 0)
				break;
			for (i = 0; i < BIG_DECIMAL_DIGITS; i++) {
				if (j < slen) s[j] = digits[r%10], r/=10;
				j++;
			}
		}
		BIG_FREE_TEMP(q, tmp_);
		while (r) {
			if (j < slen) s[j] = digits[r%10];
			r/=10;
			j++;
		}
		goto end;
	}

	/* TODO: other bases */
	return 0;
}



big_local big_size big_signed_tostr(char *s, big_size slen, const big_word *a, big_size alen, const char *digits, int dlen)
{
	big_word *tmp, tmp_[BIG_STACK_TEMP_LEN];
	big_size res;

	if (!big_is_neg(a, alen))
		return big_tostr(s, slen, a, alen, digits, dlen);

	if (slen < 1)
		return 0;

	tmp = BIG_ALLOC_TEMP(tmp_, alen);
	big_neg(tmp, a, alen);

	*s = '-';
	s++;
	slen--;
	res = big_tostr(s, slen, tmp, alen, digits, dlen);
	BIG_FREE_TEMP(tmp, tmp_);

	return res;
}
