Files
mruby-mruby/mrbgems/mruby-bigint/core/bigint.c
T
Yukihiro "Matz" Matsumoto 120c58108d mruby-bigint: fix c++ compatibility issues
- Add explicit cast for mrb_malloc return value
- Remove restrict keyword from function parameters
- Move variable declarations to avoid goto/initialization conflicts
- Fix signed/unsigned comparison warning in mpz_get_str

Co-authored-by: Claude <noreply@anthropic.com>
2025-08-19 10:06:19 +09:00

3771 lines
86 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
** @file mruby/bigint.c - Multi-precision Integer
**
** See Copyright Notice in mruby.h
*/
#include <mruby.h>
#include <mruby/object.h>
#include <mruby/numeric.h>
#include <mruby/array.h>
#include <mruby/string.h>
#include <mruby/internal.h>
#include <string.h>
#include "bigint.h"
#define DIG_SIZE (MPZ_DIG_SIZE)
#define DIG_BASE (1ULL << DIG_SIZE)
#define DIG_MASK (DIG_BASE - 1)
#define HIGH(x) ((x) >> DIG_SIZE)
#define LOW(x) ((x) & DIG_MASK)
#define iabs(x) (((x)>0)?(x):(-x))
#define imax(x,y) (((x)>(y))?(x):(y))
#define imin(x,y) (((x)<(y))?(x):(y))
#define dg(x,i) (((size_t)i < (x)->sz)?(x)->p[i]:0)
#ifndef MRB_BIGINT_POOL_SIZE
#define MRB_BIGINT_POOL_SIZE 512 /* 2KB on 32-bit, 4KB on 64-bit */
#endif
/* Scoped Memory Pool Infrastructure */
#if MRB_BIGINT_POOL_SIZE == 0
#define mpz_ctx_t mrb_state
#define MPZ_MRB(ctx) (ctx)
#define MPZ_HAS_POOL(ctx) (0)
#define MPZ_CTX_INIT(mrb_ptr, ctx, pool_ptr) mrb_state *ctx = (mrb_ptr);
#define pool_save(ctx) 0
#define pool_restore(ctx, state) (void)state
#define pool_alloc(pool, limbs) NULL
#else
typedef struct mpz_pool {
mp_limb data[MRB_BIGINT_POOL_SIZE];
size_t used;
} mpz_pool_t;
/* MPZ Context Architecture - unified parameter for mrb_state and optional pool */
typedef struct mpz_context {
mrb_state *mrb;
mpz_pool_t *pool; /* NULL for heap-only operations */
} mpz_ctx_t;
/* Convenience macros for context creation */
#define MPZ_CTX_INIT(mrb_ptr, ctx, pool_ptr) \
mpz_pool_t pool ## _storage = {0};\
mpz_pool_t *pool_ptr = &pool ## _storage;\
mpz_ctx_t ctx ## _struct = ((mpz_ctx_t){.mrb = (mrb_ptr), .pool = (pool_ptr)}); \
mpz_ctx_t *ctx = &(ctx ## _struct);
/* Access macros for readability */
#define MPZ_MRB(ctx) ((ctx)->mrb)
#define MPZ_POOL(ctx) ((ctx)->pool)
#define MPZ_HAS_POOL(ctx) ((ctx)->pool != NULL)
static size_t
pool_save(mpz_ctx_t *ctx)
{
mpz_pool_t *pool = MPZ_POOL(ctx);
return pool ? pool->used : 0;
}
static void
pool_restore(mpz_ctx_t *ctx, size_t state)
{
mpz_pool_t *pool = MPZ_POOL(ctx);
if (pool) {
pool->used = state;
}
}
static mp_limb*
pool_alloc(mpz_pool_t *pool, size_t limbs)
{
if (!pool || pool->used + limbs > MRB_BIGINT_POOL_SIZE) {
return NULL; /* Force fallback to heap */
}
mp_limb *ptr = &pool->data[pool->used];
pool->used += limbs;
return ptr;
}
#endif
/* Zero n limbs at p */
static inline void
limb_zero(mp_limb *p, size_t n)
{
memset(p, 0, n * sizeof(mp_limb));
}
static void
mpz_init(mpz_ctx_t *ctx, mpz_t *s)
{
s->p = NULL;
s->sn = 0;
s->sz = 0;
}
/* Heap-preferred allocation */
static void
mpz_init_heap(mpz_ctx_t *ctx, mpz_t *s, size_t hint)
{
s->sn = 0;
if (hint > 0) {
s->p = (mp_limb*)mrb_malloc(MPZ_MRB(ctx), hint * sizeof(mp_limb));
limb_zero(s->p, hint);
s->sz = hint;
}
else {
s->p = NULL; /* Lazy allocation via mpz_realloc later */
s->sz = 0;
}
}
#if MRB_BIGINT_POOL_SIZE > 0
/* Pool-preferred allocation (future: mpz_init_temp) */
static void
mpz_init_temp(mpz_ctx_t *ctx, mpz_t *s, size_t hint)
{
s->sn = 0;
if (hint > 0 && MPZ_HAS_POOL(ctx)) {
mp_limb *pool_ptr = pool_alloc(MPZ_POOL(ctx), hint);
if (pool_ptr) {
s->p = pool_ptr;
s->sz = hint;
return;
}
}
/* Fallback to heap allocation */
mpz_init_heap(ctx, s, hint);
}
#else
#define mpz_init_temp(ctx, s, hint) mpz_init_heap(ctx, s, hint)
#endif
/* Check if mpz_t uses pool memory */
#if MRB_BIGINT_POOL_SIZE > 0
static int
is_pool_memory(mpz_t *z, mpz_pool_t *pool)
{
if (!pool || !z->p) return 0;
uintptr_t ptr_addr = (uintptr_t)z->p;
uintptr_t pool_start = (uintptr_t)pool->data;
uintptr_t pool_end = pool_start + sizeof(pool->data);
return ptr_addr >= pool_start && ptr_addr < pool_end;
}
#endif
static void
mpz_realloc(mpz_ctx_t *ctx, mpz_t *x, size_t size)
{
if (x->sz < size) {
/* Check for overflow in size calculation */
if (size > SIZE_MAX / sizeof(mp_limb)) {
mrb_state *mrb = MPZ_MRB(ctx);
mrb_raise(mrb, E_RUNTIME_ERROR, "bigint size too large");
}
size_t old_sz = x->sz;
#if MRB_BIGINT_POOL_SIZE > 0
/* Pool memory cannot be reallocated - must use heap */
if (MPZ_HAS_POOL(ctx) && is_pool_memory(x, MPZ_POOL(ctx))) {
/* Allocate new heap memory and copy from pool */
mp_limb *new_p = (mp_limb*)mrb_malloc(MPZ_MRB(ctx), size * sizeof(mp_limb));
if (x->p) {
memcpy(new_p, x->p, old_sz * sizeof(mp_limb));
}
x->p = new_p;
}
else {
#endif
/* Regular heap reallocation */
x->p = (mp_limb*)mrb_realloc(MPZ_MRB(ctx), x->p, size * sizeof(mp_limb));
#if MRB_BIGINT_POOL_SIZE > 0
}
#endif
/* Zero-initialize new limbs */
limb_zero(x->p + old_sz, size - old_sz);
x->sz = size;
}
}
static void
mpz_set(mpz_ctx_t *ctx, mpz_t *y, mpz_t *x)
{
size_t i, k = x->sz;
mpz_realloc(ctx, y, k);
for (i=0;i < k; i++)
y->p[i] = x->p[i];
y->sz = k;
y->sn = x->sn;
}
static void
mpz_init_set(mpz_ctx_t *ctx, mpz_t *s, mpz_t *t)
{
mpz_init(ctx, s);
mpz_set(ctx, s, t);
}
static void
mpz_set_int(mpz_ctx_t *ctx, mpz_t *y, mrb_int v)
{
mrb_uint u;
if (v == 0) {
y->sn=0;
u = 0;
}
else if (v > 0) {
y->sn = 1;
u = v;
}
else /* if (v < 0) */ {
y->sn = -1;
if (v == MRB_INT_MIN) u = v;
else u = -v;
}
#if MRB_INT_BIT > DIG_SIZE
if ((u & ~DIG_MASK) != 0) {
mpz_realloc(ctx, y, 2);
y->p[1] = (mp_limb)HIGH(u);
y->p[0] = (mp_limb)LOW(u);
return;
}
#endif
mpz_realloc(ctx, y, 1);
y->p[0] = (mp_limb)u;
}
static void
mpz_set_uint64(mpz_ctx_t *ctx, mpz_t *y, uint64_t u)
{
size_t len = 0;
for (uint64_t u0=u; u0; u0>>=DIG_SIZE,len++)
;
y->sn = (u != 0);
mpz_realloc(ctx, y, len);
for (size_t i=0; i<len; i++) {
y->p[i] = (mp_limb)LOW(u);
u >>= DIG_SIZE;
}
}
#ifdef MRB_INT32
static void
mpz_set_int64(mpz_ctx_t *ctx, mpz_t *y, int64_t v)
{
uint64_t u;
if (v < 0) {
if (v == INT64_MIN) u = v;
else u = -v;
}
else {
u = v;
}
mpz_set_uint64(ctx, y, u);
if (v < 0) {
y->sn = -1;
}
}
#endif
static void
mpz_init_set_int(mpz_ctx_t *ctx, mpz_t *y, mrb_int v)
{
mpz_init(ctx, y);
mpz_set_int(ctx, y, v);
}
static void
mpz_clear(mpz_ctx_t *ctx, mpz_t *s)
{
if (s->p) {
#if MRB_BIGINT_POOL_SIZE > 0
if (MPZ_HAS_POOL(ctx) && is_pool_memory(s, MPZ_POOL(ctx))) {
/* Pool memory - don't free, just mark as unused */
}
else {
#endif
mrb_free(MPZ_MRB(ctx), s->p);
#if MRB_BIGINT_POOL_SIZE > 0
}
#endif
s->p = NULL;
}
s->sn = 0;
s->sz = 0;
}
static void
mpz_move(mpz_ctx_t *ctx, mpz_t *y, mpz_t *x)
{
mpz_clear(ctx, y);
y->sn = x->sn;
y->sz = x->sz;
y->p = x->p;
x->p = NULL;
x->sn = 0;
x->sz = 0;
}
static size_t
digits(mpz_t *x)
{
size_t i;
if (x->sz == 0) return 0;
for (i = x->sz - 1; x->p[i] == 0 && i > 0; i--)
;
return i+1;
}
static void
trim(mpz_t *x)
{
while (x->sz && x->p[x->sz-1] == 0) {
x->sz--;
}
}
/* z = x + y, without regard for sign */
/* Core addition algorithm for unsigned operands */
static void
uadd(mpz_t *z, mpz_t *x, mpz_t *y)
{
/* Core multi-limb addition with carry propagation */
mp_dbl_limb c = 0;
size_t i;
/* Add overlapping limbs from both operands */
/* 4x unrolled loop for better performance */
for (i = 0; i + 4 <= x->sz; i += 4) {
c += (mp_dbl_limb)y->p[i] + (mp_dbl_limb)x->p[i];
z->p[i] = LOW(c);
c >>= DIG_SIZE;
c += (mp_dbl_limb)y->p[i+1] + (mp_dbl_limb)x->p[i+1];
z->p[i+1] = LOW(c);
c >>= DIG_SIZE;
c += (mp_dbl_limb)y->p[i+2] + (mp_dbl_limb)x->p[i+2];
z->p[i+2] = LOW(c);
c >>= DIG_SIZE;
c += (mp_dbl_limb)y->p[i+3] + (mp_dbl_limb)x->p[i+3];
z->p[i+3] = LOW(c);
c >>= DIG_SIZE;
}
/* Handle remaining elements */
for (; i < x->sz; i++) {
c += (mp_dbl_limb)y->p[i] + (mp_dbl_limb)x->p[i];
z->p[i] = LOW(c);
c >>= DIG_SIZE;
}
/* Add remaining limbs from larger operand */
/* 4x unrolled loop for better performance */
for (; i + 4 <= y->sz; i += 4) {
c += y->p[i];
z->p[i] = LOW(c);
c >>= DIG_SIZE;
c += y->p[i+1];
z->p[i+1] = LOW(c);
c >>= DIG_SIZE;
c += y->p[i+2];
z->p[i+2] = LOW(c);
c >>= DIG_SIZE;
c += y->p[i+3];
z->p[i+3] = LOW(c);
c >>= DIG_SIZE;
}
/* Handle remaining elements */
for (; i < y->sz; i++) {
c += y->p[i];
z->p[i] = LOW(c);
c >>= DIG_SIZE;
}
/* Store final carry */
z->p[y->sz] = (mp_limb)c;
}
/* z = y - x, ignoring sign */
/* precondition: abs(y) >= abs(x) */
/* Core subtraction algorithm for unsigned operands */
static void
usub(mpz_t *z, mpz_t *y, mpz_t *x)
{
/* Core multi-limb subtraction with borrow propagation */
mp_dbl_limb_signed b = 0;
size_t i;
/* Subtract overlapping limbs from both operands */
/* 4x unrolled loop for better performance */
for (i = 0; i + 4 <= x->sz; i += 4) {
b += (mp_dbl_limb_signed)y->p[i];
b -= (mp_dbl_limb_signed)x->p[i];
z->p[i] = LOW(b);
b = HIGH(b);
b += (mp_dbl_limb_signed)y->p[i+1];
b -= (mp_dbl_limb_signed)x->p[i+1];
z->p[i+1] = LOW(b);
b = HIGH(b);
b += (mp_dbl_limb_signed)y->p[i+2];
b -= (mp_dbl_limb_signed)x->p[i+2];
z->p[i+2] = LOW(b);
b = HIGH(b);
b += (mp_dbl_limb_signed)y->p[i+3];
b -= (mp_dbl_limb_signed)x->p[i+3];
z->p[i+3] = LOW(b);
b = HIGH(b);
}
/* Handle remaining elements */
for (; i < x->sz; i++) {
b += (mp_dbl_limb_signed)y->p[i];
b -= (mp_dbl_limb_signed)x->p[i];
z->p[i] = LOW(b);
b = HIGH(b);
}
/* Process remaining limbs from minuend with borrow */
/* 4x unrolled loop for better performance */
for (; i + 4 <= y->sz; i += 4) {
b += y->p[i];
z->p[i] = LOW(b);
b = HIGH(b);
b += y->p[i+1];
z->p[i+1] = LOW(b);
b = HIGH(b);
b += y->p[i+2];
z->p[i+2] = LOW(b);
b = HIGH(b);
b += y->p[i+3];
z->p[i+3] = LOW(b);
b = HIGH(b);
}
/* Handle remaining elements */
for (; i < y->sz; i++) {
b += y->p[i];
z->p[i] = LOW(b);
b = HIGH(b);
}
/* Normalize result size */
z->sz = digits(z);
}
/* compare abs(x) and abs(y) */
static int
ucmp(mpz_t *y, mpz_t *x)
{
if (y->sz < x->sz) return -1;
if (y->sz > x->sz) return 1;
if (x->sz == 0) return 0;
for (size_t i=x->sz-1;; i--) {
mp_limb a = y->p[i];
mp_limb b = x->p[i];
if (a > b) return 1;
if (a < b) return -1;
if (i == 0) break;
}
return 0;
}
#define zero_p(x) ((x)->sn == 0)
/* check if all digits are zero */
static int
uzero_p(mpz_t *x)
{
if (x->sz == 0) return 1;
for (size_t i=x->sz-1;; i--) {
if (x->p[i] != 0)
return 0;
if (i == 0) break;
}
return 1;
}
static void
zero(mpz_t *x)
{
x->sn=0;
if (x->p) {
x->sz=1;
x->p[0]=0;
}
else {
x->sz=0;
}
}
/* z = x + y */
static void
mpz_add(mpz_ctx_t *ctx, mpz_t *zz, mpz_t *x, mpz_t *y)
{
if (zero_p(x)) {
mpz_set(ctx, zz, y);
return;
}
if (zero_p(y)) {
mpz_set(ctx, zz, x);
return;
}
/* Fast path: single-limb + multi-limb */
if (y->sz == 1 && x->sz > 1) {
mp_limb y_limb = y->p[0];
mpz_t z;
mpz_init_heap(ctx, &z, x->sz + 1);
if ((x->sn > 0 && y->sn > 0) || (x->sn < 0 && y->sn < 0)) {
/* Same signs: addition */
mp_dbl_limb carry = y_limb;
carry += x->p[0];
z.p[0] = (mp_limb)carry;
carry >>= DIG_SIZE;
/* Propagate carry through remaining limbs */
for (size_t i = 1; i < x->sz; i++) {
carry += x->p[i];
z.p[i] = (mp_limb)carry;
carry >>= DIG_SIZE;
}
z.p[x->sz] = (mp_limb)carry;
z.sn = x->sn;
}
else {
/* Different signs: subtraction */
if (x->sz == 1 && y_limb == x->p[0]) {
/* Equal magnitude: result is zero */
zero(&z);
}
else if (x->sz == 1 && x->p[0] > y_limb) {
/* |x| > |y|: result has sign of x */
z.p[0] = x->p[0] - y_limb;
z.p[1] = 0;
z.sn = x->sn;
}
else {
/* |x| > |y|: subtract y from x */
mp_dbl_limb borrow = y_limb;
if (x->p[0] >= borrow) {
z.p[0] = x->p[0] - (mp_limb)borrow;
borrow = 0;
}
else {
z.p[0] = ((mp_dbl_limb)1 << DIG_SIZE) + x->p[0] - (mp_limb)borrow;
borrow = 1;
}
/* Propagate borrow through remaining limbs */
for (size_t i = 1; i < x->sz; i++) {
if (x->p[i] >= borrow) {
z.p[i] = x->p[i] - (mp_limb)borrow;
borrow = 0;
}
else {
z.p[i] = ((mp_dbl_limb)1 << DIG_SIZE) + x->p[i] - (mp_limb)borrow;
borrow = 1;
}
}
z.sn = x->sn;
}
}
trim(&z);
mpz_move(ctx, zz, &z);
return;
}
if (x->sz == 1 && y->sz > 1) {
/* Swap and use the same fast path */
mpz_add(ctx, zz, y, x);
return;
}
mpz_t z;
size_t estimated_size = ((x->sz > y->sz) ? x->sz : y->sz) + 1;
mpz_init_heap(ctx, &z, estimated_size);
if (x->sn > 0 && y->sn > 0) {
uadd(&z, x, y);
z.sn = 1;
}
else if (x->sn < 0 && y->sn < 0) {
uadd(&z, x, y);
z.sn = -1;
}
else {
int mg;
/* signs differ */
if ((mg = ucmp(x,y)) == 0) {
zero(&z);
}
else if (mg > 0) { /* abs(y) < abs(x) */
usub(&z, x, y);
z.sn = (x->sn > 0 && y->sn < 0) ? 1 : (-1);
}
else { /* abs(y) > abs(x) */
usub(&z, y, x);
z.sn = (x->sn < 0 && y->sn > 0) ? 1 : (-1);
}
}
trim(&z);
mpz_move(ctx, zz, &z);
}
/* x += n */
/* ignores sign of x */
/* assumes n is positive and small (fits in mp_limb) */
static void
mpz_add_int(mpz_ctx_t *ctx, mpz_t *x, mrb_int n)
{
// If n is zero, no operation is needed
if (n == 0) return;
// Assume x is positive and n is a small positive integer
mp_dbl_limb carry = n; // Initialize carry with n
for (size_t i = 0; i < x->sz && carry; i++) {
carry += (mp_dbl_limb)x->p[i]; // Add current limb and carry
x->p[i] = LOW(carry); // Store lower 32 bits in current limb
carry = HIGH(carry); // Update carry with higher bits
}
if (carry != 0) {
mpz_realloc(ctx, x, x->sz + 1);
x->p[x->sz-1] = (mp_limb)carry;
x->sn = 1;
}
trim(x);
}
/* z = x - y -- just use mpz_add - I'm lazy */
static void
mpz_sub(mpz_ctx_t *ctx, mpz_t *z, mpz_t *x, mpz_t *y)
{
mpz_t u;
/* Initialize u as a view of y with negated sign - no new memory allocated */
u.p = y->p;
u.sz = y->sz;
u.sn = -(y->sn);
mpz_add(ctx, z, x, &u);
/* No mpz_clear needed since u.p points to y->p (no separate allocation) */
}
/* x -= n */
/* ignores sign of x */
/* assumes n is positive and small (fits in mp_limb) */
static void
mpz_sub_int(mpz_ctx_t *ctx, mpz_t *x, mrb_int n)
{
// If n is zero, no operation is needed
if (n == 0) return;
// If x is zero, set x to n
if (zero_p(x) || x->sz == 0) {
mpz_set_int(ctx, x, n);
return;
}
// Initialize borrow and start decrement
mp_dbl_limb_signed borrow = (mp_limb)n;
size_t i = 0;
// Subtract 1 from the least significant limb and propagate if necessary
borrow = (mp_dbl_limb_signed)x->p[i] - borrow;
x->p[i] = LOW(borrow);
borrow = (borrow < 0) ? 1 : 0;
// Continue through limbs while there is a borrow
for (i = 1; i < x->sz && borrow; i++) {
borrow = (mp_dbl_limb_signed)x->p[i] - borrow;
x->p[i] = LOW(borrow);
borrow = (borrow < 0) ? 1 : 0;
}
// Trim any unnecessary leading zeros
trim(x);
}
/* Multiply-and-add: rp[0..n-1] += s1p[0..n-1] * limb; return carry (high limb) */
static inline mp_limb
limb_addmul_1(mp_limb *rp, const mp_limb *s1p, size_t n, mp_limb limb)
{
#if defined(__SIZEOF_INT128__) && (__SIZEOF_INT128__ == 16)
/* Use 128-bit arithmetic with 8x unrolling for maximum efficiency */
unsigned __int128 acc = 0;
size_t i;
/* 8x unrolled loop for large operands */
for (i = 0; i + 8 <= n; i += 8) {
acc += (unsigned __int128)rp[i] + (unsigned __int128)s1p[i] * (unsigned __int128)limb;
rp[i] = (mp_limb)acc;
acc >>= DIG_SIZE;
acc += (unsigned __int128)rp[i+1] + (unsigned __int128)s1p[i+1] * (unsigned __int128)limb;
rp[i+1] = (mp_limb)acc;
acc >>= DIG_SIZE;
acc += (unsigned __int128)rp[i+2] + (unsigned __int128)s1p[i+2] * (unsigned __int128)limb;
rp[i+2] = (mp_limb)acc;
acc >>= DIG_SIZE;
acc += (unsigned __int128)rp[i+3] + (unsigned __int128)s1p[i+3] * (unsigned __int128)limb;
rp[i+3] = (mp_limb)acc;
acc >>= DIG_SIZE;
acc += (unsigned __int128)rp[i+4] + (unsigned __int128)s1p[i+4] * (unsigned __int128)limb;
rp[i+4] = (mp_limb)acc;
acc >>= DIG_SIZE;
acc += (unsigned __int128)rp[i+5] + (unsigned __int128)s1p[i+5] * (unsigned __int128)limb;
rp[i+5] = (mp_limb)acc;
acc >>= DIG_SIZE;
acc += (unsigned __int128)rp[i+6] + (unsigned __int128)s1p[i+6] * (unsigned __int128)limb;
rp[i+6] = (mp_limb)acc;
acc >>= DIG_SIZE;
acc += (unsigned __int128)rp[i+7] + (unsigned __int128)s1p[i+7] * (unsigned __int128)limb;
rp[i+7] = (mp_limb)acc;
acc >>= DIG_SIZE;
}
/* 4x unrolled loop for medium operands */
for (; i + 4 <= n; i += 4) {
acc += (unsigned __int128)rp[i] + (unsigned __int128)s1p[i] * (unsigned __int128)limb;
rp[i] = (mp_limb)acc;
acc >>= DIG_SIZE;
acc += (unsigned __int128)rp[i+1] + (unsigned __int128)s1p[i+1] * (unsigned __int128)limb;
rp[i+1] = (mp_limb)acc;
acc >>= DIG_SIZE;
acc += (unsigned __int128)rp[i+2] + (unsigned __int128)s1p[i+2] * (unsigned __int128)limb;
rp[i+2] = (mp_limb)acc;
acc >>= DIG_SIZE;
acc += (unsigned __int128)rp[i+3] + (unsigned __int128)s1p[i+3] * (unsigned __int128)limb;
rp[i+3] = (mp_limb)acc;
acc >>= DIG_SIZE;
}
/* Handle remaining elements */
for (; i < n; i++) {
acc += (unsigned __int128)rp[i] + (unsigned __int128)s1p[i] * (unsigned __int128)limb;
rp[i] = (mp_limb)acc;
acc >>= DIG_SIZE;
}
return (mp_limb)acc;
#elif defined(_MSC_VER) && defined(MRB_64BIT)
/* 64-bit limbs on MSVC with 6x unrolling: use _umul128 */
unsigned long long carry = 0;
size_t i;
/* 6x unrolled loop for large operands */
for (i = 0; i + 6 <= n; i += 6) {
unsigned long long hi, lo, sum;
lo = _umul128((unsigned long long)s1p[i], (unsigned long long)limb, &hi);
sum = (unsigned long long)rp[i] + lo + carry;
rp[i] = (mp_limb)sum;
carry = hi + (sum < lo);
lo = _umul128((unsigned long long)s1p[i+1], (unsigned long long)limb, &hi);
sum = (unsigned long long)rp[i+1] + lo + carry;
rp[i+1] = (mp_limb)sum;
carry = hi + (sum < lo);
lo = _umul128((unsigned long long)s1p[i+2], (unsigned long long)limb, &hi);
sum = (unsigned long long)rp[i+2] + lo + carry;
rp[i+2] = (mp_limb)sum;
carry = hi + (sum < lo);
lo = _umul128((unsigned long long)s1p[i+3], (unsigned long long)limb, &hi);
sum = (unsigned long long)rp[i+3] + lo + carry;
rp[i+3] = (mp_limb)sum;
carry = hi + (sum < lo);
lo = _umul128((unsigned long long)s1p[i+4], (unsigned long long)limb, &hi);
sum = (unsigned long long)rp[i+4] + lo + carry;
rp[i+4] = (mp_limb)sum;
carry = hi + (sum < lo);
lo = _umul128((unsigned long long)s1p[i+5], (unsigned long long)limb, &hi);
sum = (unsigned long long)rp[i+5] + lo + carry;
rp[i+5] = (mp_limb)sum;
carry = hi + (sum < lo);
}
/* 3x unrolled loop for medium operands */
for (; i + 3 <= n; i += 3) {
unsigned long long hi, lo, sum;
lo = _umul128((unsigned long long)s1p[i], (unsigned long long)limb, &hi);
sum = (unsigned long long)rp[i] + lo + carry;
rp[i] = (mp_limb)sum;
carry = hi + (sum < lo);
lo = _umul128((unsigned long long)s1p[i+1], (unsigned long long)limb, &hi);
sum = (unsigned long long)rp[i+1] + lo + carry;
rp[i+1] = (mp_limb)sum;
carry = hi + (sum < lo);
lo = _umul128((unsigned long long)s1p[i+2], (unsigned long long)limb, &hi);
sum = (unsigned long long)rp[i+2] + lo + carry;
rp[i+2] = (mp_limb)sum;
carry = hi + (sum < lo);
}
/* Handle remaining elements */
for (; i < n; i++) {
unsigned long long hi, lo;
lo = _umul128((unsigned long long)s1p[i], (unsigned long long)limb, &hi);
unsigned long long sum = (unsigned long long)rp[i] + lo + carry;
rp[i] = (mp_limb)sum;
carry = hi + (sum < lo);
}
return (mp_limb)carry;
#else
/* Portable double-limb path with 4x unrolling */
mp_dbl_limb acc = 0;
size_t i;
/* 4x unrolled loop for better performance */
for (i = 0; i + 4 <= n; i += 4) {
acc += (mp_dbl_limb)rp[i] + (mp_dbl_limb)s1p[i] * (mp_dbl_limb)limb;
rp[i] = LOW(acc);
acc = HIGH(acc);
acc += (mp_dbl_limb)rp[i+1] + (mp_dbl_limb)s1p[i+1] * (mp_dbl_limb)limb;
rp[i+1] = LOW(acc);
acc = HIGH(acc);
acc += (mp_dbl_limb)rp[i+2] + (mp_dbl_limb)s1p[i+2] * (mp_dbl_limb)limb;
rp[i+2] = LOW(acc);
acc = HIGH(acc);
acc += (mp_dbl_limb)rp[i+3] + (mp_dbl_limb)s1p[i+3] * (mp_dbl_limb)limb;
rp[i+3] = LOW(acc);
acc = HIGH(acc);
}
/* Handle remaining elements */
for (; i < n; i++) {
acc += (mp_dbl_limb)rp[i] + (mp_dbl_limb)s1p[i] * (mp_dbl_limb)limb;
rp[i] = LOW(acc);
acc = HIGH(acc);
}
return (mp_limb)acc;
#endif
}
#define KARATSUBA_THRESHOLD 8
static inline mrb_bool
should_use_karatsuba(size_t x_len, size_t y_len)
{
return x_len >= KARATSUBA_THRESHOLD && y_len >= KARATSUBA_THRESHOLD;
}
/* w = u * v (optimized schoolbook using limb_addmul_1) */
static void
mpz_mul_basic(mpz_ctx_t *ctx, mpz_t *ww, mpz_t *u, mpz_t *v)
{
if (zero_p(u) || zero_p(v)) {
zero(ww);
return;
}
/* Ensure outer loop iterates over the shorter operand for better cache use */
mpz_t *a, *b;
if (v->sz > u->sz) {
a = v; b = u;
}
else {
a = u; b = v;
}
/* Fast path: single-limb × multi-limb */
if (b->sz == 1) {
mp_limb scalar = b->p[0];
mpz_t w;
mpz_init_heap(ctx, &w, a->sz + 1);
limb_zero(w.p, a->sz + 1);
mp_limb carry = limb_addmul_1(w.p, a->p, a->sz, scalar);
w.p[a->sz] = carry;
w.sn = a->sn * b->sn;
trim(&w);
mpz_move(ctx, ww, &w);
return;
}
mpz_t w;
mpz_init_heap(ctx, &w, a->sz + b->sz);
limb_zero(w.p, a->sz + b->sz);
for (size_t j = 0; j < a->sz; j++) {
mp_limb a_limb = a->p[j];
if (a_limb == 0) continue;
mp_limb carry = limb_addmul_1(w.p + j, b->p, b->sz, a_limb);
w.p[j + b->sz] += carry;
}
w.sn = a->sn * b->sn;
trim(&w);
mpz_move(ctx, ww, &w);
}
/* Allocation-free Karatsuba helper functions */
/* Copy limbs: dest[0..n-1] = src[0..n-1] */
static void
limb_copy(mp_limb *dest, const mp_limb *src, size_t n)
{
if (n > 0) {
memcpy(dest, src, n * sizeof(mp_limb));
}
}
/* Add limbs at offset: dest[offset..offset+n-1] += src[0..n-1] */
static void
limb_add_at(mp_limb *dest, size_t dest_len, const mp_limb *src, size_t n, size_t offset)
{
mp_limb carry = 0;
size_t i = 0;
for (i = 0; i < n; i++) {
mp_dbl_limb sum = (mp_dbl_limb)dest[offset + i] + (mp_dbl_limb)src[i] + carry;
dest[offset + i] = LOW(sum);
carry = HIGH(sum);
}
/* Propagate final carry */
i = offset + n;
while (carry && i < dest_len) {
mp_dbl_limb sum = (mp_dbl_limb)dest[i] + carry;
dest[i] = LOW(sum);
carry = HIGH(sum);
i++;
}
}
/* Subtract limbs: dest[0..n-1] -= src[0..n-1], returns borrow */
static mp_limb
limb_sub(mp_limb *dest, const mp_limb *src, size_t n)
{
mp_dbl_limb_signed borrow = 0;
for (size_t i = 0; i < n; i++) {
borrow += (mp_dbl_limb_signed)dest[i] - (mp_dbl_limb_signed)src[i];
dest[i] = LOW(borrow);
borrow = HIGH(borrow);
}
return (mp_limb)(-borrow);
}
/* Basic multiplication for small operands */
static void
mpz_mul_basic_limbs(mp_limb *result, const mp_limb *x, size_t x_len,
const mp_limb *y, size_t y_len)
{
limb_zero(result, x_len + y_len);
for (size_t i = 0; i < x_len; i++) {
if (x[i] == 0) continue;
mp_limb carry = limb_addmul_1(result + i, y, y_len, x[i]);
if (i + y_len < x_len + y_len) {
result[i + y_len] += carry;
}
}
}
/* Calculate scratch space needed for Karatsuba */
static size_t
karatsuba_scratch_size(size_t x_len, size_t y_len)
{
if (!should_use_karatsuba(x_len, y_len)) {
return 0;
}
if (x_len < y_len) {
size_t tmp = x_len; x_len = y_len; y_len = tmp;
}
size_t half = y_len / 2;
size_t x1_len = x_len - half;
size_t y1_len = y_len - half;
size_t sum_x_len = x1_len + 1;
size_t sum_y_len = y1_len + 1;
size_t z0_len = half + half;
size_t z2_len = x1_len + y1_len;
size_t z1_len = sum_x_len + sum_y_len;
size_t current_level_scratch = z0_len + z2_len + z1_len + sum_x_len + sum_y_len;
size_t sub_scratch = karatsuba_scratch_size(sum_x_len, sum_y_len);
size_t sub2 = karatsuba_scratch_size(x1_len, y1_len);
size_t sub3 = karatsuba_scratch_size(half, half);
if (sub2 > sub_scratch) sub_scratch = sub2;
if (sub3 > sub_scratch) sub_scratch = sub3;
return current_level_scratch + sub_scratch;
}
/* Pool-aware Karatsuba - zero intermediate allocations */
static void
mpz_mul_karatsuba(mpz_ctx_t *ctx, mp_limb *result,
const mp_limb *x, size_t x_len,
const mp_limb *y, size_t y_len,
mp_limb *scratch)
{
/* Base case - use basic multiplication */
if (!should_use_karatsuba(x_len, y_len)) {
mpz_mul_basic_limbs(result, x, x_len, y, y_len);
return;
}
/* Make x the larger operand for consistent partitioning */
if (x_len < y_len) {
const mp_limb *tmp_ptr = x; x = y; y = tmp_ptr;
size_t tmp_len = x_len; x_len = y_len; y_len = tmp_len;
}
/* Partition inputs */
size_t half = y_len / 2;
const mp_limb *x0 = x;
const mp_limb *x1 = x + half;
const mp_limb *y0 = y;
const mp_limb *y1 = y + half;
size_t x0_len = half;
size_t x1_len = x_len - half;
size_t y0_len = half;
size_t y1_len = y_len - half;
/* Partition scratch memory */
size_t offset = 0;
mp_limb *z0 = scratch + offset; offset += x0_len + y0_len;
mp_limb *z2 = scratch + offset; offset += x1_len + y1_len;
mp_limb *sum_x = scratch + offset; offset += x1_len + 1;
mp_limb *sum_y = scratch + offset; offset += y1_len + 1;
mp_limb *z1 = scratch + offset;
size_t z1_alloc_len = (x1_len + 1) + (y1_len + 1);
offset += z1_alloc_len;
/* Step 1: Compute sums x0+x1 and y0+y1 */
mp_limb carry_x = 0;
size_t i;
for (i = 0; i < x1_len; i++) {
mp_dbl_limb sum = (mp_dbl_limb)(i < x0_len ? x0[i] : 0) + (mp_dbl_limb)x1[i] + carry_x;
sum_x[i] = LOW(sum);
carry_x = HIGH(sum);
}
sum_x[i] = carry_x;
size_t sum_x_len = x1_len + (carry_x != 0);
mp_limb carry_y = 0;
for (i = 0; i < y1_len; i++) {
mp_dbl_limb sum = (mp_dbl_limb)(i < y0_len ? y0[i] : 0) + (mp_dbl_limb)y1[i] + carry_y;
sum_y[i] = LOW(sum);
carry_y = HIGH(sum);
}
sum_y[i] = carry_y;
size_t sum_y_len = y1_len + (carry_y != 0);
/* Step 2: Recursive multiplications */
mp_limb *recursive_scratch = scratch + offset;
mpz_mul_karatsuba(ctx, z0, x0, x0_len, y0, y0_len, recursive_scratch);
mpz_mul_karatsuba(ctx, z2, x1, x1_len, y1, y1_len, recursive_scratch);
mpz_mul_karatsuba(ctx, z1, sum_x, sum_x_len, sum_y, sum_y_len, recursive_scratch);
/* Step 3: Compute z1 = z1 - z0 - z2 */
size_t z0_len = x0_len + y0_len;
size_t z2_len = x1_len + y1_len;
size_t z1_len = sum_x_len + sum_y_len;
mp_limb borrow = limb_sub(z1, z0, z0_len);
for (i = z0_len; i < z1_len && borrow; i++) {
mp_dbl_limb_signed diff = (mp_dbl_limb_signed)z1[i] - borrow;
z1[i] = LOW(diff);
borrow = (diff < 0) ? 1 : 0;
}
borrow = limb_sub(z1, z2, z2_len);
for (i = z2_len; i < z1_len && borrow; i++) {
mp_dbl_limb_signed diff = (mp_dbl_limb_signed)z1[i] - borrow;
z1[i] = LOW(diff);
borrow = (diff < 0) ? 1 : 0;
}
/* Step 4: Final assembly: result = z0 + z1*B + z2*B^2 */
size_t result_len = x_len + y_len;
limb_zero(result, result_len);
limb_copy(result, z0, z0_len);
limb_add_at(result, result_len, z1, z1_len, half);
limb_add_at(result, result_len, z2, z2_len, 2 * half);
}
/* w = u * v */
static void
mpz_mul(mpz_ctx_t *ctx, mpz_t *ww, mpz_t *u, mpz_t *v)
{
if (zero_p(u) || zero_p(v)) {
zero(ww);
return;
}
if (!should_use_karatsuba(u->sz, v->sz)) {
mpz_mul_basic(ctx, ww, u, v);
return;
}
size_t result_size = u->sz + v->sz;
mpz_realloc(ctx, ww, result_size);
size_t scratch_size = karatsuba_scratch_size(u->sz, v->sz);
size_t pool_state = pool_save(ctx);
mp_limb *scratch = NULL;
if (MPZ_HAS_POOL(ctx)) {
scratch = pool_alloc(MPZ_POOL(ctx), scratch_size);
}
if (scratch) {
mpz_mul_karatsuba(ctx, ww->p, u->p, u->sz, v->p, v->sz, scratch);
pool_restore(ctx, pool_state);
}
else {
/* Fallback to heap allocation for scratch space if pool fails */
scratch = (mp_limb*)mrb_malloc(MPZ_MRB(ctx), scratch_size * sizeof(mp_limb));
mpz_mul_karatsuba(ctx, ww->p, u->p, u->sz, v->p, v->sz, scratch);
mrb_free(MPZ_MRB(ctx), scratch);
}
ww->sz = result_size;
ww->sn = u->sn * v->sn;
trim(ww);
}
/* number of leading zero bits in digit */
static int
lzb(mp_limb x)
{
if (x == 0) return 0;
#if (defined(__GNUC__) || __has_builtin(__builtin_clz))
if (sizeof(mp_limb) == sizeof(int64_t))
return __builtin_clzll(x);
else if (sizeof(mp_limb) == sizeof(int32_t))
return __builtin_clz(x);
#endif
int j=0;
for (mp_limb i = ((mp_limb)1 << (DIG_SIZE-1)); i && !(x&i); j++,i>>=1)
;
return j;
}
/* c1 = a>>n */
/* n must be < DIG_SIZE */
static void
urshift(mpz_ctx_t *ctx, mpz_t *c1, mpz_t *a, size_t n)
{
mrb_assert(n < DIG_SIZE);
if (n == 0)
mpz_set(ctx, c1, a);
else if (uzero_p(a)) {
zero(c1);
}
else {
mpz_t c;
mp_limb cc = 0;
mp_dbl_limb rm = (((mp_dbl_limb)1<<n) - 1);
mpz_init_heap(ctx, &c, a->sz);
for (size_t i=a->sz-1;; i--) {
c.p[i] = ((a->p[i] >> n) | cc) & DIG_MASK;
cc = (a->p[i] & rm) << (DIG_SIZE - n);
if (i == 0) break;
}
trim(&c);
mpz_move(ctx, c1, &c);
}
}
/* c1 = a<<n */
/* n must be < DIG_SIZE */
static void
ulshift(mpz_ctx_t *ctx, mpz_t *c1, mpz_t *a, size_t n)
{
mrb_assert(n < DIG_SIZE);
if (n == 0)
mpz_set(ctx, c1, a);
else if (uzero_p(a)) {
zero(c1);
}
else {
mp_limb cc = 0;
mpz_t c;
mp_limb rm = (((mp_dbl_limb)1<<n) - 1) << (DIG_SIZE-n);
mpz_init_heap(ctx, &c, a->sz+1);
size_t i;
for (i=0; i<a->sz; i++) {
c.p[i] = ((a->p[i] << n) | cc) & DIG_MASK;
cc = (a->p[i] & rm) >> (DIG_SIZE-n);
}
c.p[i] = cc;
trim(&c);
mpz_move(ctx, c1, &c);
}
}
/* Fast division by single limb */
static void
div_limb(mpz_ctx_t *ctx, mpz_t *q, mpz_t *r, mpz_t *x, mp_limb d)
{
mrb_state *mrb = MPZ_MRB(ctx);
size_t pool_state = pool_save(ctx);
mpz_t temp_q, temp_r;
size_t n;
mp_dbl_limb remainder;
if (zero_p(x)) {
zero(q);
zero(r);
goto cleanup;
}
if (d == 0) {
mrb_raise(mrb, E_ZERODIV_ERROR, "divided by 0");
}
/* Power-of-2 divisor optimization */
if ((d & (d - 1)) == 0) {
/* d is power of 2, use bit operations */
int shift = 0;
mp_limb temp = d;
while (temp > 1) {
temp >>= 1;
shift++;
}
/* Quotient = x >> shift */
if (shift == 0) {
mpz_init(ctx, &temp_q);
mpz_init(ctx, &temp_r);
mpz_set(ctx, &temp_q, x);
}
else {
/* Manual right shift implementation */
size_t limb_shift = shift / DIG_SIZE;
size_t bit_shift = shift % DIG_SIZE;
if (limb_shift >= x->sz) {
mpz_init(ctx, &temp_q);
mpz_init(ctx, &temp_r);
zero(&temp_q);
}
else {
size_t new_size = x->sz - limb_shift;
mpz_init_heap(ctx, &temp_q, new_size);
mpz_init(ctx, &temp_r);
if (bit_shift == 0) {
/* Simple limb copy */
for (size_t i = 0; i < new_size; i++) {
temp_q.p[i] = x->p[i + limb_shift];
}
}
else {
/* Bit shift within limbs */
mp_limb carry = 0;
for (size_t i = new_size; i > 0; i--) {
mp_limb current = x->p[i - 1 + limb_shift];
temp_q.p[i - 1] = (current >> bit_shift) | carry;
carry = (current << (DIG_SIZE - bit_shift)) & DIG_MASK;
}
}
temp_q.sz = new_size;
trim(&temp_q);
temp_q.sn = (temp_q.sz == 0) ? 0 : 1;
}
}
/* Remainder = x & (d - 1) */
/* temp_r is already initialized in all code paths above */
mpz_realloc(ctx, &temp_r, 1);
temp_r.p[0] = x->p[0] & (d - 1);
temp_r.sz = (temp_r.p[0] == 0) ? 0 : 1;
temp_r.sn = (temp_r.sz == 0) ? 0 : 1;
mpz_move(ctx, q, &temp_q);
mpz_move(ctx, r, &temp_r);
goto cleanup;
}
/* General single-limb division */
if (x->sz == 1) {
/* Both dividend and divisor are single limb */
mpz_init_heap(ctx, &temp_q, 1);
mpz_init_heap(ctx, &temp_r, 1);
temp_q.p[0] = x->p[0] / d;
temp_r.p[0] = x->p[0] % d;
temp_q.sz = (temp_q.p[0] == 0) ? 0 : 1;
temp_q.sn = (temp_q.sz == 0) ? 0 : 1;
temp_r.sz = (temp_r.p[0] == 0) ? 0 : 1;
temp_r.sn = (temp_r.sz == 0) ? 0 : 1;
mpz_move(ctx, q, &temp_q);
mpz_move(ctx, r, &temp_r);
goto cleanup;
}
/* Multi-limb dividend, single-limb divisor */
n = x->sz;
mpz_init_heap(ctx, &temp_q, n);
mpz_init_heap(ctx, &temp_r, 1);
remainder = 0;
/* Process from most significant limb to least significant */
for (size_t i = n; i > 0; i--) {
remainder = (remainder << DIG_SIZE) + x->p[i-1];
temp_q.p[i-1] = (mp_limb)(remainder / d);
remainder = remainder % d;
}
/* Set remainder */
temp_r.p[0] = (mp_limb)remainder;
temp_r.sz = (remainder == 0) ? 0 : 1;
temp_r.sn = (temp_r.sz == 0) ? 0 : 1;
/* Trim leading zeros from quotient */
trim(&temp_q);
temp_q.sn = (temp_q.sz == 0) ? 0 : 1;
/* Copy results to avoid pool/heap mixing */
mpz_move(ctx, q, &temp_q);
mpz_move(ctx, r, &temp_r);
cleanup:
pool_restore(ctx, pool_state);
}
/* internal routine to compute x/y and x%y ignoring signs */
/* qq = xx/yy; rr = xx%yy */
static void
udiv(mpz_ctx_t *ctx, mpz_t *qq, mpz_t *rr, mpz_t *xx, mpz_t *yy)
{
/* Handle simple cases */
int cmp = ucmp(xx, yy);
if (cmp == 0) {
mpz_set_int(ctx, qq, 1);
zero(rr);
return;
}
else if (cmp < 0) {
zero(qq);
mpz_set(ctx, rr, xx);
return;
}
/* Fast path for single-limb divisor */
if (yy->sz == 1) {
div_limb(ctx, qq, rr, xx, yy->p[0]);
return;
}
mrb_assert(yy->sn != 0); /* divided by zero */
mrb_assert(yy->sz > 0); /* divided by zero */
/* Use new context architecture with automatic pool/heap management */
size_t pool_state = pool_save(ctx);
mpz_t q, x, y;
mpz_init_temp(ctx, &q, xx->sz - yy->sz + 1); /* Quotient size estimate */
mpz_init_temp(ctx, &x, xx->sz + 1); /* Dividend with potential carry */
mpz_init_temp(ctx, &y, yy->sz); /* Divisor copy */
mpz_realloc(ctx, &x, xx->sz+1);
size_t yd = digits(yy);
size_t ns = lzb(yy->p[yd-1]);
ulshift(ctx, &x, xx, ns);
ulshift(ctx, &y, yy, ns);
size_t xd = digits(&x);
mpz_realloc(ctx, &q, xd-yd+1); // Quotient has xd-yd+1 digits maximum
/* Core Knuth Algorithm D division loop */
mp_dbl_limb z = y.p[yd-1];
if (xd >= yd) {
for (size_t j = xd - yd;; j--) {
mp_dbl_limb qhat;
mp_dbl_limb rhat;
if (j + yd == xd) {
/* Only one high limb available */
mp_dbl_limb dividend_val = (((mp_dbl_limb)0 << DIG_SIZE) + x.p[j+yd-1]);
qhat = dividend_val / z;
rhat = dividend_val % z;
}
else {
/* Two limbs available - use enhanced estimation */
mp_dbl_limb dividend_val = ((mp_dbl_limb)x.p[j+yd] << DIG_SIZE) + x.p[j+yd-1];
qhat = dividend_val / z;
rhat = dividend_val % z;
/* Three-limb pre-adjustment when available */
if (yd >= 2 && j+yd-2 < x.sz && y.p[yd-2] != 0) {
mp_dbl_limb y_second = y.p[yd-2];
mp_dbl_limb x_third = x.p[j+yd-2];
if (qhat > 0) {
mp_dbl_limb left = qhat * y_second;
mp_dbl_limb right = (rhat << DIG_SIZE) + x_third;
if (qhat >= ((mp_dbl_limb)1 << DIG_SIZE) || left > right) {
qhat--;
rhat += z;
}
}
}
}
/* Enhanced qhat refinement step */
if (yd > 2) { // Now considering at least 3 limbs of divisor
mp_dbl_limb y_second = y.p[yd-2];
mp_dbl_limb y_third = y.p[yd-3]; // New: third limb of divisor
mp_dbl_limb x_third = (j+yd-2 < x.sz) ? x.p[j+yd-2] : 0;
mp_dbl_limb x_fourth = (j+yd-3 < x.sz) ? x.p[j+yd-3] : 0; // New: fourth limb of dividend
// Initial check with 2 limbs
mp_dbl_limb left_side = qhat * y_second;
mp_dbl_limb right_side = (rhat << DIG_SIZE) + x_third;
while (qhat >= ((mp_dbl_limb)1 << DIG_SIZE) || (left_side > right_side)) {
qhat--;
rhat += z;
if (rhat >= ((mp_dbl_limb)1 << DIG_SIZE)) break;
left_side -= y_second;
right_side = (rhat << DIG_SIZE) + x_third;
}
// Additional check with 3 limbs (new refinement)
left_side = qhat * y_third;
right_side = (rhat << DIG_SIZE) + x_fourth;
while (qhat >= ((mp_dbl_limb)1 << DIG_SIZE) || (left_side > right_side)) {
qhat--;
rhat += z;
if (rhat >= ((mp_dbl_limb)1 << DIG_SIZE)) break;
left_side -= y_third;
right_side = (rhat << DIG_SIZE) + x_fourth;
}
} else if (yd == 2) { // Original 2-limb check
mp_dbl_limb y_second = y.p[yd-2];
mp_dbl_limb x_third = (j+yd-2 < x.sz) ? x.p[j+yd-2] : 0;
mp_dbl_limb left_side = qhat * y_second;
mp_dbl_limb right_side = (rhat << DIG_SIZE) + x_third;
while (qhat >= ((mp_dbl_limb)1 << DIG_SIZE) || (left_side > right_side)) {
qhat--;
rhat += z;
if (rhat >= ((mp_dbl_limb)1 << DIG_SIZE)) break;
left_side -= y_second;
right_side = (rhat << DIG_SIZE) + x_third;
}
}
if (qhat > 0) {
/* Subtract qhat * divisor from dividend */
mp_dbl_limb_signed borrow = 0;
size_t i;
for (i = 0; i < yd; i++) {
mp_dbl_limb product = qhat * y.p[i];
mp_dbl_limb_signed diff = (mp_dbl_limb_signed)x.p[i+j] - (mp_dbl_limb_signed)LOW(product) + borrow;
x.p[i+j] = LOW(diff);
borrow = HIGH(diff) - (mp_dbl_limb_signed)HIGH(product);
}
/* Handle final borrow propagation */
if (i+j < x.sz) {
borrow += (mp_dbl_limb_signed)x.p[i+j];
x.p[i+j] = LOW(borrow);
borrow = HIGH(borrow);
}
/* Correction: if borrow is negative, qhat was too large, add back */
if (borrow < 0) {
qhat--;
mp_dbl_limb carry = 0;
for (i = 0; i < yd; i++) {
carry += (mp_dbl_limb)x.p[i+j] + (mp_dbl_limb)y.p[i];
x.p[i+j] = LOW(carry);
carry = HIGH(carry);
}
if (i+j < x.sz && carry > 0) {
x.p[i+j] += (mp_limb)carry;
}
}
}
q.p[j] = (mp_limb)qhat;
if (j == 0) break;
}
}
x.sz = yy->sz;
urshift(ctx, rr, &x, ns);
trim(&q);
mpz_move(ctx, qq, &q);
mpz_clear(ctx, &q);
mpz_clear(ctx, &x);
mpz_clear(ctx, &y);
pool_restore(ctx, pool_state);
}
static void
mpz_mdiv(mpz_ctx_t *ctx, mpz_t *q, mpz_t *x, mpz_t *y)
{
mpz_t r;
short sn1 = x->sn, sn2 = y->sn, qsign;
if (zero_p(x)) {
mpz_init_set_int(ctx, q, 0);
return;
}
mpz_init(ctx, &r);
udiv(ctx, q, &r, x, y);
qsign = q->sn = sn1 * sn2;
if (uzero_p(q))
q->sn = 0;
/* now if r != 0 and q < 0 we need to round q towards -inf */
if (!uzero_p(&r) && qsign < 0) {
/* add 1 to magnitude */
mpz_add_int(ctx, q, 1);
/* force negative sign in case the value of q was zero before rounding */
q->sn = -1;
}
mpz_clear(ctx, &r);
}
static void
mpz_mmod(mpz_ctx_t *ctx, mpz_t *r, mpz_t *x, mpz_t *y)
{
mpz_t q;
short sn1 = x->sn, sn2 = y->sn, sn3;
mpz_init(ctx, &q);
if (sn1 == 0) {
zero(r);
return;
}
udiv(ctx, &q, r, x, y);
mpz_clear(ctx, &q);
if (uzero_p(r)) {
r->sn = 0;
return;
}
sn3 = sn1 * sn2;
if (sn3 > 0)
r->sn = sn1;
else if (sn1 < 0 && sn2 > 0) {
r->sn = 1;
mpz_sub(ctx, r, y, r);
}
else {
r->sn = 1;
mpz_add(ctx, r, y, r);
}
}
static void
mpz_mdivmod(mpz_ctx_t *ctx, mpz_t *q, mpz_t *r, mpz_t *x, mpz_t *y)
{
short sn1 = x->sn, sn2 = y->sn, qsign;
if (sn1 == 0) {
zero(q);
zero(r);
return;
}
udiv(ctx, q, r, x, y);
qsign = q->sn = sn1 * sn2;
if (uzero_p(r)) {
/* q != 0, since q=r=0 would mean x=0, which was tested above */
r->sn = 0;
return;
}
if (q->sn > 0)
r->sn = sn1;
else if (sn1 < 0 && sn2 > 0) {
r->sn = 1;
mpz_sub(ctx, r, y, r);
}
else {
r->sn = 1;
mpz_add(ctx, r, y, r);
}
if (uzero_p(q))
q->sn = 0;
/* now if r != 0 and q < 0 we need to round q towards -inf */
if (!uzero_p(r) && qsign < 0) {
/* add 1 to magnitude */
mpz_add_int(ctx, q, 1);
/* force negative sign in case the value of q was zero before rounding */
q->sn = -1;
}
}
/* Fast modular reduction for single-limb modulus */
static void
mpz_mod_limb(mpz_ctx_t *ctx, mpz_t *r, mpz_t *x, mp_limb m)
{
if (zero_p(x)) {
zero(r);
return;
}
if (x->sz == 1) {
/* Single limb case - simple modulo */
mp_limb result = x->p[0] % m;
mpz_set_int(ctx, r, result);
r->sn = x->sn;
return;
}
/* Multi-limb case - use repeated division */
mp_dbl_limb remainder = 0;
for (size_t i = x->sz; i > 0; i--) {
remainder = (remainder << DIG_SIZE) | x->p[i-1];
remainder %= m;
}
mpz_set_int(ctx, r, (mp_limb)remainder);
r->sn = x->sn;
if (remainder == 0)
r->sn = 0;
}
/* Forward declarations for Barrett reduction functions */
static void mpz_barrett_mu(mpz_ctx_t *ctx, mpz_t *mu, mpz_t *m);
static void mpz_barrett_reduce(mpz_ctx_t *ctx, mpz_t *r, mpz_t *x, mpz_t *m, mpz_t *mu);
static void
mpz_mod(mpz_ctx_t *ctx, mpz_t *r, mpz_t *x, mpz_t *y)
{
short sn = x->sn;
if (zero_p(x)) {
mpz_init(ctx, r);
zero(r);
return;
}
/* Fast path for single-limb modulus */
if (y->sz == 1) {
mpz_mod_limb(ctx, r, x, y->p[0]);
if (y->sn < 0) r->sn = -r->sn;
return;
}
/* Barrett reduction for moderate-sized moduli */
if (y->sz >= 2 && y->sz <= 16 && x->sz >= y->sz + 2) {
mpz_t mu;
mpz_init_temp(ctx, &mu, y->sz + 1);
mpz_barrett_mu(ctx, &mu, y);
mpz_init_heap(ctx, r, y->sz);
mpz_barrett_reduce(ctx, r, x, y, &mu);
r->sn = sn;
if (uzero_p(r))
r->sn = 0;
mpz_clear(ctx, &mu);
return;
}
/* General division fallback */
mpz_t q;
mpz_init_temp(ctx, &q, x->sz);
mpz_init_heap(ctx, r, y->sz);
udiv(ctx, &q, r, x, y);
r->sn = sn;
if (uzero_p(r))
r->sn = 0;
mpz_clear(ctx, &q);
}
static mrb_int
mpz_cmp(mpz_ctx_t *ctx, mpz_t *x, mpz_t *y)
{
if (x->sn < 0 && y->sn > 0)
return (-1);
if (x->sn > 0 && y->sn < 0)
return 1;
int abscmp=ucmp(x, y);
if (x->sn >=0 && y->sn >=0)
return abscmp;
return (-abscmp); // if (x->sn <=0 && y->sn <=0)
}
/* 2<=base<=36 - this overestimates the optimal value, which is OK */
static size_t
mpz_sizeinbase(mpz_t *x, mrb_int base)
{
size_t i, j;
size_t bits = digits(x) * DIG_SIZE;
mrb_assert(2 <= base && base <= 36);
if (zero_p(x) || x->sz == 0) return 0;
for (j=0,i=1; i<=(size_t)base; i*=2,j++)
;
return bits/(j-1)+1;
}
/* x = y * n (only called from mpz_init_set_str) */
/* assumes x and n are positive or zero */
/* assumes n is small (fits in mp_limb) */
static void
mpz_mul_int(mpz_ctx_t *ctx, mpz_t *x, mrb_int n)
{
if (n == 0 || zero_p(x)) {
zero(x);
return;
}
size_t x_sz = x->sz;
size_t new_sz = x_sz + 1; // Maximum possible size after multiplication
// Reallocate x if necessary
mpz_realloc(ctx, x, new_sz);
mp_dbl_limb cc = 0;
mp_limb n_limb = (mp_limb)n;
for (size_t i = 0; i < x_sz; i++) {
// Multiply each limb and add carry
cc += (mp_dbl_limb)x->p[i] * n_limb;
x->p[i] = LOW(cc);
cc = HIGH(cc);
}
if (cc) {
// If there is a remaining carry, store it
x->p[x_sz] = (mp_limb)cc;
}
else {
x->sz = x_sz;
}
x->sn = 1;
trim(x);
}
static int
mpz_init_set_str(mpz_ctx_t *ctx, mpz_t *x, const char *s, mrb_int len, mrb_int base)
{
int retval = 0;
short sn;
uint8_t k;
mpz_init(ctx, x);
if (*s == '-') {
sn = -1; s++;
}
else if (*s == '+') {
sn = 1; s++;
}
else
sn = 1;
for (mrb_int i=0; i<len; i++) {
if (s[i]=='_') continue;
if (s[i] >= '0' && s[i] <= '9')
k = (uint8_t)s[i] - (uint8_t)'0';
else if (s[i] >= 'A' && s[i] <= 'Z')
k = (uint8_t)s[i] - (uint8_t)'A'+10;
else if (s[i] >= 'a' && s[i] <= 'z')
k = (uint8_t)s[i] - (uint8_t)'a'+10;
else {
retval = (-1);
break;
}
if (k >= base) {
retval = (-1);
break;
}
mpz_mul_int(ctx, x, base);
mpz_add_int(ctx, x, k);
}
x->sn = x->sz == 0 ? 0 : sn;
return retval;
}
/* power of base no bigger than DIG_BASE */
/* power of 2 is handled differently */
static const mp_limb base_limit[34*2] = {
#ifdef MRB_NO_MPZ64BIT
59049, // 3^10
0, // 4^8 (skip)
15625, // 5^6
46656, // 6^6
16807, // 7^5
0, // 8^5 (skip)
59049, // 9^5
10000, // 10^4
14641, // 11^4
20736, // 12^4
28561, // 13^4
38416, // 14^4
50625, // 15^4
0, // 16^4 (skip)
4913, // 17^3
5832, // 18^3
6859, // 19^3
8000, // 20^3
9261, // 21^3
10648, // 22^3
12167, // 23^3
13824, // 24^3
15625, // 25^3
17576, // 26^3
19683, // 27^3
21952, // 28^3
24389, // 29^3
27000, // 30^3
29791, // 31^3
0, // 32^3 (skip)
35937, // 33^3
39304, // 34^3
42875, // 35^3
46656, // 36^3
#else
3486784401UL, // 3^20
0, // 4^16 (skip)
1220703125UL, // 5^13
2176782336UL, // 6^12
1977326743UL, // 7^11
0, // 8^10 (skip)
3486784401UL, // 9^10
1000000000UL, // 10^9
2357947691UL, // 11^9
429981696UL, // 12^8
815730721UL, // 13^8
1475789056UL, // 14^8
2562890625UL, // 15^8
0, // 16^8 (skip)
410338673UL, // 17^7
612220032UL, // 18^7
893871739UL, // 19^7
1280000000UL, // 20^7
1801088541UL, // 21^7
2494357888UL, // 22^7
3404825447UL, // 23^7
191102976UL, // 24^6
244140625UL, // 25^6
308915776UL, // 26^6
387420489UL, // 27^6
481890304UL, // 28^6
594823321UL, // 29^6
729000000UL, // 30^6
887503681UL, // 31^6
0, // 32^6 (skip)
1291467969UL, // 33^6
1544804416UL, // 34^6
1838265625UL, // 35^6
2176782336UL, // 36^6
#endif
};
static char*
mpz_get_str(mpz_ctx_t *ctx, char *s, mrb_int sz, mrb_int base, mpz_t *x)
{
mrb_state *mrb = MPZ_MRB(ctx);
mrb_assert(2 <= base && base <= 36);
if (zero_p(x)) {
*s='0';
*(s+1)='\0';
return s;
}
char *ps = s;
char *se = s+sz;
int xlen = (int)digits(x);
if ((base & (base - 1)) == 0) { // base is a power of 2
int shift = 0;
while ((1 << shift) < base) shift++;
mp_limb mask = (mp_limb)base - 1;
mp_dbl_limb value = 0;
int bits = 0;
/* Process all limbs */
for (int i = 0; i < xlen; i++) {
value |= (mp_dbl_limb)x->p[i] << bits;
bits += DIG_SIZE;
while (bits >= shift) {
mp_limb digit = value & mask;
value >>= shift;
bits -= shift;
if (digit < 10) *s++ = '0' + digit;
else *s++ = 'a' + digit - 10;
}
}
/* Handle any remaining bits */
while (bits > 0) {
mp_limb digit = value & mask;
value >>= shift;
bits -= shift;
if (digit < 10) *s++ = '0' + digit;
else *s++ = 'a' + digit - 10;
}
}
else {
/* Check for overflow in size calculation */
if ((size_t)xlen > SIZE_MAX / sizeof(mp_limb)) {
mrb_raise(mrb, E_RUNTIME_ERROR, "bigint size too large for string conversion");
}
mp_limb *t = (mp_limb*)mrb_malloc(mrb, xlen * sizeof(mp_limb));
mp_limb *tend = t + xlen;
memcpy(t, x->p, xlen * sizeof(mp_limb));
mp_limb b2 = base_limit[base-3];
for (;;) {
mp_limb *d = tend;
mp_dbl_limb a = 0;
while (--d >= t) {
mp_limb d0 = *d;
a = (a<<DIG_SIZE) | d0;
*d = (mp_limb)(a / b2);
a %= b2;
}
// convert to character
for (mp_limb b=b2; b>=base; b/=base) {
char a0 = (char)(a % base);
if (a0 < 10) a0 += '0';
else a0 += 'a' - 10;
if (s == se) break;
*s++ = a0;
a /= base;
}
// check if number is zero
for (d = t; d < tend; d++) {
if (*d != 0) break;
}
if (d == tend) break;
}
mrb_free(mrb, t);
}
while (ps<s && s[-1]=='0') s--;
if (x->sn < 0) {
*s++ = '-';
}
/* reverse string */
for (char *u = ps,*v=s-1; u < v; u++,v--) {
char temp = *u;
*u = *v;
*v = temp;
}
*s = '\0'; /* null termination */
return ps;
}
static int
mpz_get_int(mpz_t *y, mrb_int *v)
{
if (zero_p(y)) {
*v = 0;
return TRUE;
}
mp_dbl_limb i = 0;
mp_limb *d = y->p + y->sz;
while (d-- > y->p) {
if (HIGH(i) != 0) {
/* will overflow */
return FALSE;
}
i = (i << DIG_SIZE) | *d;
}
if (i > MRB_INT_MAX) {
/* overflow */
return FALSE;
}
if (y->sn < 0) {
*v = -(mrb_int)i;
}
else {
*v = (mrb_int)i;
}
return TRUE;
}
static void
mpz_mul_2exp(mpz_ctx_t *ctx, mpz_t *z, mpz_t *x, mrb_int e)
{
if (e==0)
mpz_set(ctx, z, x);
else {
short sn = x->sn;
size_t digs = e / DIG_SIZE;
size_t bs = e % DIG_SIZE;
mpz_t y;
mpz_init_heap(ctx, &y, x->sz+digs);
for (size_t i=0;i<x->sz;i++)
y.p[i+digs] = x->p[i];
if (bs) {
ulshift(ctx, z, &y, bs);
mpz_clear(ctx, &y);
}
else {
mpz_move(ctx, z, &y);
}
z->sn = sn;
}
}
static void
mpz_div_2exp(mpz_ctx_t *ctx, mpz_t *z, mpz_t *x, mrb_int e)
{
short sn = x->sn;
if (e == 0) {
mpz_init_heap(ctx, z, x->sz);
mpz_set(ctx, z, x);
}
else {
size_t digs = e / DIG_SIZE;
size_t bs = e % DIG_SIZE;
mpz_t y;
size_t new_size = (digs >= x->sz) ? 1 : x->sz - digs;
mpz_init_temp(ctx, &y, new_size);
mpz_realloc(ctx, &y, new_size);
for (size_t i = 0; i < x->sz - digs; i++)
y.p[i] = x->p[i + digs];
if (bs) {
mpz_init_heap(ctx, z, new_size);
urshift(ctx, z, &y, bs);
mpz_clear(ctx, &y);
}
else {
mpz_move(ctx, z, &y);
}
if (uzero_p(z))
z->sn = 0;
else {
z->sn = sn;
}
}
}
static void
mpz_neg(mpz_ctx_t *ctx, mpz_t *x, mpz_t *y)
{
mpz_init_heap(ctx, x, y->sz);
mpz_set(ctx, x, y);
x->sn = -(y->sn);
}
/* Fast modular reduction by power of 2: z = x mod 2^e */
static void
mpz_mod_2exp(mpz_ctx_t *ctx, mpz_t *z, mpz_t *x, mrb_int e)
{
if (e <= 0) {
mpz_init(ctx, z);
zero(z);
return;
}
size_t eint = e / DIG_SIZE;
size_t bs = e % DIG_SIZE;
size_t sz = x->sz;
if (eint >= sz) {
/* x < 2^e, so x mod 2^e = x */
mpz_init_heap(ctx, z, x->sz);
mpz_set(ctx, z, x);
return;
}
/* Need to mask off high bits */
size_t result_sz = eint + (bs > 0 ? 1 : 0);
mpz_init_heap(ctx, z, result_sz);
mpz_realloc(ctx, z, result_sz);
z->sn = x->sn;
z->sz = result_sz;
/* Copy full limbs */
for (size_t i = 0; i < eint; i++) {
z->p[i] = x->p[i];
}
/* Mask partial limb if needed */
if (bs > 0) {
mp_limb mask = (1UL << bs) - 1;
z->p[eint] = x->p[eint] & mask;
}
trim(z);
}
#define make_2comp(v,c) do { v=~(v)+(c); c=((v)==0 && (c));} while (0)
static void
mpz_and(mpz_ctx_t *ctx, mpz_t *z, mpz_t *x, mpz_t *y)
{
if (zero_p(x) || zero_p(y)) {
mpz_init(ctx, z);
zero(z);
return;
}
mrb_assert(x->sz > 0 || y->sz > 0);
size_t max_sz = (x->sz > y->sz) ? x->sz : y->sz;
mpz_init_heap(ctx, z, max_sz);
mpz_realloc(ctx, z, max_sz);
z->sn = (x->sn == y->sn) ? x->sn : 1;
char c1 = 1, c2 = 1, c3 = 1;
for (size_t i = 0; i < max_sz; i++) {
mp_limb xv = (i < x->sz) ? x->p[i] : 0;
mp_limb yv = (i < y->sz) ? y->p[i] : 0;
if (x->sn < 0) make_2comp(xv, c1);
if (y->sn < 0) make_2comp(yv, c2);
mp_limb zv = xv & yv;
if (z->sn < 0) make_2comp(zv, c3);
z->p[i] = zv;
}
}
static void
mpz_or(mpz_ctx_t *ctx, mpz_t *z, mpz_t *x, mpz_t *y) /* not the most efficient way to do this */
{
if (zero_p(x)) {
mpz_init_heap(ctx, z, y->sz);
mpz_set(ctx, z, y);
return;
}
if (zero_p(y)) {
mpz_init_heap(ctx, z, x->sz);
mpz_set(ctx, z, x);
return;
}
mrb_assert(x->sz > 0 || y->sz > 0);
size_t max_sz = (x->sz > y->sz) ? x->sz : y->sz;
mpz_init_heap(ctx, z, max_sz);
mpz_realloc(ctx, z, max_sz);
z->sn = (x->sn == y->sn) ? x->sn : -1;
char c1 = 1, c2 = 1, c3 = 1;
for (size_t i = 0; i < max_sz; i++) {
mp_limb xv = (i < x->sz) ? x->p[i] : 0;
mp_limb yv = (i < y->sz) ? y->p[i] : 0;
if (x->sn < 0) make_2comp(xv, c1);
if (y->sn < 0) make_2comp(yv, c2);
mp_limb zv = xv | yv;
if (z->sn < 0) make_2comp(zv, c3);
z->p[i] = zv;
}
}
static void
mpz_xor(mpz_ctx_t *ctx, mpz_t *z, mpz_t *x, mpz_t *y) /* not the most efficient way to do this */
{
if (zero_p(x)) {
mpz_init_heap(ctx, z, y->sz);
mpz_set(ctx, z, y);
return;
}
if (zero_p(y)) {
mpz_init_heap(ctx, z, x->sz);
mpz_set(ctx, z, x);
return;
}
mrb_assert(x->sz > 0 || y->sz > 0);
size_t max_sz = (x->sz > y->sz) ? x->sz : y->sz;
mpz_init_heap(ctx, z, max_sz);
mpz_realloc(ctx, z, max_sz);
z->sn = (x->sn == y->sn) ? 1 : -1;
char c1 = 1, c2 = 1, c3 = 1;
for (size_t i = 0; i < max_sz; i++) {
mp_limb xv = (i < x->sz) ? x->p[i] : 0;
mp_limb yv = (i < y->sz) ? y->p[i] : 0;
if (x->sn < 0) make_2comp(xv, c1);
if (y->sn < 0) make_2comp(yv, c2);
mp_limb zv = xv ^ yv;
if (z->sn < 0) make_2comp(zv, c3);
z->p[i] = zv;
}
}
static void
mpz_pow(mpz_ctx_t *ctx, mpz_t *zz, mpz_t *x, mrb_int e)
{
if (e == 0) {
mpz_init_set_int(ctx, zz, 1L);
return;
}
mrb_uint mask = 1ULL << (sizeof(mrb_int) * 8 - 1);
while (mask != 0 && !(mask & e)) {
mask >>= 1;
}
/* Set initial value to x for exponentiation */
mpz_init_set(ctx, zz, x);
if (mask == 0) { /* e is 0 or 1 */
if (e == 0) mpz_set_int(ctx, zz, 1L);
return;
}
mask >>= 1;
/* Pre-allocate a single temporary variable */
mpz_t temp;
mpz_init(ctx, &temp);
for (; mask != 0; mask >>= 1) {
/* squaring: temp = zz * zz */
mpz_mul(ctx, &temp, zz, zz);
if (e & mask) {
/* multiplication: zz = temp * x */
mpz_mul(ctx, zz, &temp, x);
}
else {
/* move result: zz = temp */
mpz_move(ctx, zz, &temp);
}
}
mpz_clear(ctx, &temp);
}
static void
mpz_powm(mpz_ctx_t *ctx, mpz_t *zz, mpz_t *x, mpz_t *ex, mpz_t *n)
{
/* Handle special cases */
if (zero_p(ex) || uzero_p(ex)) {
mpz_set_int(ctx, zz, 1);
return;
}
if (ex->sn < 0) {
return;
}
size_t pool_state = pool_save(ctx);
mpz_t t, b;
mpz_init_set_int(ctx, &t, 1);
mpz_init_set(ctx, &b, x);
/* Optimize with Barrett reduction for moderate-sized moduli */
mpz_t mu, temp;
int use_barrett = (n->sz >= 2 && n->sz <= 8);
mpz_init_temp(ctx, &temp, n->sz * 2); /* For intermediate calculations */
if (use_barrett) {
mpz_init_temp(ctx, &mu, n->sz + 1); /* Barrett parameter */
mpz_barrett_mu(ctx, &mu, n);
}
size_t len = digits(ex);
for (size_t i = 0; i < len; i++) {
mp_limb e = ex->p[i];
for (size_t j = 0; j < sizeof(mp_limb) * 8; j++) {
if ((e & 1) == 1) {
mpz_mul(ctx, &temp, &t, &b);
if (use_barrett) {
mpz_barrett_reduce(ctx, &t, &temp, n, &mu);
}
else {
mpz_mod(ctx, &t, &temp, n);
}
}
e >>= 1;
mpz_mul(ctx, &temp, &b, &b);
if (use_barrett) {
mpz_barrett_reduce(ctx, &b, &temp, n, &mu);
}
else {
mpz_mod(ctx, &b, &temp, n);
}
}
}
mpz_move(ctx, zz, &t);
mpz_clear(ctx, &t);
mpz_clear(ctx, &b);
mpz_clear(ctx, &temp);
if (use_barrett) {
mpz_clear(ctx, &mu);
}
pool_restore(ctx, pool_state);
}
static void
mpz_powm_i(mpz_ctx_t *ctx, mpz_t *zz, mpz_t *x, mrb_int ex, mpz_t *n)
{
if (ex == 0) {
mpz_set_int(ctx, zz, 1);
return;
}
if (ex < 0) {
return;
}
size_t pool_state = pool_save(ctx);
mpz_t t, b;
mpz_init_set_int(ctx, &t, 1);
mpz_init_set(ctx, &b, x);
/* Optimize with Barrett reduction for moderate-sized moduli */
mpz_t mu, temp;
int use_barrett = (n->sz >= 2 && n->sz <= 8);
mpz_init_temp(ctx, &temp, n->sz * 2); /* For intermediate calculations */
if (use_barrett) {
mpz_init_temp(ctx, &mu, n->sz + 1); /* Barrett parameter */
mpz_barrett_mu(ctx, &mu, n);
}
while (ex > 0) {
if ((ex & 1) == 1) {
mpz_mul(ctx, &temp, &t, &b);
if (use_barrett) {
mpz_barrett_reduce(ctx, &t, &temp, n, &mu);
}
else {
mpz_mod(ctx, &t, &temp, n);
}
}
ex >>= 1;
if (ex > 0) { /* Skip final squaring when ex becomes 0 */
mpz_mul(ctx, &temp, &b, &b);
if (use_barrett) {
mpz_barrett_reduce(ctx, &b, &temp, n, &mu);
}
else {
mpz_mod(ctx, &b, &temp, n);
}
}
}
mpz_move(ctx, zz, &t);
mpz_clear(ctx, &t);
mpz_clear(ctx, &b);
mpz_clear(ctx, &temp);
if (use_barrett) {
mpz_clear(ctx, &mu);
}
pool_restore(ctx, pool_state);
}
/* Helper functions for pool-based GCD operations */
static int
mpz_abs_copy(mpz_ctx_t *ctx, mpz_t *result, mpz_t *operand) {
if (!operand || operand->sz == 0) {
result->sz = 0;
result->sn = 0;
return 1;
}
/* Copy limbs */
for (size_t i = 0; i < operand->sz && i < result->sz; i++) {
result->p[i] = operand->p[i];
}
result->sz = (operand->sz < result->sz) ? operand->sz : result->sz;
result->sn = (operand->sn < 0) ? -operand->sn : operand->sn; /* Always positive */
return 1;
}
static void
mpz_abs(mpz_ctx_t *ctx, mpz_t *x, mpz_t *y)
{
mpz_init_heap(ctx, x, y->sz);
mpz_realloc(ctx, x, y->sz);
mpz_abs_copy(ctx, x, y);
}
/* Fast GCD for single limbs using binary algorithm */
static mp_limb
limb_gcd(mp_limb a, mp_limb b)
{
if (a == 0) return b;
if (b == 0) return a;
/* Find power of 2 dividing both a and b */
int shift = 0;
while (((a | b) & 1) == 0) {
a >>= 1;
b >>= 1;
shift++;
}
/* Make a odd */
while ((a & 1) == 0) {
a >>= 1;
}
/* From here on, a is always odd */
do {
/* Make b odd */
while ((b & 1) == 0) {
b >>= 1;
}
/* Now both a and b are odd. Ensure a >= b */
if (a < b) {
mp_limb temp = a;
a = b;
b = temp;
}
/* Replace b with (b - a) */
b = b - a;
} while (b != 0);
/* Restore common factors of 2 */
return a << shift;
}
/* Count trailing zero bits in a multi-precision integer */
static size_t
mpz_trailing_zeros(mpz_t *x)
{
if (zero_p(x) || x->sz == 0) return 0;
size_t zeros = 0;
/* Count complete zero limbs */
size_t i = 0;
while (i < x->sz && x->p[i] == 0) {
zeros += DIG_SIZE;
i++;
}
/* Count trailing zeros in first non-zero limb */
if (i < x->sz) {
mp_limb limb = x->p[i];
#if (defined(__GNUC__) || __has_builtin(__builtin_ctzll))
if (sizeof(mp_limb) == sizeof(unsigned long long)) {
zeros += __builtin_ctzll(limb);
}
else if (sizeof(mp_limb) == sizeof(unsigned long)) {
zeros += __builtin_ctzl(limb);
}
else {
zeros += __builtin_ctz(limb);
}
#else
/* Fallback bit counting */
while ((limb & 1) == 0) {
limb >>= 1;
zeros++;
}
#endif
}
return zeros;
}
/* Check if a number is a power of 2 */
static int
mpz_power_of_2_p(mpz_t *x)
{
if (zero_p(x) || x->sz == 0) return 0;
/* Count non-zero limbs */
size_t non_zero_limbs = 0;
size_t non_zero_index = 0;
for (size_t i = 0; i < x->sz; i++) {
if (x->p[i] != 0) {
non_zero_limbs++;
non_zero_index = i;
if (non_zero_limbs > 1) return 0; /* More than one non-zero limb */
}
}
if (non_zero_limbs == 0) return 0; /* All zero */
if (non_zero_limbs > 1) return 0; /* Multiple non-zero limbs */
/* Check if the single non-zero limb is a power of 2 */
mp_limb limb = x->p[non_zero_index];
return (limb != 0) && ((limb & (limb - 1)) == 0);
}
/* Binary GCD algorithm (Stein's algorithm) - faster than Euclidean GCD */
static void
mpz_gcd(mpz_ctx_t *ctx, mpz_t *gg, mpz_t *aa, mpz_t *bb)
{
size_t pool_state = pool_save(ctx);
mpz_t a, b;
size_t shift;
size_t a_zeros;
size_t b_zeros;
/* Handle special cases */
if (zero_p(aa)) {
mpz_abs(ctx, gg, bb);
goto cleanup;
}
if (zero_p(bb)) {
mpz_abs(ctx, gg, aa);
goto cleanup;
}
/* Fast path for single-limb numbers */
if (aa->sz <= 1 && bb->sz <= 1) {
mp_limb a_limb = (aa->sz == 0) ? 0 : aa->p[0];
mp_limb b_limb = (bb->sz == 0) ? 0 : bb->p[0];
mp_limb result = limb_gcd(a_limb, b_limb);
mpz_init(ctx, gg);
if (result == 0) {
gg->sn = 0;
gg->sz = 0;
}
else {
mpz_realloc(ctx, gg, 1);
gg->p[0] = result;
gg->sn = 1;
}
goto cleanup;
}
/* Fast path for powers of 2 */
if (mpz_power_of_2_p(aa)) {
a_zeros = mpz_trailing_zeros(aa);
b_zeros = mpz_trailing_zeros(bb);
size_t min_zeros = (a_zeros < b_zeros) ? a_zeros : b_zeros;
mpz_init_set_int(ctx, gg, 1);
mpz_mul_2exp(ctx, gg, gg, min_zeros);
goto cleanup;
}
if (mpz_power_of_2_p(bb)) {
a_zeros = mpz_trailing_zeros(aa);
b_zeros = mpz_trailing_zeros(bb);
size_t min_zeros = (a_zeros < b_zeros) ? a_zeros : b_zeros;
mpz_init_set_int(ctx, gg, 1);
mpz_mul_2exp(ctx, gg, gg, min_zeros);
goto cleanup;
}
mpz_init_set(ctx, &a, aa);
mpz_init_set(ctx, &b, bb);
shift = 0;
a_zeros = mpz_trailing_zeros(&a);
b_zeros = mpz_trailing_zeros(&b);
shift = (a_zeros < b_zeros) ? a_zeros : b_zeros;
mpz_div_2exp(ctx, &a, &a, a_zeros);
mpz_div_2exp(ctx, &b, &b, b_zeros);
/* Euclidean algorithm for multi-limb numbers */
while (!zero_p(&b)) {
mpz_t temp;
mpz_init_temp(ctx, &temp, a.sz);
mpz_mod(ctx, &temp, &a, &b);
mpz_move(ctx, &a, &b);
mpz_move(ctx, &b, &temp);
mpz_clear(ctx, &temp);
}
mpz_mul_2exp(ctx, gg, &a, shift);
mpz_clear(ctx, &a);
mpz_clear(ctx, &b);
cleanup:
pool_restore(ctx, pool_state);
}
static size_t
mpz_bits(const mpz_t *x)
{
if (x->sz == 0 || x->sn == 0) return 0;
size_t limb_bits = sizeof(mp_limb) * 8;
// Get the most significant limb
size_t i = x->sz - 1;
mp_limb high = x->p[i];
// Number of bits = total full limbs + significant bits in top limb
return i * limb_bits + (limb_bits - lzb(high));
}
/* Compute Barrett parameter μ = floor(2^(2k) / m) where k ≈ log₂(m) */
static void
mpz_barrett_mu(mpz_ctx_t *ctx, mpz_t *mu, mpz_t *m)
{
size_t k = mpz_bits(m);
mpz_t temp;
mpz_init_set_int(ctx, &temp, 1);
mpz_mul_2exp(ctx, &temp, &temp, 2 * k); /* temp = 2^(2k) */
mpz_mdiv(ctx, mu, &temp, m); /* mu = floor(2^(2k) / m) */
mpz_clear(ctx, &temp);
}
/* Barrett reduction: r = x mod m using precomputed μ */
static void
mpz_barrett_reduce(mpz_ctx_t *ctx, mpz_t *r, mpz_t *x, mpz_t *m, mpz_t *mu)
{
size_t k = mpz_bits(m);
/* If x < m, then x mod m = x */
if (mpz_cmp(ctx, x, m) < 0) {
mpz_set(ctx, r, x);
return;
}
mpz_t q1, q2, q3, r1, r2;
/* Conservative size estimates for Barrett reduction temporaries */
size_t q_size = x->sz + mu->sz + 1; /* For multiplication results */
size_t r_size = m->sz + 1; /* For modular reduction results */
mpz_init_temp(ctx, &q1, x->sz + 1);
mpz_init_temp(ctx, &q2, q_size);
mpz_init_temp(ctx, &q3, q_size);
mpz_init_temp(ctx, &r1, r_size);
mpz_init_temp(ctx, &r2, r_size);
/* Step 1: q1 = floor(x / 2^(k-1)) */
if (k > 1) {
mpz_div_2exp(ctx, &q1, x, k - 1);
}
else {
mpz_set(ctx, &q1, x);
}
/* Step 2: q2 = q1 * μ */
mpz_mul(ctx, &q2, &q1, mu);
/* Step 3: q3 = floor(q2 / 2^(k+1)) */
mpz_div_2exp(ctx, &q3, &q2, k + 1);
/* Step 4: r1 = x mod 2^(k+1) */
mpz_mod_2exp(ctx, &r1, x, k + 1);
/* Step 5: r2 = (q3 * m) mod 2^(k+1) */
mpz_mul(ctx, &r2, &q3, m);
mpz_mod_2exp(ctx, &r2, &r2, k + 1);
/* Step 6: r = r1 - r2 */
if (mpz_cmp(ctx, &r1, &r2) >= 0) {
mpz_sub(ctx, r, &r1, &r2);
}
else {
/* r1 < r2, so add 2^(k+1) to r1 */
mpz_t power;
mpz_init_set_int(ctx, &power, 1);
mpz_mul_2exp(ctx, &power, &power, k + 1);
mpz_add(ctx, &r1, &r1, &power);
mpz_sub(ctx, r, &r1, &r2);
mpz_clear(ctx, &power);
}
/* Step 7: Final correction - ensure 0 ≤ r < m */
while (mpz_cmp(ctx, r, m) >= 0) {
mpz_sub(ctx, r, r, m);
}
mpz_clear(ctx, &q1);
mpz_clear(ctx, &q2);
mpz_clear(ctx, &q3);
mpz_clear(ctx, &r1);
mpz_clear(ctx, &r2);
}
static void
mpz_sqrt(mpz_ctx_t *ctx, mpz_t *z, mpz_t *x)
{
mrb_assert(x->sn >= 0);
if (x->sz == 0) {
// sqrt(0) = 0
mpz_init(ctx, z);
z->sn = 0;
z->sz = 0;
return;
}
// Use heap-only implementation for now
size_t xbits = mpz_bits(x);
size_t sbit = (xbits + 1) / 2;
mpz_t s, t;
mpz_init_set_int(ctx, &s, 1);
mpz_mul_2exp(ctx, &s, &s, sbit);
mpz_init_temp(ctx, &t, x->sz + 1);
// Iteratively refine s using Newton-Raphson method:
// s = (s + x / s) / 2
for (;;) {
mpz_mdiv(ctx, &t, x, &s); // t = x / s
mpz_add(ctx, &t, &t, &s); // t = s + x/s
mpz_div_2exp(ctx, &t, &t, 1); // t = (s + x/s) / 2
if (mpz_cmp(ctx, &t, &s) >= 0) {
// Converged: t >= s
break;
}
mpz_set(ctx, &s, &t);
}
mpz_move(ctx, z, &s);
mpz_clear(ctx, &t);
}
/* Barrett reduction for efficient modular arithmetic with repeated operations */
/* --- mruby functions --- */
/* initialize mpz_t from RBigint (not need to clear) */
static void
bint_as_mpz(struct RBigint *b, mpz_t *x)
{
x->p = RBIGINT_ARY(b);
x->sz = RBIGINT_SIZE(b);
x->sn = RBIGINT_SIGN(b);
}
static struct RBigint*
bint_new(mpz_ctx_t *ctx, mpz_t *x)
{
struct RBigint *b = MRB_OBJ_ALLOC(MPZ_MRB(ctx), MRB_TT_BIGINT, MPZ_MRB(ctx)->integer_class);
if (x->sz <= RBIGINT_EMBED_SIZE_MAX) {
RBIGINT_SET_EMBED_SIZE(b, x->sz);
RBIGINT_SET_EMBED_SIGN(b, x->sn);
if (x->p) memcpy(RBIGINT_EMBED_ARY(b), x->p, x->sz*sizeof(mp_limb));
mpz_clear(ctx, x);
}
else {
RBIGINT_SET_HEAP(b);
mpz_move(ctx, &b->as.heap, x);
}
return b;
}
static struct RBigint*
bint_new_int(mpz_ctx_t *ctx, mrb_int n)
{
mpz_t x;
mpz_init_set_int(ctx, &x, n);
return bint_new(ctx, &x);
}
mrb_value
mrb_bint_new_int(mrb_state *mrb, mrb_int x)
{
MPZ_CTX_INIT(mrb, ctx, pool);
struct RBigint *b = bint_new_int(ctx, x);
return mrb_obj_value(b);
}
#ifdef MRB_INT32
mrb_value
mrb_bint_new_int64(mrb_state *mrb, int64_t n)
{
mpz_t x;
MPZ_CTX_INIT(mrb, ctx, pool);
mpz_set_int64(ctx, &x, n);
struct RBigint *b = bint_new(ctx, &x);
return mrb_obj_value(b);
}
#endif
mrb_value
mrb_bint_new_uint64(mrb_state *mrb, uint64_t x)
{
mpz_t z;
MPZ_CTX_INIT(mrb, ctx, pool);
mpz_init(ctx, &z);
mpz_set_uint64(ctx, &z, x);
struct RBigint *b = bint_new(ctx, &z);
return mrb_obj_value(b);
}
mrb_value
mrb_bint_new_str(mrb_state *mrb, const char *x, mrb_int len, mrb_int base)
{
mpz_t z;
int sn = 1;
if (base < 0) {
base = -base;
sn = -1;
}
mrb_assert(2 <= base && base <= 36);
MPZ_CTX_INIT(mrb, ctx, pool);
mpz_init_set_str(ctx, &z, x, len, base);
if (sn < 0) {
z.sn = sn;
}
struct RBigint *b = bint_new(ctx, &z);
return mrb_obj_value(b);
}
static mrb_value
bint_norm(mrb_state *mrb, struct RBigint *b)
{
mrb_int i;
mpz_t a;
bint_as_mpz(b, &a);
if (mpz_get_int(&a, &i)) {
return mrb_int_value(mrb, i);
}
return mrb_obj_value(b);
}
void
mrb_gc_free_bint(mrb_state *mrb, struct RBasic *x)
{
struct RBigint *b = (struct RBigint*)x;
MPZ_CTX_INIT(mrb, ctx, pool);
if (!RBIGINT_EMBED_P(b)) {
mpz_clear(ctx, &b->as.heap);
}
}
#ifndef MRB_NO_FLOAT
mrb_value
mrb_bint_new_float(mrb_state *mrb, mrb_float x)
{
/* x should not be NaN nor Infinity */
mrb_assert(x == x && x != x * 0.5);
if (FIXABLE_FLOAT(x)) {
return mrb_int_value(mrb, (mrb_int)x);
}
int sn;
if (x < 0.0) {
x = -x;
sn = -1;
}
else {
sn = 1;
}
if (x < 1.0) {
return mrb_fixnum_value(0);
}
MPZ_CTX_INIT(mrb, ctx, pool);
mpz_t r;
mpz_init(ctx, &r);
r.sn = sn;
mrb_float b = (double)DIG_BASE;
mrb_float bi = 1.0 / b;
size_t rn;
for (rn = 1; x >= b; rn++)
x *= bi;
mpz_realloc(ctx, &r, rn);
mp_limb *rp = r.p;
for (size_t i=rn-1;;i--) {
mp_limb f = LOW((mp_limb)x);
x -= f;
mrb_assert(x < 1.0);
rp[i] = f;
if (i == 0) break;
}
return bint_norm(mrb, bint_new(ctx, &r));
}
mrb_float
mrb_bint_as_float(mrb_state *mrb, mrb_value self)
{
mpz_t m;
bint_as_mpz(RBIGINT(self), &m);
mp_limb *d = m.p + m.sz;
mrb_float val = 0;
while (d-- > m.p) {
val = val * DIG_BASE + *d;
}
if (m.sn < 0) {
val = -val;
}
return val;
}
#endif
mrb_value
mrb_as_bint(mrb_state *mrb, mrb_value x)
{
if (mrb_bigint_p(x)) return x;
return mrb_bint_new_int(mrb, mrb_as_int(mrb, x));
}
mrb_int
mrb_bint_as_int(mrb_state *mrb, mrb_value x)
{
mpz_t m;
mrb_int i;
bint_as_mpz(RBIGINT(x), &m);
if (!mpz_get_int(&m, &i)) {
mrb_raise(mrb, E_RANGE_ERROR, "integer out of range");
}
return i;
}
#ifdef MRB_INT32
int64_t
mrb_bint_as_int64(mrb_state *mrb, mrb_value x)
{
mpz_t m;
bint_as_mpz(RBIGINT(x), &m);
uint64_t u = 0;
size_t len = digits(&m);
if (len*sizeof(mp_limb) > sizeof(uint64_t)) {
out_of_range:
mrb_raise(mrb, E_RANGE_ERROR, "integer out of range");
}
for (size_t i=len-1; ; i--) {
u <<= DIG_SIZE;
u |= m.p[i];
if (i==0) break;
}
if (u > INT64_MAX) goto out_of_range;
if (m.sn < 0) return -(int64_t)u;
return (int64_t)u;
}
#endif
uint64_t
mrb_bint_as_uint64(mrb_state *mrb, mrb_value x)
{
mpz_t m;
bint_as_mpz(RBIGINT(x), &m);
uint64_t u = 0;
size_t len = digits(&m);
if (m.sn < 0 || len*sizeof(mp_limb) > sizeof(uint64_t)) {
mrb_raise(mrb, E_RANGE_ERROR, "integer out of range");
}
for (size_t i=len-1; ; i--) {
u <<= DIG_SIZE;
u |= m.p[i];
if (i==0) break;
}
return u;
}
static mrb_bool
int_fit_limb_p(mrb_int i)
{
#if DIG_SIZE == 32
# ifdef MRB_INT64
// if mp_limb is int32_t
return (i > INT32_MIN && i <= INT32_MAX);
# else
// if mp_limb is also int32_t, it always fits
return TRUE;
# endif
#else /* if DIG_SIZE == 16 */
// if mp_limb is int16_t
return (i > INT16_MIN && i <= INT16_MAX);
#endif
}
/* unnormalize version of mrb_bint_add */
mrb_value
mrb_bint_add_n(mrb_state *mrb, mrb_value x, mrb_value y)
{
mpz_t a, b, z;
bint_as_mpz(RBIGINT(x), &a);
MPZ_CTX_INIT(mrb, ctx, pool);
if (mrb_integer_p(y)) {
mrb_int n = mrb_integer(y);
if (int_fit_limb_p(n)) {
mpz_init_set(ctx, &z, &a);
if ((n > 0) ^ (z.sn > 0)) {
mpz_sub_int(ctx, &z, n<0 ? -n : n);
}
else {
mpz_add_int(ctx, &z, n<0 ? -n : n);
}
struct RBigint *v = bint_new(ctx, &z);
return mrb_obj_value(v);
}
}
y = mrb_as_bint(mrb, y);
bint_as_mpz(RBIGINT(y), &b);
mpz_init(ctx, &z);
mpz_add(ctx, &z, &a, &b);
struct RBigint *v = bint_new(ctx, &z);
return mrb_obj_value(v);
}
mrb_value
mrb_bint_add(mrb_state *mrb, mrb_value x, mrb_value y)
{
#ifndef MRB_NO_FLOAT
if (mrb_float_p(y)) {
mrb_float v1 = mrb_bint_as_float(mrb, x);
mrb_float v2 = mrb_float(y);
return mrb_float_value(mrb,v1+v2);
}
#endif
x = mrb_bint_add_n(mrb, x, y);
return bint_norm(mrb, RBIGINT(x));
}
/* unnormalize version of mrb_bint_sub */
mrb_value
mrb_bint_sub_n(mrb_state *mrb, mrb_value x, mrb_value y)
{
mpz_t a, b, z;
MPZ_CTX_INIT(mrb, ctx, pool);
bint_as_mpz(RBIGINT(x), &a);
if (mrb_integer_p(y)) {
mrb_int n = mrb_integer(y);
if (int_fit_limb_p(n)) {
mpz_init_set(ctx, &z, &a);
if ((n > 0) ^ (z.sn > 0)) {
mpz_add_int(ctx, &z, n<0 ? -n : n);
}
else {
mpz_sub_int(ctx, &z, n<0 ? -n : n);
}
struct RBigint *v = bint_new(ctx, &z);
return mrb_obj_value(v);
}
}
y = mrb_as_bint(mrb, y);
bint_as_mpz(RBIGINT(y), &b);
mpz_init(ctx, &z);
mpz_sub(ctx, &z, &a, &b);
struct RBigint *v = bint_new(ctx, &z);
return mrb_obj_value(v);
}
mrb_value
mrb_bint_sub(mrb_state *mrb, mrb_value x, mrb_value y)
{
#ifndef MRB_NO_FLOAT
if (mrb_float_p(y)) {
mrb_float v1 = mrb_bint_as_float(mrb, x);
mrb_float v2 = mrb_float(y);
return mrb_float_value(mrb,v1-v2);
}
#endif
x = mrb_bint_sub_n(mrb, x, y);
return bint_norm(mrb, RBIGINT(x));
}
static struct RBigint*
bint_mul(mrb_state *mrb, mrb_value x, mrb_value y)
{
mpz_t a, b, z;
y = mrb_as_bint(mrb, y);
bint_as_mpz(RBIGINT(x), &a);
bint_as_mpz(RBIGINT(y), &b);
MPZ_CTX_INIT(mrb, ctx, pool);
mpz_init(ctx, &z);
mpz_mul(ctx, &z, &a, &b);
return bint_new(ctx, &z);
}
mrb_value
mrb_bint_mul(mrb_state *mrb, mrb_value x, mrb_value y)
{
if (mrb_integer_p(y)) {
if (mrb_integer(y) == 0) return mrb_fixnum_value(0);
if (mrb_integer(y) == 1) return bint_norm(mrb, RBIGINT(x));
}
#ifndef MRB_NO_FLOAT
if (mrb_float_p(y)) {
mrb_float v1 = mrb_bint_as_float(mrb, x);
mrb_float v2 = mrb_float(y);
return mrb_float_value(mrb,v1*v2);
}
#endif
return bint_norm(mrb, bint_mul(mrb, x, y));
}
mrb_value
mrb_bint_mul_n(mrb_state *mrb, mrb_value x, mrb_value y)
{
struct RBigint *b = bint_mul(mrb, x, y);
return mrb_obj_value(b);
}
mrb_value
mrb_bint_div(mrb_state *mrb, mrb_value x, mrb_value y)
{
if (mrb_integer_p(y)) {
if (mrb_integer(y) == 0) mrb_int_zerodiv(mrb);
if (mrb_integer(y) == 1) return bint_norm(mrb, RBIGINT(x));
}
#ifndef MRB_NO_FLOAT
if (mrb_float_p(y)) {
mrb_float v1 = mrb_bint_as_float(mrb, x);
mrb_float v2 = mrb_float(y);
return mrb_float_value(mrb,v1*v2);
}
#endif
mpz_t a, b, z;
y = mrb_as_bint(mrb, y);
bint_as_mpz(RBIGINT(y), &b);
if (zero_p(&b) || uzero_p(&b)) {
mrb_int_zerodiv(mrb);
}
bint_as_mpz(RBIGINT(x), &a);
MPZ_CTX_INIT(mrb, ctx, pool);
mpz_init(ctx, &z);
mpz_mdiv(ctx, &z, &a, &b);
return bint_norm(mrb, bint_new(ctx, &z));
}
mrb_value
mrb_bint_add_ii(mrb_state *mrb, mrb_int x, mrb_int y)
{
mpz_t a, b, z;
MPZ_CTX_INIT(mrb, ctx, pool);
mpz_init(ctx, &z);
mpz_init_set_int(ctx, &a, x);
mpz_init_set_int(ctx, &b, y);
mpz_add(ctx, &z, &a, &b);
mpz_clear(ctx, &a);
mpz_clear(ctx, &b);
return bint_norm(mrb, bint_new(ctx, &z));
}
mrb_value
mrb_bint_sub_ii(mrb_state *mrb, mrb_int x, mrb_int y)
{
mpz_t a, b, z;
MPZ_CTX_INIT(mrb, ctx, pool);
mpz_init(ctx, &z);
mpz_init_set_int(ctx, &a, x);
mpz_init_set_int(ctx, &b, y);
mpz_sub(ctx, &z, &a, &b);
mpz_clear(ctx, &a);
mpz_clear(ctx, &b);
return bint_norm(mrb, bint_new(ctx, &z));
}
mrb_value
mrb_bint_mul_ii(mrb_state *mrb, mrb_int x, mrb_int y)
{
mpz_t a, b, z;
MPZ_CTX_INIT(mrb, ctx, pool);
mpz_init(ctx, &z);
mpz_init_set_int(ctx, &a, x);
mpz_init_set_int(ctx, &b, y);
mpz_mul(ctx, &z, &a, &b);
mpz_clear(ctx, &a);
mpz_clear(ctx, &b);
return bint_norm(mrb, bint_new(ctx, &z));
}
mrb_value
mrb_bint_mod(mrb_state *mrb, mrb_value x, mrb_value y)
{
#ifndef MRB_NO_FLOAT
if (mrb_float_p(y)) {
mrb_float v1 = mrb_bint_as_float(mrb, x);
mrb_float v2 = mrb_float(y);
return mrb_float_value(mrb, fmod(v1, v2));
}
#endif
if (mrb_integer_p(y) && mrb_integer(y) == 0) {
mrb_int_zerodiv(mrb);
}
mpz_t a, b, z;
y = mrb_as_bint(mrb, y);
bint_as_mpz(RBIGINT(y), &b);
if (zero_p(&b) || uzero_p(&b)) {
mrb_int_zerodiv(mrb);
}
bint_as_mpz(RBIGINT(x), &a);
MPZ_CTX_INIT(mrb, ctx, pool);
mpz_init(ctx, &z);
mpz_mmod(ctx, &z, &a, &b);
return bint_norm(mrb, bint_new(ctx, &z));
}
mrb_value
mrb_bint_rem(mrb_state *mrb, mrb_value x, mrb_value y)
{
/* called from mrbgems/mruby-numeric-ext/src/numeric_ext.c */
/* y should not be float */
if (mrb_integer_p(y) && mrb_integer(y) == 0) {
mrb_int_zerodiv(mrb);
}
mpz_t a, b, z;
y = mrb_as_bint(mrb, y);
bint_as_mpz(RBIGINT(y), &b);
if (zero_p(&b) || uzero_p(&b)) {
mrb_int_zerodiv(mrb);
}
bint_as_mpz(RBIGINT(x), &a);
MPZ_CTX_INIT(mrb, ctx, pool);
mpz_init(ctx, &z);
mpz_mod(ctx, &z, &a, &b);
return bint_norm(mrb, bint_new(ctx, &z));
}
mrb_value
mrb_bint_divmod(mrb_state *mrb, mrb_value x, mrb_value y)
{
/* called from src/numeric.c */
/* y should not be float */
if (mrb_integer_p(y) && mrb_integer(y) == 0) {
mrb_int_zerodiv(mrb);
}
y = mrb_as_bint(mrb, y);
mpz_t a, b, c, d;
bint_as_mpz(RBIGINT(y), &b);
if (zero_p(&b) || uzero_p(&b)) {
mrb_int_zerodiv(mrb);
}
bint_as_mpz(RBIGINT(x), &a);
MPZ_CTX_INIT(mrb, ctx, pool);
mpz_init(ctx, &c);
mpz_init(ctx, &d);
mpz_mdivmod(ctx, &c, &d, &a, &b);
return mrb_assoc_new(mrb, bint_norm(mrb, bint_new(ctx, &c)), bint_norm(mrb, bint_new(ctx, &d)));
}
mrb_int
mrb_bint_cmp(mrb_state *mrb, mrb_value x, mrb_value y)
{
#ifndef MRB_NO_FLOAT
if (mrb_float_p(y)) {
mrb_float v1 = mrb_bint_as_float(mrb, x);
mrb_float v2 = mrb_float(y);
if (v1 == v2) return 0;
if (v1 > v2) return 1;
return -1;
}
#endif
mpz_t a;
bint_as_mpz(RBIGINT(x), &a);
if (!mrb_bigint_p(y)) {
if (!mrb_integer_p(y)) return -2; /* type mismatch */
mrb_int i1, i2 = mrb_integer(y);
if (mpz_get_int(&a, &i1)) {
if (i1 == i2) return 0;
if (i1 > i2) return 1;
return -1;
}
if (a.sn > 0) return 1;
return -1;
}
mpz_t b;
bint_as_mpz(RBIGINT(y), &b);
MPZ_CTX_INIT(mrb, ctx, pool);
return mpz_cmp(ctx, &a, &b);
}
mrb_value
mrb_bint_pow(mrb_state *mrb, mrb_value x, mrb_value y)
{
mpz_t a;
bint_as_mpz(RBIGINT(x), &a);
switch (mrb_type(y)) {
case MRB_TT_INTEGER:
break;
case MRB_TT_BIGINT:
mrb_raise(mrb, E_TYPE_ERROR, "too big power");
default:
mrb_raisef(mrb, E_TYPE_ERROR, "%Y cannot be convert to integer", y);
}
mpz_t z;
MPZ_CTX_INIT(mrb, ctx, pool);
mpz_pow(ctx, &z, &a, mrb_integer(y));
struct RBigint *b = bint_new(ctx, &z);
return mrb_obj_value(b);
}
mrb_value
mrb_bint_powm(mrb_state *mrb, mrb_value x, mrb_value exp, mrb_value mod)
{
mpz_t a, b, c, z;
MPZ_CTX_INIT(mrb, ctx, pool);
bint_as_mpz(RBIGINT(x), &a);
if (mrb_integer_p(mod)) {
mrb_int m = mrb_integer(mod);
if (m == 0) mrb_int_zerodiv(mrb);
mpz_init_set_int(ctx, &c, m);
}
else {
mod = mrb_as_bint(mrb, mod);
bint_as_mpz(RBIGINT(mod), &c);
if (zero_p(&c) || uzero_p(&c)) {
mrb_int_zerodiv(mrb);
}
}
mpz_init(ctx, &z);
if (mrb_bigint_p(exp)) {
bint_as_mpz(RBIGINT(exp), &b);
if (b.sn < 0) goto raise;
mpz_powm(ctx, &z, &a, &b, &c);
}
else {
mrb_int e = mrb_integer(exp);
if (e < 0) goto raise;
mpz_powm_i(ctx, &z, &a, e, &c);
}
if (mrb_integer_p(mod)) mpz_clear(ctx, &c);
return bint_norm(mrb, bint_new(ctx, &z));
raise:
if (mrb_integer_p(mod)) mpz_clear(ctx, &c);
mrb_raise(mrb, E_ARGUMENT_ERROR, "int.pow(n,m): n must be positive");
/* not reached */
return mrb_nil_value();
}
mrb_value
mrb_bint_to_s(mrb_state *mrb, mrb_value x, mrb_int base)
{
mpz_t a;
bint_as_mpz(RBIGINT(x), &a);
if (zero_p(&a) || uzero_p(&a)) {
return mrb_str_new_lit(mrb, "0");
}
size_t len = mpz_sizeinbase(&a, (int)base);
if (sizeof(size_t) >= sizeof(mrb_int) && MRB_INT_MAX-2 < len) {
mrb_raise(mrb, E_ARGUMENT_ERROR, "too long string from Integer");
}
mrb_value str = mrb_str_new(mrb, NULL, len+2);
MPZ_CTX_INIT(mrb, ctx, pool);
mpz_get_str(ctx, RSTRING_PTR(str), len, base, &a);
RSTR_SET_LEN(RSTRING(str), strlen(RSTRING_PTR(str)));
return str;
}
mrb_value
mrb_bint_and(mrb_state *mrb, mrb_value x, mrb_value y)
{
mpz_t a, b, c;
bint_as_mpz(RBIGINT(x), &a);
if (mrb_integer_p(y)) {
mrb_int z = mrb_integer(y);
if (z == 0) return mrb_fixnum_value(0);
if (z > 0 && (mp_dbl_limb)z < DIG_BASE) {
z &= a.p[0];
return mrb_int_value(mrb, z);
}
if (z == -1) return x;
}
y = mrb_as_bint(mrb, y);
bint_as_mpz(RBIGINT(y), &b);
if (zero_p(&a) || zero_p(&b)) return mrb_fixnum_value(0);
MPZ_CTX_INIT(mrb, ctx, pool);
mpz_init(ctx, &c);
mpz_and(ctx, &c, &a, &b);
return bint_norm(mrb, bint_new(ctx, &c));
}
mrb_value
mrb_bint_or(mrb_state *mrb, mrb_value x, mrb_value y)
{
mpz_t a, b, c;
bint_as_mpz(RBIGINT(x), &a);
if (mrb_integer_p(y)) {
mrb_int z = mrb_integer(y);
if (z == 0) return x;
if (z == -1) return y;
}
MPZ_CTX_INIT(mrb, ctx, pool);
y = mrb_as_bint(mrb, y);
bint_as_mpz(RBIGINT(y), &b);
if (zero_p(&a)) return y;
if (zero_p(&b)) return x;
mpz_init(ctx, &c);
mpz_or(ctx, &c, &b, &a);
return bint_norm(mrb, bint_new(ctx, &c));
}
mrb_value
mrb_bint_xor(mrb_state *mrb, mrb_value x, mrb_value y)
{
mpz_t a, b, c;
MPZ_CTX_INIT(mrb, ctx, pool);
bint_as_mpz(RBIGINT(x), &a);
if (mrb_integer_p(y) && a.sn > 0) {
mrb_int z = mrb_integer(y);
if (z == 0) return x;
if (0 < z && (mp_dbl_limb)z < DIG_BASE) {
mpz_init_set(ctx, &c, &a);
c.p[0] ^= z;
return bint_norm(mrb, bint_new(ctx, &c));
}
}
y = mrb_as_bint(mrb, y);
bint_as_mpz(RBIGINT(y), &b);
if (zero_p(&a)) return y;
if (zero_p(&b)) return x;
mpz_init(ctx, &c);
mpz_xor(ctx, &c, &a, &b);
return bint_norm(mrb, bint_new(ctx, &c));
}
mrb_value
mrb_bint_neg(mrb_state *mrb, mrb_value x)
{
mpz_t a, b;
MPZ_CTX_INIT(mrb, ctx, pool);
bint_as_mpz(RBIGINT(x), &a);
mpz_init(ctx, &b);
mpz_neg(ctx, &b, &a);
struct RBigint *b2 = bint_new(ctx, &b);
/* no normalization */
return mrb_obj_value(b2);
}
mrb_value
mrb_bint_rev(mrb_state *mrb, mrb_value x)
{
mpz_t a, b;
MPZ_CTX_INIT(mrb, ctx, pool);
bint_as_mpz(RBIGINT(x), &a);
mpz_init(ctx, &b);
mpz_neg(ctx, &b, &a);
mpz_sub_int(ctx, &b, 1);
return bint_norm(mrb, bint_new(ctx, &b));
}
mrb_value
mrb_bint_lshift(mrb_state *mrb, mrb_value x, mrb_int width)
{
mpz_t a, z;
MPZ_CTX_INIT(mrb, ctx, pool);
bint_as_mpz(RBIGINT(x), &a);
mpz_init(ctx, &z);
if (width < 0) {
mpz_div_2exp(ctx, &z, &a, -width);
}
else {
mpz_mul_2exp(ctx, &z, &a, width);
}
return bint_norm(mrb, bint_new(ctx, &z));
}
mrb_value
mrb_bint_rshift(mrb_state *mrb, mrb_value x, mrb_int width)
{
mpz_t a, z;
MPZ_CTX_INIT(mrb, ctx, pool);
bint_as_mpz(RBIGINT(x), &a);
mpz_init(ctx, &z);
if (width < 0) {
mpz_mul_2exp(ctx, &z, &a, -width);
}
else {
mpz_div_2exp(ctx, &z, &a, width);
}
return bint_norm(mrb, bint_new(ctx, &z));
}
void
mrb_bint_copy(mrb_state *mrb, mrb_value x, mrb_value y)
{
mpz_t a, b;
MPZ_CTX_INIT(mrb, ctx, pool);
bint_as_mpz(RBIGINT(x), &a);
bint_as_mpz(RBIGINT(y), &b);
mpz_init_set(ctx, &a, &b);
}
size_t
mrb_bint_memsize(mrb_value x)
{
mpz_t z;
bint_as_mpz(RBIGINT(x), &z);
return z.sz * sizeof(mp_limb);
}
mrb_value
mrb_bint_sqrt(mrb_state *mrb, mrb_value x)
{
mpz_t a;
bint_as_mpz(RBIGINT(x), &a);
if (a.sn < 0) {
mrb_raise(mrb, E_ARGUMENT_ERROR, "square root of negative number");
}
MPZ_CTX_INIT(mrb, ctx, pool);
mpz_t z;
mpz_init(ctx, &z);
mpz_sqrt(ctx, &z, &a);
return bint_norm(mrb, bint_new(ctx, &z));
}
mrb_int
mrb_bint_sign(mrb_state *mrb, mrb_value bint)
{
return RBIGINT_SIGN(RBIGINT(bint));
}
mrb_int
mrb_bint_size(mrb_state *mrb, mrb_value bint)
{
mpz_t z;
bint_as_mpz(RBIGINT(bint), &z);
return z.sz * sizeof(mp_limb);
}
mrb_value
mrb_bint_from_bytes(mrb_state *mrb, const uint8_t *bytes, mrb_int len)
{
mpz_t z;
size_t limb_len = (len + sizeof(mp_limb) - 1) / sizeof(mp_limb);
MPZ_CTX_INIT(mrb, ctx, pool);
mpz_init_heap(ctx, &z, limb_len);
memcpy(z.p, bytes, len);
z.sn = (len > 0) ? 1 : 0;
z.sz = limb_len;
trim(&z);
return bint_norm(mrb, bint_new(ctx, &z));
}
mrb_value
mrb_bint_hash(mrb_state *mrb, mrb_value x)
{
mpz_t z;
bint_as_mpz(RBIGINT(x), &z);
uint32_t hash = mrb_byte_hash((uint8_t*)z.p, z.sz*sizeof(mp_limb));
hash = mrb_byte_hash_step((uint8_t*)&z.sn, sizeof(z.sn), hash);
return mrb_int_value(mrb, hash);
}
/* to be used only from mruby-sprintf */
mrb_value
mrb_bint_2comp(mrb_state *mrb, mrb_value x)
{
mpz_t a, z;
MPZ_CTX_INIT(mrb, ctx, pool);
bint_as_mpz(RBIGINT(x), &a);
mpz_init(ctx, &z);
mrb_assert(a.sn < 0);
size_t size = a.sz;
mpz_realloc(ctx, &z, size);
mp_limb *ds = a.p;
mp_limb *dd = z.p;
char carry = 1;
for (size_t i=0; i<size; i++) {
mp_limb xv = ds[i];
make_2comp(xv, carry);
dd[i] = xv;
}
z.sn = 1;
struct RBigint *b2 = bint_new(ctx, &z);
return mrb_obj_value(b2);
}
#ifdef MRB_USE_RATIONAL
void
mrb_bint_reduce(mrb_state *mrb, mrb_value *xp, mrb_value *yp)
{
mpz_t r, x, y, a, b;
MPZ_CTX_INIT(mrb, ctx, pool);
mpz_init(ctx, &r);
mpz_init(ctx, &a); mpz_init(ctx, &b);
bint_as_mpz(RBIGINT(*xp), &x);
bint_as_mpz(RBIGINT(*yp), &y);
mpz_gcd(ctx, &r, &x, &y);
mpz_mdiv(ctx, &a, &x, &r);
mpz_mdiv(ctx, &b, &y, &r);
mpz_clear(ctx, &r);
struct RBigint *b1 = bint_new(ctx, &a);
struct RBigint *b2 = bint_new(ctx, &b);
*xp = mrb_obj_value(b1);
*yp = mrb_obj_value(b2);
}
#endif
mrb_value
mrb_bint_gcd(mrb_state *mrb, mrb_value x, mrb_value y)
{
mpz_t r, a, b;
MPZ_CTX_INIT(mrb, ctx, pool);
mpz_init(ctx, &r);
bint_as_mpz(RBIGINT(x), &a);
bint_as_mpz(RBIGINT(y), &b);
mpz_gcd(ctx, &r, &a, &b);
struct RBigint *result = bint_new(ctx, &r);
return bint_norm(mrb, result);
}
mrb_value
mrb_bint_lcm(mrb_state *mrb, mrb_value x, mrb_value y)
{
mpz_t gcd_val, x_mpz, y_mpz, abs_x, abs_y, product, result_mpz;
mrb_value zero = mrb_bint_new_int(mrb, 0);
if (mrb_bint_cmp(mrb, x, zero) == 0 || mrb_bint_cmp(mrb, y, zero) == 0) {
return zero;
}
MPZ_CTX_INIT(mrb, ctx, pool);
/* Get input operand sizes for size estimation */
size_t x_size = RBIGINT_EMBED_P(RBIGINT(x)) ? RBIGINT_EMBED_SIZE(RBIGINT(x)) : RBIGINT(x)->as.heap.sz;
size_t y_size = RBIGINT_EMBED_P(RBIGINT(y)) ? RBIGINT_EMBED_SIZE(RBIGINT(y)) : RBIGINT(y)->as.heap.sz;
size_t max_size = (x_size > y_size) ? x_size : y_size;
mpz_init_temp(ctx, &gcd_val, max_size);
mpz_init_temp(ctx, &abs_x, x_size);
mpz_init_temp(ctx, &abs_y, y_size);
mpz_init_temp(ctx, &product, x_size + y_size + 1);
mpz_init_temp(ctx, &result_mpz, x_size + y_size + 1);
bint_as_mpz(RBIGINT(x), &x_mpz);
bint_as_mpz(RBIGINT(y), &y_mpz);
mpz_abs(ctx, &abs_x, &x_mpz);
mpz_abs(ctx, &abs_y, &y_mpz);
mpz_gcd(ctx, &gcd_val, &abs_x, &abs_y);
mpz_mul(ctx, &product, &abs_x, &abs_y);
mpz_mdiv(ctx, &result_mpz, &product, &gcd_val);
mpz_clear(ctx, &gcd_val);
mpz_clear(ctx, &abs_x);
mpz_clear(ctx, &abs_y);
mpz_clear(ctx, &product);
struct RBigint *result = bint_new(ctx, &result_mpz);
return mrb_obj_value(result);
}
mrb_value
mrb_bint_abs(mrb_state *mrb, mrb_value x)
{
mpz_t a, result_mpz;
MPZ_CTX_INIT(mrb, ctx, pool);
mpz_init(ctx, &result_mpz);
bint_as_mpz(RBIGINT(x), &a);
mpz_abs(ctx, &result_mpz, &a);
struct RBigint *result = bint_new(ctx, &result_mpz);
return mrb_obj_value(result);
}