Files
mruby-mruby/mrbgems/mruby-bigint/core/bigint.c
T
Yukihiro "Matz" Matsumoto 4e505b2b85 mruby-bigint: fix multiplication commutativity bug
Fixed non-commutative multiplication bug where operands with different
limb counts would produce different results based on order (a*b \!= b*a).

Root cause was asymmetric carry propagation in the multiplication algorithm.
The fix ensures consistent operand ordering by always processing the smaller
operand first in the nested loops, making multiplication truly commutative.

Also fixed division algorithm quotient allocation and qhat refinement.

Co-authored-by: Claude <noreply@anthropic.com>
2025-08-14 10:52:48 +09:00

2951 lines
62 KiB
C

/**
** @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)
static void
mpz_init(mrb_state *mrb, mpz_t *s)
{
s->p = NULL;
s->sn = 0;
s->sz = 0;
}
/* Helper macros for safer temporary variable management */
#define MPZ_TMP_INIT(mrb, var) \
mpz_t var; \
mpz_init(mrb, &var)
#define MPZ_TMP_CLEAR(mrb, var) \
mpz_clear(mrb, &var)
static void
mpz_realloc(mrb_state *mrb, mpz_t *x, size_t size)
{
if (x->sz < size) {
/* Check for overflow in size calculation */
if (size > SIZE_MAX / sizeof(mp_limb)) {
mrb_raise(mrb, E_RUNTIME_ERROR, "bigint size too large");
}
size_t old_sz = x->sz;
x->p = (mp_limb*)mrb_realloc(mrb, x->p, size * sizeof(mp_limb));
/* Zero-initialize new limbs */
for (size_t i = old_sz; i < size; i++) {
x->p[i] = 0;
}
x->sz = size;
}
}
static void
mpz_set(mrb_state *mrb, mpz_t *y, mpz_t *x)
{
size_t i, k = x->sz;
mpz_realloc(mrb, 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(mrb_state *mrb, mpz_t *s, mpz_t *t)
{
mpz_init(mrb, s);
mpz_set(mrb, s, t);
}
static void
mpz_set_int(mrb_state *mrb, 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(mrb, y, 2);
y->p[1] = (mp_limb)HIGH(u);
y->p[0] = (mp_limb)LOW(u);
return;
}
#endif
mpz_realloc(mrb, y, 1);
y->p[0] = (mp_limb)u;
}
static void
mpz_set_uint64(mrb_state *mrb, 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(mrb, 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(mrb_state *mrb, 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(mrb, y, u);
if (v < 0) {
y->sn = -1;
}
}
#endif
static void
mpz_init_set_int(mrb_state *mrb, mpz_t *y, mrb_int v)
{
mpz_init(mrb, y);
mpz_set_int(mrb, y, v);
}
static void
mpz_clear(mrb_state *mrb, mpz_t *s)
{
if (s->p) {
mrb_free(mrb, s->p);
s->p = NULL; /* Prevent double-free */
}
s->sn = 0;
s->sz = 0;
}
static void
mpz_move(mrb_state *mrb, mpz_t *y, mpz_t *x)
{
mpz_clear(mrb, 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 */
static void
uadd(mrb_state *mrb, mpz_t *z, mpz_t *x, mpz_t *y)
{
if (y->sz < x->sz) {
mpz_t *t; /* swap x,y */
t=x; x=y; y=t;
}
/* now y->sz >= x->sz */
mpz_realloc(mrb, z, y->sz+1);
mp_dbl_limb c = 0;
size_t i;
for (i=0; 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;
}
for (;i<y->sz; i++) {
c += y->p[i];
z->p[i] = LOW(c);
c >>= DIG_SIZE;
}
z->p[y->sz] = (mp_limb)c;
}
/* z = y - x, ignoring sign */
/* precondition: abs(y) >= abs(x) */
static void
usub(mrb_state *mrb, mpz_t *z, mpz_t *y, mpz_t *x)
{
mpz_realloc(mrb, z, (size_t)(y->sz));
mp_dbl_limb_signed b = 0;
size_t i;
for (i=0;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);
}
for (;i<y->sz; i++) {
b += y->p[i];
z->p[i] = LOW(b);
b = HIGH(b);
}
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(mrb_state *mrb, mpz_t *zz, mpz_t *x, mpz_t *y)
{
if (zero_p(x)) {
mpz_set(mrb, zz, y);
return;
}
if (zero_p(y)) {
mpz_set(mrb, zz, x);
return;
}
mpz_t z;
mpz_init(mrb, &z);
if (x->sn > 0 && y->sn > 0) {
uadd(mrb, &z, x, y);
z.sn = 1;
}
else if (x->sn < 0 && y->sn < 0) {
uadd(mrb, &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(mrb, &z, x, y);
z.sn = (x->sn > 0 && y->sn < 0) ? 1 : (-1);
}
else { /* abs(y) > abs(x) */
usub(mrb, &z, y, x);
z.sn = (x->sn < 0 && y->sn > 0) ? 1 : (-1);
}
}
trim(&z);
mpz_move(mrb, zz, &z);
}
/* x += n */
/* ignores sign of x */
/* assumes n is positive and small (fits in mp_limb) */
static void
mpz_add_int(mrb_state *mrb, 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(mrb, 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(mrb_state *mrb, 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(mrb, 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(mrb_state *mrb, 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(mrb, 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);
}
/* w = u * v */
/* Simple Multiply */
static void
mpz_mul(mrb_state *mrb, mpz_t *ww, mpz_t *u, mpz_t *v)
{
if (zero_p(u) || zero_p(v)) {
zero(ww);
return;
}
// Ensure consistent operand ordering: smaller operand as second argument
mpz_t *first, *second;
if (u->sz <= v->sz) {
first = u;
second = v;
}
else {
first = v;
second = u;
}
mpz_t w;
mpz_init(mrb, &w);
mpz_realloc(mrb, &w, first->sz + second->sz + 1);
// Standard multiplication algorithm with consistent operand order
for (size_t j = 0; j < first->sz; j++) {
mp_limb u0 = first->p[j];
if (u0 == 0) continue;
mp_dbl_limb cc = 0;
size_t i;
for (i = 0; i < second->sz; i++) {
mp_limb v0 = second->p[i];
cc += (mp_dbl_limb)w.p[i + j] + (mp_dbl_limb)u0 * (mp_dbl_limb)v0;
w.p[i + j] = LOW(cc);
cc = HIGH(cc);
}
// Propagate carries
while (cc && (i + j) < w.sz) {
cc += (mp_dbl_limb)w.p[i + j];
w.p[i + j] = LOW(cc);
cc = HIGH(cc);
i++;
}
}
w.sn = u->sn * v->sn;
trim(&w);
mpz_move(mrb, ww, &w);
}
/* 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(mrb_state *mrb, mpz_t *c1, mpz_t *a, size_t n)
{
mrb_assert(n < DIG_SIZE);
if (n == 0)
mpz_set(mrb, 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(mrb, &c);
mpz_realloc(mrb, &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(mrb, c1, &c);
}
}
/* c1 = a<<n */
/* n must be < DIG_SIZE */
static void
ulshift(mrb_state *mrb, mpz_t *c1, mpz_t *a, size_t n)
{
mrb_assert(n < DIG_SIZE);
if (n == 0)
mpz_set(mrb, 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(mrb, &c);
mpz_realloc(mrb, &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(mrb, c1, &c);
}
}
/* Fast division by single limb */
static void
mpz_div_limb(mrb_state *mrb, mpz_t *q, mpz_t *r, mpz_t *x, mp_limb d)
{
if (zero_p(x)) {
zero(q);
zero(r);
return;
}
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_set(mrb, 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) {
zero(q);
}
else {
size_t new_size = x->sz - limb_shift;
mpz_realloc(mrb, q, new_size);
if (bit_shift == 0) {
/* Simple limb copy */
for (size_t i = 0; i < new_size; i++) {
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];
q->p[i - 1] = (current >> bit_shift) | carry;
carry = (current << (DIG_SIZE - bit_shift)) & DIG_MASK;
}
}
q->sz = new_size;
trim(q);
q->sn = (q->sz == 0) ? 0 : 1;
}
}
/* Remainder = x & (d - 1) */
mpz_realloc(mrb, r, 1);
r->p[0] = x->p[0] & (d - 1);
r->sz = (r->p[0] == 0) ? 0 : 1;
r->sn = (r->sz == 0) ? 0 : 1;
return;
}
/* General single-limb division */
if (x->sz == 1) {
/* Both dividend and divisor are single limb */
mpz_realloc(mrb, q, 1);
mpz_realloc(mrb, r, 1);
q->p[0] = x->p[0] / d;
r->p[0] = x->p[0] % d;
q->sz = (q->p[0] == 0) ? 0 : 1;
q->sn = (q->sz == 0) ? 0 : 1;
r->sz = (r->p[0] == 0) ? 0 : 1;
r->sn = (r->sz == 0) ? 0 : 1;
return;
}
/* Multi-limb dividend, single-limb divisor */
size_t n = x->sz;
mpz_realloc(mrb, q, n);
mp_dbl_limb 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];
q->p[i-1] = (mp_limb)(remainder / d);
remainder = remainder % d;
}
/* Set remainder */
mpz_realloc(mrb, r, 1);
r->p[0] = (mp_limb)remainder;
r->sz = (remainder == 0) ? 0 : 1;
r->sn = (r->sz == 0) ? 0 : 1;
/* Trim leading zeros from quotient */
trim(q);
q->sn = (q->sz == 0) ? 0 : 1;
}
/* internal routine to compute x/y and x%y ignoring signs */
/* qq = xx/yy; rr = xx%yy */
static void
udiv(mrb_state *mrb, mpz_t *qq, mpz_t *rr, mpz_t *xx, mpz_t *yy)
{
/* simple cases */
int cmp = ucmp(xx, yy);
if (cmp == 0) {
mpz_set_int(mrb, qq, 1);
zero(rr);
return;
}
else if (cmp < 0) {
zero(qq);
mpz_set(mrb, rr, xx);
return;
}
/* Fast path for single-limb divisor */
if (yy->sz == 1) {
mpz_div_limb(mrb, qq, rr, xx, yy->p[0]);
return;
}
mpz_t q, x, y;
mrb_assert(yy->sn != 0); /* divided by zero */
mrb_assert(yy->sz > 0); /* divided by zero */
mpz_init(mrb, &q);
mpz_init(mrb, &x);
mpz_init(mrb, &y);
mpz_realloc(mrb, &x, xx->sz+1);
size_t yd = digits(yy);
size_t ns = lzb(yy->p[yd-1]);
ulshift(mrb, &x, xx, ns);
ulshift(mrb, &y, yy, ns);
size_t xd = digits(&x);
mpz_realloc(mrb, &q, xd-yd+1); // Quotient has xd-yd+1 digits maximum
mp_dbl_limb z = y.p[yd-1];
if (xd>=yd) {
for (size_t j=xd-yd;; j--) {
mp_dbl_limb_signed b=0;
mp_dbl_limb qhat;
mp_dbl_limb rhat;
if (j+yd == xd) {
// Treat missing high limb as 0 and use the same two-limb formula
qhat = (((mp_dbl_limb)0 << DIG_SIZE) + x.p[j+yd-1]) / z;
rhat = (((mp_dbl_limb)0 << DIG_SIZE) + x.p[j+yd-1]) % z;
}
else {
mp_dbl_limb dividend = ((mp_dbl_limb)x.p[j+yd] << DIG_SIZE) + x.p[j+yd-1];
qhat = dividend / z;
rhat = dividend % z;
}
// Knuth's qhat refinement step - essential to prevent overestimation
if (yd > 1) { // Apply refinement for all iterations, including j=0
mp_dbl_limb left_side = qhat * y.p[yd-2];
mp_dbl_limb right_side = (rhat << DIG_SIZE) + (j+yd-2 < x.sz ? x.p[j+yd-2] : 0);
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 = qhat * y.p[yd-2];
right_side = (rhat << DIG_SIZE) + (j+yd-2 < x.sz ? x.p[j+yd-2] : 0);
}
}
if (qhat) {
size_t i;
for (i=0; i<yd; i++) {
mp_dbl_limb zz = qhat * y.p[i];
mp_dbl_limb_signed u = LOW(b)+x.p[i+j]-LOW(zz);
x.p[i+j] = LOW(u);
b = HIGH(b) - HIGH(zz) + HIGH(u);
}
b += x.p[i+j];
}
for (; b!=0; qhat--) {
mp_dbl_limb c = 0;
for (size_t i=0; i<yd; i++) {
c += (mp_dbl_limb)x.p[i+j] + (mp_dbl_limb)y.p[i];
x.p[i+j] = LOW(c);
c = HIGH(c);
}
b += c;
}
q.p[j] = (mp_limb)qhat;
if (j == 0) break;
}
}
x.sz = yy->sz;
urshift(mrb, rr, &x, ns);
trim(&q);
mpz_move(mrb, qq, &q);
mpz_clear(mrb, &x);
mpz_clear(mrb, &y);
}
static void
mpz_mdiv(mrb_state *mrb, 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(mrb, q, 0);
return;
}
mpz_init(mrb, &r);
udiv(mrb, 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(mrb, q, 1);
/* force negative sign in case the value of q was zero before rounding */
q->sn = -1;
}
mpz_clear(mrb, &r);
}
static void
mpz_mmod(mrb_state *mrb, mpz_t *r, mpz_t *x, mpz_t *y)
{
mpz_t q;
short sn1 = x->sn, sn2 = y->sn, sn3;
mpz_init(mrb, &q);
if (sn1 == 0) {
zero(r);
return;
}
udiv(mrb, &q, r, x, y);
mpz_clear(mrb, &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(mrb, r, y, r);
}
else {
r->sn = 1;
mpz_add(mrb, r, y, r);
}
}
static void
mpz_mdivmod(mrb_state *mrb, 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(mrb, 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(mrb, r, y, r);
}
else {
r->sn = 1;
mpz_add(mrb, 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(mrb, 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(mrb_state *mrb, 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(mrb, 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(mrb, 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(mrb_state *mrb, mpz_t *mu, mpz_t *m);
static void mpz_barrett_reduce(mrb_state *mrb, mpz_t *r, mpz_t *x, mpz_t *m, mpz_t *mu);
static void
mpz_mod(mrb_state *mrb, mpz_t *r, mpz_t *x, mpz_t *y)
{
mpz_t q;
short sn = x->sn;
if (zero_p(x)) {
zero(r);
return;
}
/* Fast path for single-limb modulus */
if (y->sz == 1) {
mpz_mod_limb(mrb, 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 <= 8 && x->sz >= y->sz + 2) {
mpz_t mu;
mpz_init(mrb, &mu);
mpz_barrett_mu(mrb, &mu, y);
mpz_barrett_reduce(mrb, r, x, y, &mu);
r->sn = sn;
if (uzero_p(r))
r->sn = 0;
mpz_clear(mrb, &mu);
return;
}
/* General division fallback */
mpz_init(mrb, &q);
udiv(mrb, &q, r, x, y);
r->sn = sn;
if (uzero_p(r))
r->sn = 0;
mpz_clear(mrb, &q);
}
static mrb_int
mpz_cmp(mrb_state *mrb, 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(mrb_state *mrb, 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(mrb, 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(mrb_state *mrb, mpz_t *x, const char *s, mrb_int len, mrb_int base)
{
int retval = 0;
short sn;
uint8_t k;
mpz_init(mrb, 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(mrb, x, base);
mpz_add_int(mrb, 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(mrb_state *mrb, char *s, mrb_int sz, mrb_int base, mpz_t *x)
{
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 (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(mrb_state *mrb, mpz_t *z, mpz_t *x, mrb_int e)
{
if (e==0)
mpz_set(mrb, z, x);
else {
short sn = x->sn;
size_t digs = e / DIG_SIZE;
size_t bs = e % DIG_SIZE;
mpz_t y;
mpz_init(mrb, &y);
mpz_realloc(mrb, &y, x->sz+digs);
for (size_t i=0;i<x->sz;i++)
y.p[i+digs] = x->p[i];
if (bs) {
ulshift(mrb, z, &y, bs);
mpz_clear(mrb, &y);
}
else {
mpz_move(mrb, z, &y);
}
z->sn = sn;
}
}
static void
mpz_div_2exp(mrb_state *mrb, mpz_t *z, mpz_t *x, mrb_int e)
{
short sn = x->sn;
if (e==0)
mpz_set(mrb, z, x);
else {
size_t digs = e / DIG_SIZE;
size_t bs = e % DIG_SIZE;
mpz_t y;
mpz_init(mrb, &y);
mpz_realloc(mrb, &y, x->sz-digs);
for (size_t i=0; i < x->sz-digs; i++)
y.p[i] = x->p[i+digs];
if (bs) {
urshift(mrb, z, &y, bs);
mpz_clear(mrb, &y);
}
else {
mpz_move(mrb, z, &y);
}
if (uzero_p(z))
z->sn = 0;
else {
z->sn = sn;
}
}
}
static void
mpz_neg(mrb_state *mrb, mpz_t *x, mpz_t *y)
{
mpz_set(mrb, x, y);
x->sn = -(y->sn);
}
/* Fast modular reduction by power of 2: z = x mod 2^e */
static void
mpz_mod_2exp(mrb_state *mrb, mpz_t *z, mpz_t *x, mrb_int e)
{
if (e <= 0) {
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_set(mrb, z, x);
return;
}
/* Need to mask off high bits */
size_t result_sz = eint + (bs > 0 ? 1 : 0);
mpz_realloc(mrb, 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(mrb_state *mrb, mpz_t *z, mpz_t *x, mpz_t *y)
{
if (zero_p(x) || zero_p(y)) {
zero(z);
return;
}
mrb_assert(x->sz > 0 || y->sz > 0);
size_t max_sz = (x->sz > y->sz) ? x->sz : y->sz;
mpz_realloc(mrb, 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(mrb_state *mrb, mpz_t *z, mpz_t *x, mpz_t *y) /* not the most efficient way to do this */
{
if (zero_p(x)) {
mpz_set(mrb, z, y);
return;
}
if (zero_p(y)) {
mpz_set(mrb, z, x);
return;
}
mrb_assert(x->sz > 0 || y->sz > 0);
size_t max_sz = (x->sz > y->sz) ? x->sz : y->sz;
mpz_realloc(mrb, 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(mrb_state *mrb, mpz_t *z, mpz_t *x, mpz_t *y) /* not the most efficient way to do this */
{
if (zero_p(x)) {
mpz_set(mrb, z, y);
return;
}
if (zero_p(y)) {
mpz_set(mrb, z, x);
return;
}
mrb_assert(x->sz > 0 || y->sz > 0);
size_t max_sz = (x->sz > y->sz) ? x->sz : y->sz;
mpz_realloc(mrb, 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(mrb_state *mrb, mpz_t *zz, mpz_t *x, mrb_int e)
{
mpz_t t;
mrb_uint mask = 1ULL<<(sizeof(mrb_int)*8-1);
if (e==0) {
mpz_set_int(mrb, zz, 1L);
return;
}
mpz_init_set(mrb, &t, x);
for (;!(mask &e); mask>>=1)
;
mask>>=1;
for (;mask!=0; mask>>=1) {
mpz_mul(mrb, &t, &t, &t);
if (e & mask)
mpz_mul(mrb, &t, &t, x);
}
mpz_move(mrb, zz, &t);
}
static void
mpz_powm(mrb_state *mrb, mpz_t *zz, mpz_t *x, mpz_t *ex, mpz_t *n)
{
if (zero_p(ex) || uzero_p(ex)) {
mpz_set_int(mrb, zz, 1);
return;
}
if (ex->sn < 0) {
return;
}
mpz_t t, b;
mpz_init_set_int(mrb, &t, 1);
mpz_init_set(mrb, &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(mrb, &temp);
if (use_barrett) {
mpz_init(mrb, &mu);
mpz_barrett_mu(mrb, &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(mrb, &temp, &t, &b);
if (use_barrett) {
mpz_barrett_reduce(mrb, &t, &temp, n, &mu);
}
else {
mpz_mod(mrb, &t, &temp, n);
}
}
e >>= 1;
mpz_mul(mrb, &temp, &b, &b);
if (use_barrett) {
mpz_barrett_reduce(mrb, &b, &temp, n, &mu);
}
else {
mpz_mod(mrb, &b, &temp, n);
}
}
}
mpz_clear(mrb, &temp);
if (use_barrett) {
mpz_clear(mrb, &mu);
}
mpz_move(mrb, zz, &t);
mpz_clear(mrb, &b);
}
static void
mpz_powm_i(mrb_state *mrb, mpz_t *zz, mpz_t *x, mrb_int ex, mpz_t *n)
{
if (ex == 0) {
mpz_set_int(mrb, zz, 1);
return;
}
if (ex < 0) {
return;
}
mpz_t t, b;
mpz_init_set_int(mrb, &t, 1);
mpz_init_set(mrb, &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(mrb, &temp);
if (use_barrett) {
mpz_init(mrb, &mu);
mpz_barrett_mu(mrb, &mu, n);
}
while (ex > 0) {
if ((ex & 1) == 1) {
mpz_mul(mrb, &temp, &t, &b);
if (use_barrett) {
mpz_barrett_reduce(mrb, &t, &temp, n, &mu);
}
else {
mpz_mod(mrb, &t, &temp, n);
}
}
ex >>= 1;
if (ex > 0) { /* Skip final squaring when ex becomes 0 */
mpz_mul(mrb, &temp, &b, &b);
if (use_barrett) {
mpz_barrett_reduce(mrb, &b, &temp, n, &mu);
}
else {
mpz_mod(mrb, &b, &temp, n);
}
}
}
mpz_clear(mrb, &temp);
if (use_barrett) {
mpz_clear(mrb, &mu);
}
mpz_move(mrb, zz, &t);
mpz_clear(mrb, &b);
}
static void
mpz_abs(mrb_state *mrb, mpz_t *x, mpz_t *y)
{
mpz_init_set(mrb, x, y);
if (zero_p(y))
x->sn = 0;
else
x->sn = 1;
}
/* 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(mrb_state *mrb, mpz_t *gg, mpz_t *aa, mpz_t *bb)
{
mpz_t a, b;
/* Handle special cases */
if (zero_p(aa)) {
mpz_abs(mrb, gg, bb);
return;
}
if (zero_p(bb)) {
mpz_abs(mrb, gg, aa);
return;
}
/* 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(mrb, gg);
if (result == 0) {
gg->sn = 0;
gg->sz = 0;
}
else {
mpz_realloc(mrb, gg, 1);
gg->p[0] = result;
gg->sn = 1;
}
return;
}
/* Fast path for powers of 2 */
if (mpz_power_of_2_p(aa)) {
size_t a_zeros = mpz_trailing_zeros(aa);
size_t b_zeros = mpz_trailing_zeros(bb);
size_t min_zeros = (a_zeros < b_zeros) ? a_zeros : b_zeros;
mpz_init_set_int(mrb, gg, 1);
mpz_mul_2exp(mrb, gg, gg, min_zeros);
return;
}
if (mpz_power_of_2_p(bb)) {
size_t a_zeros = mpz_trailing_zeros(aa);
size_t b_zeros = mpz_trailing_zeros(bb);
size_t min_zeros = (a_zeros < b_zeros) ? a_zeros : b_zeros;
mpz_init_set_int(mrb, gg, 1);
mpz_mul_2exp(mrb, gg, gg, min_zeros);
return;
}
mpz_abs(mrb, &a, aa);
mpz_abs(mrb, &b, bb);
/* Find power of 2 that divides both a and b */
size_t a_zeros = mpz_trailing_zeros(&a);
size_t b_zeros = mpz_trailing_zeros(&b);
size_t shift = (a_zeros < b_zeros) ? a_zeros : b_zeros;
/* Remove common factors of 2 */
if (shift > 0) {
mpz_div_2exp(mrb, &a, &a, shift);
mpz_div_2exp(mrb, &b, &b, shift);
}
/* Remove remaining factors of 2 from a */
if (a_zeros > shift) {
mpz_div_2exp(mrb, &a, &a, a_zeros - shift);
}
/* Remove remaining factors of 2 from b */
if (b_zeros > shift) {
mpz_div_2exp(mrb, &b, &b, b_zeros - shift);
}
/* Use Lehmer's algorithm for large multi-limb numbers (> 3 limbs) */
if (a.sz > 3 && b.sz > 3) {
/* Extract the two most significant limbs for approximation */
mp_limb a_high = a.p[a.sz - 1];
mp_limb a_low = a.p[a.sz - 2];
mp_limb b_high = b.p[b.sz - 1];
mp_limb b_low = b.p[b.sz - 2];
/* Perform Lehmer reduction on double-precision approximations */
mp_limb u0 = 1, u1 = 0, v0 = 0, v1 = 1;
while (b_high > 0) {
/* Calculate quotient using double-precision approximation */
mp_limb q;
if (a_high == b_high) {
q = (a_low >= b_low) ? 1 : 0;
}
else {
/* Approximate quotient from most significant limbs */
q = a_high / (b_high + 1);
}
if (q == 0) break;
/* Check if applying this quotient would cause overflow */
mp_limb max_limb = (mp_limb)(-1);
if (u1 > 0 && q > max_limb / u1) break;
if (v1 > 0 && q > max_limb / v1) break;
/* Update transformation matrix */
mp_limb t;
t = u0 - q * u1; u0 = u1; u1 = t;
t = v0 - q * v1; v0 = v1; v1 = t;
t = a_high - q * b_high; a_high = b_high; b_high = t;
/* Stop if coefficients get too large */
if (u1 == 0 && v1 == 0) break;
}
/* Apply the transformation if it's non-trivial */
if (u1 != 0 || v1 != 0) {
mpz_t temp_a, temp_b, u0_a, v0_b, u1_a, v1_b;
mpz_init(mrb, &temp_a);
mpz_init(mrb, &temp_b);
mpz_init_set(mrb, &u0_a, &a);
mpz_init_set(mrb, &v0_b, &b);
mpz_init_set(mrb, &u1_a, &a);
mpz_init_set(mrb, &v1_b, &b);
/* Compute u0*a, v0*b, u1*a, v1*b */
mpz_mul_int(mrb, &u0_a, u0);
mpz_mul_int(mrb, &v0_b, v0);
mpz_mul_int(mrb, &u1_a, u1);
mpz_mul_int(mrb, &v1_b, v1);
/* temp_a = u0*a + v0*b */
mpz_add(mrb, &temp_a, &u0_a, &v0_b);
/* temp_b = u1*a + v1*b */
mpz_add(mrb, &temp_b, &u1_a, &v1_b);
/* Update a and b */
mpz_set(mrb, &a, &temp_a);
mpz_set(mrb, &b, &temp_b);
mpz_clear(mrb, &temp_a);
mpz_clear(mrb, &temp_b);
mpz_clear(mrb, &u0_a);
mpz_clear(mrb, &v0_b);
mpz_clear(mrb, &u1_a);
mpz_clear(mrb, &v1_b);
/* Ensure a >= b after transformation */
if (mpz_cmp(mrb, &a, &b) < 0) {
mpz_t temp_holder = a;
a = b;
b = temp_holder;
}
}
}
/* From here on, a is always odd */
do {
/* Make b odd efficiently */
if ((b.p[0] & 1) == 0) {
size_t b_trailing = mpz_trailing_zeros(&b);
if (b_trailing > 0) {
mpz_div_2exp(mrb, &b, &b, b_trailing);
}
}
/* Now both a and b are odd. Ensure a >= b */
if (mpz_cmp(mrb, &a, &b) < 0) {
/* In-place swap without temporary variable */
mpz_t temp_holder = a;
a = b;
b = temp_holder;
}
/* Replace a with (a - b) */
mpz_sub(mrb, &a, &a, &b);
/* Remove factors of 2 from the result if it's even */
if (a.sz > 0 && (a.p[0] & 1) == 0) {
size_t a_trailing = mpz_trailing_zeros(&a);
if (a_trailing > 0) {
mpz_div_2exp(mrb, &a, &a, a_trailing);
}
}
} while (!zero_p(&a));
/* Restore common factors of 2 */
mpz_mul_2exp(mrb, &b, &b, shift);
trim(&b);
mpz_move(mrb, gg, &b);
mpz_clear(mrb, &a);
}
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(mrb_state *mrb, mpz_t *mu, mpz_t *m)
{
size_t k = mpz_bits(m);
mpz_t temp;
mpz_init_set_int(mrb, &temp, 1);
mpz_mul_2exp(mrb, &temp, &temp, 2 * k); /* temp = 2^(2k) */
mpz_mdiv(mrb, mu, &temp, m); /* mu = floor(2^(2k) / m) */
mpz_clear(mrb, &temp);
}
/* Barrett reduction: r = x mod m using precomputed μ */
static void
mpz_barrett_reduce(mrb_state *mrb, 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(mrb, x, m) < 0) {
mpz_set(mrb, r, x);
return;
}
mpz_t q1, q2, q3, r1, r2;
mpz_init(mrb, &q1);
mpz_init(mrb, &q2);
mpz_init(mrb, &q3);
mpz_init(mrb, &r1);
mpz_init(mrb, &r2);
/* Step 1: q1 = floor(x / 2^(k-1)) */
if (k > 1) {
mpz_div_2exp(mrb, &q1, x, k - 1);
}
else {
mpz_set(mrb, &q1, x);
}
/* Step 2: q2 = q1 * μ */
mpz_mul(mrb, &q2, &q1, mu);
/* Step 3: q3 = floor(q2 / 2^(k+1)) */
mpz_div_2exp(mrb, &q3, &q2, k + 1);
/* Step 4: r1 = x mod 2^(k+1) */
mpz_mod_2exp(mrb, &r1, x, k + 1);
/* Step 5: r2 = (q3 * m) mod 2^(k+1) */
mpz_mul(mrb, &r2, &q3, m);
mpz_mod_2exp(mrb, &r2, &r2, k + 1);
/* Step 6: r = r1 - r2 */
if (mpz_cmp(mrb, &r1, &r2) >= 0) {
mpz_sub(mrb, r, &r1, &r2);
}
else {
/* r1 < r2, so add 2^(k+1) to r1 */
mpz_t power;
mpz_init_set_int(mrb, &power, 1);
mpz_mul_2exp(mrb, &power, &power, k + 1);
mpz_add(mrb, &r1, &r1, &power);
mpz_sub(mrb, r, &r1, &r2);
mpz_clear(mrb, &power);
}
/* Step 7: Final correction - ensure 0 ≤ r < m */
while (mpz_cmp(mrb, r, m) >= 0) {
mpz_sub(mrb, r, r, m);
}
mpz_clear(mrb, &q1);
mpz_clear(mrb, &q2);
mpz_clear(mrb, &q3);
mpz_clear(mrb, &r1);
mpz_clear(mrb, &r2);
}
static void
mpz_sqrt(mrb_state *mrb, mpz_t *z, mpz_t *x)
{
mrb_assert(x->sn >= 0);
if (x->sz == 0) {
// sqrt(0) = 0
z->sn = 0;
z->sz = 0;
return;
}
// Estimate initial value: 1 << (bit_length(x) / 2)
size_t xbits = mpz_bits(x);
size_t sbit = (xbits + 1) / 2;
mpz_t s, t;
mpz_init_set_int(mrb, &s, 1);
mpz_mul_2exp(mrb, &s, &s, sbit);
mpz_init(mrb, &t);
// Iteratively refine s using Newton-Raphson method:
// s = (s + x / s) / 2
for (;;) {
mpz_mdiv(mrb, &t, x, &s); // t = x / s
mpz_add(mrb, &t, &t, &s); // t = s + x/s
mpz_div_2exp(mrb, &t, &t, 1); // t = (s + x/s) / 2
if (mpz_cmp(mrb, &t, &s) >= 0) {
// Converged: t >= s
break;
}
mpz_set(mrb, &s, &t);
}
mpz_move(mrb, z, &s);
mpz_clear(mrb, &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(mrb_state *mrb, mpz_t *x)
{
struct RBigint *b = MRB_OBJ_ALLOC(mrb, MRB_TT_BIGINT, mrb->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(mrb, x);
}
else {
RBIGINT_SET_HEAP(b);
b->as.heap = *x;
}
return b;
}
static struct RBigint*
bint_new_int(mrb_state *mrb, mrb_int n)
{
mpz_t x;
mpz_init_set_int(mrb, &x, n);
return bint_new(mrb, &x);
}
mrb_value
mrb_bint_new_int(mrb_state *mrb, mrb_int x)
{
struct RBigint *b = bint_new_int(mrb, 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_set_int64(mrb, &x, n);
struct RBigint *b = bint_new(mrb, &x);
return mrb_obj_value(b);
}
#endif
mrb_value
mrb_bint_new_uint64(mrb_state *mrb, uint64_t x)
{
mpz_t z;
mpz_init(mrb, &z);
mpz_set_uint64(mrb, &z, x);
struct RBigint *b = bint_new(mrb ,&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_init_set_str(mrb, &z, x, len, base);
if (sn < 0) {
z.sn = sn;
}
struct RBigint *b = bint_new(mrb, &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;
if (!RBIGINT_EMBED_P(b)) {
mpz_clear(mrb, &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_t r;
mpz_init(mrb, &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(mrb, &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(mrb, &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);
if (mrb_integer_p(y)) {
mrb_int n = mrb_integer(y);
if (int_fit_limb_p(n)) {
mpz_init_set(mrb, &z, &a);
if ((n > 0) ^ (z.sn > 0)) {
mpz_sub_int(mrb, &z, n<0 ? -n : n);
}
else {
mpz_add_int(mrb, &z, n<0 ? -n : n);
}
struct RBigint *v = bint_new(mrb, &z);
return mrb_obj_value(v);
}
}
y = mrb_as_bint(mrb, y);
bint_as_mpz(RBIGINT(y), &b);
mpz_init(mrb, &z);
mpz_add(mrb, &z, &a, &b);
struct RBigint *v = bint_new(mrb, &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;
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(mrb, &z, &a);
if ((n > 0) ^ (z.sn > 0)) {
mpz_add_int(mrb, &z, n<0 ? -n : n);
}
else {
mpz_sub_int(mrb, &z, n<0 ? -n : n);
}
struct RBigint *v = bint_new(mrb, &z);
return mrb_obj_value(v);
}
}
y = mrb_as_bint(mrb, y);
bint_as_mpz(RBIGINT(y), &b);
mpz_init(mrb, &z);
mpz_sub(mrb, &z, &a, &b);
struct RBigint *v = bint_new(mrb, &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_init(mrb, &z);
mpz_mul(mrb, &z, &a, &b);
return bint_new(mrb, &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_init(mrb, &z);
mpz_mdiv(mrb, &z, &a, &b);
return bint_norm(mrb, bint_new(mrb, &z));
}
mrb_value
mrb_bint_add_ii(mrb_state *mrb, mrb_int x, mrb_int y)
{
mpz_t a, b, z;
mpz_init_set_int(mrb, &a, x);
mpz_init_set_int(mrb, &b, y);
mpz_init(mrb, &z);
mpz_add(mrb, &z, &a, &b);
mpz_clear(mrb, &a);
mpz_clear(mrb, &b);
return bint_norm(mrb, bint_new(mrb, &z));
}
mrb_value
mrb_bint_sub_ii(mrb_state *mrb, mrb_int x, mrb_int y)
{
mpz_t a, b, z;
mpz_init_set_int(mrb, &a, x);
mpz_init_set_int(mrb, &b, y);
mpz_init(mrb, &z);
mpz_sub(mrb, &z, &a, &b);
mpz_clear(mrb, &a);
mpz_clear(mrb, &b);
return bint_norm(mrb, bint_new(mrb, &z));
}
mrb_value
mrb_bint_mul_ii(mrb_state *mrb, mrb_int x, mrb_int y)
{
mpz_t a, b, z;
mpz_init_set_int(mrb, &a, x);
mpz_init_set_int(mrb, &b, y);
mpz_init(mrb, &z);
mpz_mul(mrb, &z, &a, &b);
mpz_clear(mrb, &a);
mpz_clear(mrb, &b);
return bint_norm(mrb, bint_new(mrb, &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_init(mrb, &z);
mpz_mmod(mrb, &z, &a, &b);
return bint_norm(mrb, bint_new(mrb, &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_init(mrb, &z);
mpz_mod(mrb, &z, &a, &b);
return bint_norm(mrb, bint_new(mrb, &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_init(mrb, &c);
mpz_init(mrb, &d);
mpz_mdivmod(mrb, &c, &d, &a, &b);
return mrb_assoc_new(mrb, bint_norm(mrb, bint_new(mrb, &c)), bint_norm(mrb, bint_new(mrb, &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);
return mpz_cmp(mrb, &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_init(mrb, &z);
mpz_pow(mrb, &z, &a, mrb_integer(y));
struct RBigint *b = bint_new(mrb, &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;
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(mrb, &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(mrb, &z);
if (mrb_bigint_p(exp)) {
bint_as_mpz(RBIGINT(exp), &b);
if (b.sn < 0) goto raise;
mpz_powm(mrb, &z, &a, &b, &c);
}
else {
mrb_int e = mrb_integer(exp);
if (e < 0) goto raise;
mpz_powm_i(mrb, &z, &a, e, &c);
}
if (mrb_integer_p(mod)) mpz_clear(mrb, &c);
return bint_norm(mrb, bint_new(mrb, &z));
raise:
if (mrb_integer_p(mod)) mpz_clear(mrb, &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_get_str(mrb, 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_init(mrb, &c);
mpz_and(mrb, &c, &a, &b);
return bint_norm(mrb, bint_new(mrb, &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;
}
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(mrb, &c);
mpz_or(mrb, &c, &b, &a);
return bint_norm(mrb, bint_new(mrb, &c));
}
mrb_value
mrb_bint_xor(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) && 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(mrb, &c, &a);
c.p[0] ^= z;
return bint_norm(mrb, bint_new(mrb, &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(mrb, &c);
mpz_xor(mrb, &c, &a, &b);
return bint_norm(mrb, bint_new(mrb, &c));
}
mrb_value
mrb_bint_neg(mrb_state *mrb, mrb_value x)
{
mpz_t a, b;
bint_as_mpz(RBIGINT(x), &a);
mpz_init(mrb, &b);
mpz_neg(mrb, &b, &a);
struct RBigint *b2 = bint_new(mrb, &b);
/* no normalization */
return mrb_obj_value(b2);
}
mrb_value
mrb_bint_rev(mrb_state *mrb, mrb_value x)
{
mpz_t a, b;
bint_as_mpz(RBIGINT(x), &a);
mpz_init(mrb, &b);
mpz_neg(mrb, &b, &a);
mpz_sub_int(mrb, &b, 1);
return bint_norm(mrb, bint_new(mrb, &b));
}
mrb_value
mrb_bint_lshift(mrb_state *mrb, mrb_value x, mrb_int width)
{
mpz_t a, z;
bint_as_mpz(RBIGINT(x), &a);
mpz_init(mrb, &z);
if (width < 0) {
mpz_div_2exp(mrb, &z, &a, -width);
}
else {
mpz_mul_2exp(mrb, &z, &a, width);
}
return bint_norm(mrb, bint_new(mrb, &z));
}
mrb_value
mrb_bint_rshift(mrb_state *mrb, mrb_value x, mrb_int width)
{
mpz_t a, z;
bint_as_mpz(RBIGINT(x), &a);
mpz_init(mrb, &z);
if (width < 0) {
mpz_mul_2exp(mrb, &z, &a, -width);
}
else {
mpz_div_2exp(mrb, &z, &a, width);
}
return bint_norm(mrb, bint_new(mrb, &z));
}
void
mrb_bint_copy(mrb_state *mrb, mrb_value x, mrb_value y)
{
mpz_t a, b;
bint_as_mpz(RBIGINT(x), &a);
bint_as_mpz(RBIGINT(y), &b);
mpz_init_set(mrb, &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_t z;
mpz_init(mrb, &z);
mpz_sqrt(mrb, &z, &a);
return bint_norm(mrb, bint_new(mrb, &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;
mpz_init(mrb, &z);
size_t limb_len = (len + sizeof(mp_limb) - 1) / sizeof(mp_limb);
mpz_realloc(mrb, &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(mrb, &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;
bint_as_mpz(RBIGINT(x), &a);
mpz_init(mrb, &z);
mrb_assert(a.sn < 0);
size_t size = a.sz;
mpz_realloc(mrb, &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(mrb, &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_init(mrb, &r);
mpz_init(mrb, &a); mpz_init(mrb, &b);
bint_as_mpz(RBIGINT(*xp), &x);
bint_as_mpz(RBIGINT(*yp), &y);
mpz_gcd(mrb, &r, &x, &y);
mpz_mdiv(mrb, &a, &x, &r);
mpz_mdiv(mrb, &b, &y, &r);
mpz_clear(mrb, &r);
struct RBigint *b1 = bint_new(mrb, &a);
struct RBigint *b2 = bint_new(mrb, &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_init(mrb, &r);
bint_as_mpz(RBIGINT(x), &a);
bint_as_mpz(RBIGINT(y), &b);
mpz_gcd(mrb, &r, &a, &b);
struct RBigint *result = bint_new(mrb, &r);
mpz_clear(mrb, &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_init(mrb, &gcd_val);
mpz_init(mrb, &abs_x);
mpz_init(mrb, &abs_y);
mpz_init(mrb, &product);
mpz_init(mrb, &result_mpz);
bint_as_mpz(RBIGINT(x), &x_mpz);
bint_as_mpz(RBIGINT(y), &y_mpz);
mpz_abs(mrb, &abs_x, &x_mpz);
mpz_abs(mrb, &abs_y, &y_mpz);
mpz_gcd(mrb, &gcd_val, &abs_x, &abs_y);
mpz_mul(mrb, &product, &abs_x, &abs_y);
mpz_mdiv(mrb, &result_mpz, &product, &gcd_val);
mpz_clear(mrb, &gcd_val);
mpz_clear(mrb, &abs_x);
mpz_clear(mrb, &abs_y);
mpz_clear(mrb, &product);
struct RBigint *result = bint_new(mrb, &result_mpz);
mpz_clear(mrb, &result_mpz);
return mrb_obj_value(result);
}
mrb_value
mrb_bint_abs(mrb_state *mrb, mrb_value x)
{
mpz_t a, result_mpz;
mpz_init(mrb, &result_mpz);
bint_as_mpz(RBIGINT(x), &a);
mpz_abs(mrb, &result_mpz, &a);
struct RBigint *result = bint_new(mrb, &result_mpz);
mpz_clear(mrb, &result_mpz);
return mrb_obj_value(result);
}