Adds tagged bignum support alongside the existing 48-bit fixnum on the C tier. Tag 6 = bignum, heap struct sign-magnitude with u64 little-endian limbs. Reader emits bignums for any literal past the fixnum range; +, -, *, quotient, remainder, modulo, expt, =, <, >, abs, odd?, even?, integer?, exact?, number->string, string->number all promote fixnum → bignum on overflow & demote back when results fit. Boehm GC owns every allocation. Schoolbook O(n²) mul + shift-subtract divmod is sufficient at our 4-limb / 256-bit scale. Before: (expt 2 48) = 0, (expt 2 256) = 0, secp256k1-p = -4294968273. After: all three return their exact arbitrary-precision values, matching Python tier byte-for-byte. Validated: - c/test.c — 85/85 pass (+2 new bignum unit tests). - tests/functional.lsp — 205/205 pass on both C & Python tiers. - tests/bignum-cross-tier.lsp — 33/33 pass byte-identical on both tiers (diff produces no output). - ecdsa/runs/lumbda-sweep-003/c-tier-bignum-probe.lsp — all four assertions now match the Python oracle. - ecdsa Phase B byte-identity sweep inside QEMU guest: n+1=9 p=251 sha256 c668bbe3... — matches Python oracle. n+1=18 p=131071 sha256 8a031f96... — matches Python oracle. n+1=33 p=2³²-5 sha256 0bc56905... — matches Python oracle. Previously the n+1=33 C tier emitted sha256 b024d6d9... (26,078 fewer Toffolis due to silent fixnum wrap). Bignums close that gate. secp256k1 production-width emit (n+1=257) is now structurally unblocked on C tier; downstream agent (#55) drives that next-step on the ecdsa side. Asm tier inherits in a follow-up port.
496 lines
19 KiB
C
496 lines
19 KiB
C
/*
|
|
* bignum.c — Arbitrary-precision integer arithmetic for lumbda C tier.
|
|
*
|
|
* Design (full notes in lumbda.h after the Rational block):
|
|
* - Sign-magnitude representation: int32_t sign (-1, 0, +1),
|
|
* uint32_t n_limbs, uint64_t *limbs (little-endian magnitude).
|
|
* - bignum_normalize() trims leading-zero limbs & demotes to fixnum
|
|
* when |value| <= FIXNUM_MAX so callers never branch on tag.
|
|
* - All arithmetic uses 64-bit limbs with 128-bit products (__int128)
|
|
* for schoolbook multiply. Division uses long-division by single
|
|
* limb when divisor fits one limb, otherwise schoolbook with
|
|
* normalization (sufficient for our scale: secp256k1 needs at most
|
|
* 4 limbs / 256 bits, far below where Karatsuba helps).
|
|
* - Boehm GC owns the Bignum struct & its limbs array.
|
|
*
|
|
* Cross-tier oracle: Python tier (native arbitrary int) is the byte-
|
|
* identity standard. Every operation here must agree with Python on
|
|
* matching inputs — see tests/bignum-cross-tier.lsp.
|
|
*/
|
|
#include "lumbda.h"
|
|
|
|
/* ═══════════════════════════════════════════════════════════════════════════
|
|
* Allocation helpers
|
|
* ═══════════════════════════════════════════════════════════════════════════ */
|
|
|
|
static Bignum *alloc_bignum(uint32_t n_limbs) {
|
|
Bignum *b = (Bignum *)ul_malloc(sizeof(Bignum));
|
|
b->hdr.type = OBJ_BIGNUM;
|
|
b->sign = 0;
|
|
b->n_limbs = n_limbs;
|
|
if (n_limbs == 0) {
|
|
b->limbs = NULL;
|
|
} else {
|
|
b->limbs = (uint64_t *)ul_malloc(sizeof(uint64_t) * n_limbs);
|
|
memset(b->limbs, 0, sizeof(uint64_t) * n_limbs);
|
|
}
|
|
return b;
|
|
}
|
|
|
|
static Value wrap_bignum(Bignum *b) {
|
|
return NANBOX(TAG_BIGNUM, (uintptr_t)b);
|
|
}
|
|
|
|
/* Trim leading zero limbs in place. Zero magnitude => sign 0. */
|
|
static void trim(Bignum *b) {
|
|
while (b->n_limbs > 0 && b->limbs[b->n_limbs - 1] == 0)
|
|
b->n_limbs--;
|
|
if (b->n_limbs == 0) b->sign = 0;
|
|
}
|
|
|
|
/* ═══════════════════════════════════════════════════════════════════════════
|
|
* Constructors
|
|
* ═══════════════════════════════════════════════════════════════════════════ */
|
|
|
|
Value make_bignum_from_i64(int64_t v) {
|
|
if (FITS_FIXNUM(v)) return VAL_INT(v);
|
|
Bignum *b = alloc_bignum(1);
|
|
if (v < 0) {
|
|
b->sign = -1;
|
|
/* Avoid -INT64_MIN UB: cast then negate via unsigned. */
|
|
b->limbs[0] = (uint64_t)(-(v + 1)) + 1ULL;
|
|
} else if (v > 0) {
|
|
b->sign = 1;
|
|
b->limbs[0] = (uint64_t)v;
|
|
} else {
|
|
b->sign = 0;
|
|
b->n_limbs = 0;
|
|
}
|
|
return wrap_bignum(b);
|
|
}
|
|
|
|
Value bignum_from_limbs(int32_t sign, uint64_t *limbs, uint32_t n) {
|
|
Bignum *b = alloc_bignum(n);
|
|
if (n > 0) memcpy(b->limbs, limbs, sizeof(uint64_t) * n);
|
|
b->sign = sign;
|
|
trim(b);
|
|
return wrap_bignum(b);
|
|
}
|
|
|
|
/* Demote bignum-shape to fixnum when |value| fits 48 signed bits.
|
|
* On demote we return VAL_INT(...); otherwise allocate a fresh Bignum. */
|
|
Value bignum_normalize(int32_t sign, uint64_t *limbs, uint32_t n) {
|
|
/* Strip top zeros (compute effective limb count). */
|
|
while (n > 0 && limbs[n - 1] == 0) n--;
|
|
if (n == 0) return VAL_INT(0);
|
|
if (n == 1) {
|
|
uint64_t lo = limbs[0];
|
|
/* Signed 48-bit range: -2^47 .. 2^47 - 1. */
|
|
if (sign >= 0 && lo <= (uint64_t)FIXNUM_MAX)
|
|
return VAL_INT((int64_t)lo);
|
|
if (sign < 0 && lo <= (uint64_t)(1ULL << 47))
|
|
return VAL_INT(-(int64_t)lo);
|
|
}
|
|
Bignum *b = alloc_bignum(n);
|
|
memcpy(b->limbs, limbs, sizeof(uint64_t) * n);
|
|
b->sign = sign;
|
|
return wrap_bignum(b);
|
|
}
|
|
|
|
/* Promote a fixnum to a Bignum struct (caller-owned). Bignum input passes
|
|
* through (the same struct is returned — Bignum structs are immutable from
|
|
* the perspective of arithmetic so sharing is safe). */
|
|
Bignum *as_bignum(Value v) {
|
|
if (IS_BIGNUM(v)) return AS_BIGNUM(v);
|
|
if (IS_INT(v)) {
|
|
int64_t x = as_int(v);
|
|
Bignum *b = alloc_bignum(x == 0 ? 0 : 1);
|
|
if (x > 0) { b->sign = 1; b->limbs[0] = (uint64_t)x; }
|
|
else if (x < 0) {
|
|
b->sign = -1;
|
|
b->limbs[0] = (uint64_t)(-(x + 1)) + 1ULL;
|
|
}
|
|
return b;
|
|
}
|
|
if (IS_DOUBLE(v)) {
|
|
double d = as_double(v);
|
|
return AS_BIGNUM(make_bignum_from_i64((int64_t)d));
|
|
}
|
|
lisp_error("not an integer");
|
|
return NULL;
|
|
}
|
|
|
|
Value to_bignum(Value v) {
|
|
if (IS_BIGNUM(v)) return v;
|
|
return wrap_bignum(as_bignum(v));
|
|
}
|
|
|
|
/* ═══════════════════════════════════════════════════════════════════════════
|
|
* Parsing (string → bignum)
|
|
* ═══════════════════════════════════════════════════════════════════════════ */
|
|
|
|
/* Multiply magnitude by single limb; add another limb. Magnitude grows. */
|
|
static void mag_mul_add(uint64_t *limbs, uint32_t *n, uint64_t mul, uint64_t add) {
|
|
__uint128_t carry = add;
|
|
for (uint32_t i = 0; i < *n; i++) {
|
|
__uint128_t prod = (__uint128_t)limbs[i] * mul + carry;
|
|
limbs[i] = (uint64_t)prod;
|
|
carry = prod >> 64;
|
|
}
|
|
while (carry) {
|
|
limbs[(*n)++] = (uint64_t)carry;
|
|
carry >>= 64;
|
|
}
|
|
}
|
|
|
|
Value make_bignum_from_str(const char *s, int base) {
|
|
int32_t sign = 1;
|
|
if (*s == '-') { sign = -1; s++; }
|
|
else if (*s == '+') s++;
|
|
/* Conservative limb count: log2(10) ≈ 3.33 bits per digit → roughly
|
|
* len/19 limbs for base 10; len/16 limbs for base 16. Round up + 1. */
|
|
size_t len = strlen(s);
|
|
uint32_t cap = (uint32_t)((len * 4) / 19) + 2;
|
|
uint64_t *limbs = (uint64_t *)ul_malloc(sizeof(uint64_t) * cap);
|
|
memset(limbs, 0, sizeof(uint64_t) * cap);
|
|
uint32_t n = 0;
|
|
for (; *s; s++) {
|
|
int d;
|
|
if (*s >= '0' && *s <= '9') d = *s - '0';
|
|
else if (*s >= 'a' && *s <= 'f') d = *s - 'a' + 10;
|
|
else if (*s >= 'A' && *s <= 'F') d = *s - 'A' + 10;
|
|
else { ul_free(limbs); lisp_error("bad digit '%c' in number", *s); }
|
|
if (d >= base) { ul_free(limbs); lisp_error("digit out of base"); }
|
|
mag_mul_add(limbs, &n, (uint64_t)base, (uint64_t)d);
|
|
if (n >= cap - 1) {
|
|
cap *= 2;
|
|
limbs = (uint64_t *)ul_realloc(limbs, sizeof(uint64_t) * cap);
|
|
memset(limbs + n, 0, sizeof(uint64_t) * (cap - n));
|
|
}
|
|
}
|
|
Value v = bignum_normalize(sign, limbs, n);
|
|
ul_free(limbs);
|
|
return v;
|
|
}
|
|
|
|
/* ═══════════════════════════════════════════════════════════════════════════
|
|
* Magnitude-level primitives
|
|
* ═══════════════════════════════════════════════════════════════════════════ */
|
|
|
|
/* Compare magnitudes. Returns -1, 0, +1. */
|
|
static int mag_cmp(const uint64_t *a, uint32_t na, const uint64_t *b, uint32_t nb) {
|
|
if (na != nb) return na < nb ? -1 : 1;
|
|
for (int32_t i = (int32_t)na - 1; i >= 0; i--) {
|
|
if (a[i] != b[i]) return a[i] < b[i] ? -1 : 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* out = a + b magnitudes. out_cap must be at least max(na, nb) + 1. */
|
|
static uint32_t mag_add(const uint64_t *a, uint32_t na,
|
|
const uint64_t *b, uint32_t nb,
|
|
uint64_t *out) {
|
|
uint32_t n = na > nb ? na : nb;
|
|
__uint128_t carry = 0;
|
|
for (uint32_t i = 0; i < n; i++) {
|
|
__uint128_t s = carry;
|
|
if (i < na) s += a[i];
|
|
if (i < nb) s += b[i];
|
|
out[i] = (uint64_t)s;
|
|
carry = s >> 64;
|
|
}
|
|
if (carry) out[n++] = (uint64_t)carry;
|
|
return n;
|
|
}
|
|
|
|
/* out = a - b magnitudes, assuming a >= b. out_cap >= na. */
|
|
static uint32_t mag_sub(const uint64_t *a, uint32_t na,
|
|
const uint64_t *b, uint32_t nb,
|
|
uint64_t *out) {
|
|
int64_t borrow = 0;
|
|
for (uint32_t i = 0; i < na; i++) {
|
|
__int128 s = (__int128)a[i] - borrow;
|
|
if (i < nb) s -= b[i];
|
|
if (s < 0) { s += ((__int128)1 << 64); borrow = 1; }
|
|
else borrow = 0;
|
|
out[i] = (uint64_t)s;
|
|
}
|
|
while (na > 0 && out[na - 1] == 0) na--;
|
|
return na;
|
|
}
|
|
|
|
/* out = a * b magnitudes. out_cap >= na + nb. */
|
|
static uint32_t mag_mul(const uint64_t *a, uint32_t na,
|
|
const uint64_t *b, uint32_t nb,
|
|
uint64_t *out) {
|
|
memset(out, 0, sizeof(uint64_t) * (na + nb));
|
|
for (uint32_t i = 0; i < na; i++) {
|
|
__uint128_t carry = 0;
|
|
for (uint32_t j = 0; j < nb; j++) {
|
|
__uint128_t prod = (__uint128_t)a[i] * b[j] + out[i + j] + carry;
|
|
out[i + j] = (uint64_t)prod;
|
|
carry = prod >> 64;
|
|
}
|
|
out[i + nb] += (uint64_t)carry;
|
|
}
|
|
uint32_t n = na + nb;
|
|
while (n > 0 && out[n - 1] == 0) n--;
|
|
return n;
|
|
}
|
|
|
|
/* Divide magnitude a by single 64-bit limb d. Quotient into q (cap >= na),
|
|
* remainder returned. */
|
|
static uint64_t mag_divmod_u64(const uint64_t *a, uint32_t na,
|
|
uint64_t d, uint64_t *q, uint32_t *nq) {
|
|
__uint128_t r = 0;
|
|
for (int32_t i = (int32_t)na - 1; i >= 0; i--) {
|
|
__uint128_t cur = (r << 64) | a[i];
|
|
q[i] = (uint64_t)(cur / d);
|
|
r = cur % d;
|
|
}
|
|
*nq = na;
|
|
while (*nq > 0 && q[*nq - 1] == 0) (*nq)--;
|
|
return (uint64_t)r;
|
|
}
|
|
|
|
/* Schoolbook division: a / b → (q, r) where a = q*b + r, 0 <= r < b.
|
|
* For our scale (operands <= 4 limbs commonly, up to a few dozen for
|
|
* intermediates in expt), a basic Knuth Algorithm D is overkill — we use
|
|
* shift-subtract on bits. O(na * nb * 64). */
|
|
static void mag_divmod(const uint64_t *a, uint32_t na,
|
|
const uint64_t *b, uint32_t nb,
|
|
uint64_t *q, uint32_t *nq,
|
|
uint64_t *r, uint32_t *nr) {
|
|
if (nb == 0) lisp_error("division by zero");
|
|
if (mag_cmp(a, na, b, nb) < 0) {
|
|
/* a < b: q=0, r=a */
|
|
*nq = 0;
|
|
memcpy(r, a, sizeof(uint64_t) * na);
|
|
*nr = na;
|
|
return;
|
|
}
|
|
if (nb == 1) {
|
|
uint64_t rem = mag_divmod_u64(a, na, b[0], q, nq);
|
|
if (rem) { r[0] = rem; *nr = 1; } else { *nr = 0; }
|
|
return;
|
|
}
|
|
/* Shift-subtract long division: walk bits MSB → LSB of dividend. */
|
|
memset(q, 0, sizeof(uint64_t) * na);
|
|
memset(r, 0, sizeof(uint64_t) * (nb + 1));
|
|
uint32_t rn = 0;
|
|
for (int64_t bit = (int64_t)na * 64 - 1; bit >= 0; bit--) {
|
|
/* r <<= 1 */
|
|
uint64_t carry = 0;
|
|
for (uint32_t i = 0; i <= nb; i++) {
|
|
uint64_t new_carry = r[i] >> 63;
|
|
r[i] = (r[i] << 1) | carry;
|
|
carry = new_carry;
|
|
}
|
|
if (carry || r[nb]) rn = nb + 1;
|
|
else { while (rn > 0 && r[rn - 1] == 0) rn--; }
|
|
/* r |= bit of a */
|
|
uint64_t bit_val = (a[bit / 64] >> (bit % 64)) & 1ULL;
|
|
r[0] |= bit_val;
|
|
if (bit_val && rn == 0) rn = 1;
|
|
/* if r >= b: r -= b; set bit in q */
|
|
if (mag_cmp(r, rn, b, nb) >= 0) {
|
|
uint32_t new_rn = mag_sub(r, rn, b, nb, r);
|
|
rn = new_rn;
|
|
q[bit / 64] |= 1ULL << (bit % 64);
|
|
}
|
|
}
|
|
*nq = na;
|
|
while (*nq > 0 && q[*nq - 1] == 0) (*nq)--;
|
|
*nr = rn;
|
|
}
|
|
|
|
/* ═══════════════════════════════════════════════════════════════════════════
|
|
* Signed arithmetic (sign-magnitude wrappers)
|
|
* ═══════════════════════════════════════════════════════════════════════════ */
|
|
|
|
Value big_add(Value a, Value b) {
|
|
Bignum *ba = as_bignum(a), *bb = as_bignum(b);
|
|
if (ba->sign == 0) return bignum_normalize(bb->sign, bb->limbs, bb->n_limbs);
|
|
if (bb->sign == 0) return bignum_normalize(ba->sign, ba->limbs, ba->n_limbs);
|
|
uint32_t cap = (ba->n_limbs > bb->n_limbs ? ba->n_limbs : bb->n_limbs) + 1;
|
|
uint64_t *out = (uint64_t *)ul_malloc(sizeof(uint64_t) * cap);
|
|
Value res;
|
|
if (ba->sign == bb->sign) {
|
|
uint32_t n = mag_add(ba->limbs, ba->n_limbs, bb->limbs, bb->n_limbs, out);
|
|
res = bignum_normalize(ba->sign, out, n);
|
|
} else {
|
|
int cmp = mag_cmp(ba->limbs, ba->n_limbs, bb->limbs, bb->n_limbs);
|
|
if (cmp == 0) { res = VAL_INT(0); }
|
|
else if (cmp > 0) {
|
|
uint32_t n = mag_sub(ba->limbs, ba->n_limbs, bb->limbs, bb->n_limbs, out);
|
|
res = bignum_normalize(ba->sign, out, n);
|
|
} else {
|
|
uint32_t n = mag_sub(bb->limbs, bb->n_limbs, ba->limbs, ba->n_limbs, out);
|
|
res = bignum_normalize(bb->sign, out, n);
|
|
}
|
|
}
|
|
ul_free(out);
|
|
return res;
|
|
}
|
|
|
|
Value big_neg(Value a) {
|
|
if (IS_INT(a)) {
|
|
int64_t x = as_int(a);
|
|
return make_bignum_from_i64(-x);
|
|
}
|
|
Bignum *ba = as_bignum(a);
|
|
if (ba->sign == 0) return VAL_INT(0);
|
|
return bignum_normalize(-ba->sign, ba->limbs, ba->n_limbs);
|
|
}
|
|
|
|
Value big_sub(Value a, Value b) {
|
|
return big_add(a, big_neg(b));
|
|
}
|
|
|
|
Value big_mul(Value a, Value b) {
|
|
Bignum *ba = as_bignum(a), *bb = as_bignum(b);
|
|
if (ba->sign == 0 || bb->sign == 0) return VAL_INT(0);
|
|
uint32_t cap = ba->n_limbs + bb->n_limbs;
|
|
uint64_t *out = (uint64_t *)ul_malloc(sizeof(uint64_t) * cap);
|
|
uint32_t n = mag_mul(ba->limbs, ba->n_limbs, bb->limbs, bb->n_limbs, out);
|
|
Value res = bignum_normalize(ba->sign * bb->sign, out, n);
|
|
ul_free(out);
|
|
return res;
|
|
}
|
|
|
|
bool big_is_zero(Value v) {
|
|
if (IS_INT(v)) return as_int(v) == 0;
|
|
if (IS_BIGNUM(v)) return AS_BIGNUM(v)->sign == 0;
|
|
return false;
|
|
}
|
|
|
|
bool big_is_odd(Value v) {
|
|
if (IS_INT(v)) return (as_int(v) & 1) != 0;
|
|
if (IS_BIGNUM(v)) {
|
|
Bignum *b = AS_BIGNUM(v);
|
|
return b->n_limbs > 0 && (b->limbs[0] & 1ULL) != 0;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Value big_abs(Value v) {
|
|
if (IS_INT(v)) { int64_t x = as_int(v); return make_bignum_from_i64(x < 0 ? -x : x); }
|
|
Bignum *b = as_bignum(v);
|
|
if (b->sign >= 0) return v;
|
|
return bignum_normalize(1, b->limbs, b->n_limbs);
|
|
}
|
|
|
|
int big_cmp(Value a, Value b) {
|
|
Bignum *ba = as_bignum(a), *bb = as_bignum(b);
|
|
if (ba->sign != bb->sign) return ba->sign < bb->sign ? -1 : 1;
|
|
if (ba->sign == 0) return 0;
|
|
int m = mag_cmp(ba->limbs, ba->n_limbs, bb->limbs, bb->n_limbs);
|
|
return ba->sign > 0 ? m : -m;
|
|
}
|
|
|
|
/* Quotient: truncated toward zero (matches C / Python's // for non-negatives;
|
|
* Scheme's `quotient` is also truncate-toward-zero). */
|
|
Value big_quotient(Value a, Value b) {
|
|
Bignum *ba = as_bignum(a), *bb = as_bignum(b);
|
|
if (bb->sign == 0) lisp_error("division by zero");
|
|
if (ba->sign == 0) return VAL_INT(0);
|
|
uint32_t qcap = ba->n_limbs > 0 ? ba->n_limbs : 1;
|
|
uint32_t rcap = bb->n_limbs + 1;
|
|
uint64_t *q = (uint64_t *)ul_malloc(sizeof(uint64_t) * qcap);
|
|
uint64_t *r = (uint64_t *)ul_malloc(sizeof(uint64_t) * rcap);
|
|
uint32_t nq, nr;
|
|
mag_divmod(ba->limbs, ba->n_limbs, bb->limbs, bb->n_limbs, q, &nq, r, &nr);
|
|
int32_t sign = ba->sign * bb->sign;
|
|
Value res = bignum_normalize(nq == 0 ? 0 : sign, q, nq);
|
|
ul_free(q); ul_free(r);
|
|
return res;
|
|
}
|
|
|
|
/* Remainder: result has sign of dividend (Scheme `remainder`). */
|
|
Value big_remainder(Value a, Value b) {
|
|
Bignum *ba = as_bignum(a), *bb = as_bignum(b);
|
|
if (bb->sign == 0) lisp_error("division by zero");
|
|
if (ba->sign == 0) return VAL_INT(0);
|
|
uint32_t qcap = ba->n_limbs > 0 ? ba->n_limbs : 1;
|
|
uint32_t rcap = bb->n_limbs + 1;
|
|
uint64_t *q = (uint64_t *)ul_malloc(sizeof(uint64_t) * qcap);
|
|
uint64_t *r = (uint64_t *)ul_malloc(sizeof(uint64_t) * rcap);
|
|
uint32_t nq, nr;
|
|
mag_divmod(ba->limbs, ba->n_limbs, bb->limbs, bb->n_limbs, q, &nq, r, &nr);
|
|
Value res = bignum_normalize(nr == 0 ? 0 : ba->sign, r, nr);
|
|
ul_free(q); ul_free(r);
|
|
return res;
|
|
}
|
|
|
|
/* Modulo: result has sign of divisor (Scheme `modulo`). */
|
|
Value big_modulo(Value a, Value b) {
|
|
Value r = big_remainder(a, b);
|
|
/* If r != 0 and sign(r) != sign(b), add b. */
|
|
if (big_is_zero(r)) return r;
|
|
Bignum *br = as_bignum(r), *bb = as_bignum(b);
|
|
if (br->sign != bb->sign) return big_add(r, b);
|
|
return r;
|
|
}
|
|
|
|
/* Binary exponentiation. exp must be a non-negative integer. */
|
|
Value big_expt(Value base, Value exp) {
|
|
/* exp must be an integer. */
|
|
if (!IS_INTEGER(exp)) lisp_error("expt: integer exponent required");
|
|
/* Negative exponent → not handled here (caller falls back to double). */
|
|
if (IS_INT(exp) && as_int(exp) < 0) lisp_error("expt: negative exponent");
|
|
if (IS_BIGNUM(exp) && AS_BIGNUM(exp)->sign < 0) lisp_error("expt: negative exponent");
|
|
/* Read exp as u64 — bignum exponents larger than 2^63 would never finish. */
|
|
uint64_t e;
|
|
if (IS_INT(exp)) e = (uint64_t)as_int(exp);
|
|
else {
|
|
Bignum *be = AS_BIGNUM(exp);
|
|
if (be->n_limbs > 1) lisp_error("expt: exponent too large");
|
|
e = be->n_limbs == 0 ? 0 : be->limbs[0];
|
|
}
|
|
Value result = VAL_INT(1);
|
|
Value b = base;
|
|
while (e > 0) {
|
|
if (e & 1) result = big_mul(result, b);
|
|
e >>= 1;
|
|
if (e) b = big_mul(b, b);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/* ═══════════════════════════════════════════════════════════════════════════
|
|
* Decimal rendering
|
|
* ═══════════════════════════════════════════════════════════════════════════ */
|
|
|
|
char *bignum_to_str(Bignum *b) {
|
|
if (b->sign == 0) return ul_strdup("0");
|
|
/* Divide repeatedly by 10^18 → 18 decimal digits per chunk. */
|
|
uint32_t n = b->n_limbs;
|
|
uint64_t *mag = (uint64_t *)ul_malloc(sizeof(uint64_t) * n);
|
|
memcpy(mag, b->limbs, sizeof(uint64_t) * n);
|
|
/* Generous size: log10(2) ≈ 0.30103 → n*64*0.31 + sign + null. */
|
|
size_t cap = (size_t)n * 21 + 4;
|
|
char *buf = (char *)ul_malloc(cap);
|
|
size_t pos = cap;
|
|
buf[--pos] = '\0';
|
|
const uint64_t CHUNK = 1000000000000000000ULL; /* 10^18 fits in u64. */
|
|
while (n > 0) {
|
|
uint64_t rem = mag_divmod_u64(mag, n, CHUNK, mag, &n);
|
|
if (n > 0) {
|
|
/* Full 18-digit chunk (zero-pad). */
|
|
for (int i = 0; i < 18; i++) {
|
|
buf[--pos] = '0' + (int)(rem % 10);
|
|
rem /= 10;
|
|
}
|
|
} else {
|
|
/* Top chunk: no leading zeros. */
|
|
if (rem == 0) buf[--pos] = '0';
|
|
else while (rem) { buf[--pos] = '0' + (int)(rem % 10); rem /= 10; }
|
|
}
|
|
}
|
|
if (b->sign < 0) buf[--pos] = '-';
|
|
/* Move string to start. */
|
|
size_t len = cap - pos;
|
|
memmove(buf, buf + pos, len);
|
|
ul_free(mag);
|
|
return buf;
|
|
}
|