GitHub

@@ -83,6 +83,7 @@ static void mpz_mul_2exp(mpz_ctx_t *ctx, mpz_t *z, mpz_t *x, mrb_int e);

8383

static void mpz_div_2exp(mpz_ctx_t *ctx, mpz_t *z, mpz_t *x, mrb_int e);

8484

static void mpz_mod_2exp(mpz_ctx_t *ctx, mpz_t *z, mpz_t *x, mrb_int e);

8585

static void mpz_set_int(mpz_ctx_t *ctx, mpz_t *y, mrb_int v);

86+

static void mpz_mul(mpz_ctx_t *ctx, mpz_t *ww, mpz_t *u, mpz_t *v);

86878788

static mp_limb*

8889

pool_alloc(mpz_pool_t *pool, size_t limbs)

@@ -2065,6 +2066,93 @@ mpz_sqr(mpz_ctx_t *ctx, mpz_t *ww, mpz_t *u)

20652066

trim(ww);

20662067

}

206720682069+

/*

2070+

* Balance multiplication for asymmetric operands.

2071+

* When one operand is much larger than the other (max >= 2*min),

2072+

* split the larger into chunks of size equal to the smaller,

2073+

* multiply each chunk, and combine with shifts.

2074+

*/

2075+

static void

2076+

mpz_mul_balance(mpz_ctx_t *ctx, mpz_t *ww, mpz_t *a, mpz_t *b)

2077+

{

2078+

/* Ensure 'a' is the larger operand */

2079+

if (a->sz < b->sz) {

2080+

mpz_t *t = a; a = b; b = t;

2081+

}

2082+2083+

size_t bsize = b->sz; /* chunk size = smaller operand size */

2084+

size_t nblocks = a->sz / bsize; /* number of full chunks */

2085+2086+

size_t pool_state = pool_save(ctx);

2087+

mpz_t chunk, tmp, result;

2088+

mpz_init(ctx, &result);

2089+

mpz_init_heap(ctx, &chunk, bsize);

2090+

mpz_init(ctx, &tmp);

2091+2092+

size_t j = 0;

2093+

for (size_t i = 0; i < nblocks; i++) {

2094+

/* Copy chunk from a */

2095+

memcpy(chunk.p, a->p + j, bsize * sizeof(mp_limb));

2096+

chunk.sz = bsize;

2097+

chunk.sn = 1;

2098+

trim(&chunk);

2099+

j += bsize;

2100+2101+

if (!zero_p(&chunk)) {

2102+

/* Multiply chunk * b */

2103+

mpz_mul(ctx, &tmp, &chunk, b);

2104+2105+

/* Shift tmp left by (i * bsize) limbs */

2106+

if (i > 0) {

2107+

size_t shift_limbs = i * bsize;

2108+

size_t old_sz = tmp.sz;

2109+

size_t new_sz = old_sz + shift_limbs;

2110+

mpz_realloc(ctx, &tmp, new_sz);

2111+

memmove(tmp.p + shift_limbs, tmp.p, old_sz * sizeof(mp_limb));

2112+

memset(tmp.p, 0, shift_limbs * sizeof(mp_limb));

2113+

tmp.sz = new_sz;

2114+

}

2115+2116+

/* Add to result */

2117+

mpz_add(ctx, &result, &result, &tmp);

2118+

}

2119+

}

2120+2121+

/* Handle leftover (remaining limbs after full chunks) */

2122+

if (j < a->sz) {

2123+

size_t remaining = a->sz - j;

2124+

mpz_realloc(ctx, &chunk, remaining);

2125+

memcpy(chunk.p, a->p + j, remaining * sizeof(mp_limb));

2126+

chunk.sz = remaining;

2127+

chunk.sn = 1;

2128+

trim(&chunk);

2129+2130+

if (!zero_p(&chunk)) {

2131+

mpz_mul(ctx, &tmp, &chunk, b);

2132+2133+

/* Shift by j limbs */

2134+

if (j > 0) {

2135+

size_t old_sz = tmp.sz;

2136+

size_t new_sz = old_sz + j;

2137+

mpz_realloc(ctx, &tmp, new_sz);

2138+

memmove(tmp.p + j, tmp.p, old_sz * sizeof(mp_limb));

2139+

memset(tmp.p, 0, j * sizeof(mp_limb));

2140+

tmp.sz = new_sz;

2141+

}

2142+2143+

mpz_add(ctx, &result, &result, &tmp);

2144+

}

2145+

}

2146+2147+

/* Apply sign: result sign = a->sn * b->sn */

2148+

result.sn = a->sn * b->sn;

2149+2150+

mpz_move(ctx, ww, &result);

2151+

mpz_clear(ctx, &chunk);

2152+

mpz_clear(ctx, &tmp);

2153+

pool_restore(ctx, pool_state);

2154+

}

2155+20682156

/* w = u * v */

20692157

static void

20702158

mpz_mul(mpz_ctx_t *ctx, mpz_t *ww, mpz_t *u, mpz_t *v)

@@ -2130,15 +2218,23 @@ mpz_mul(mpz_ctx_t *ctx, mpz_t *ww, mpz_t *u, mpz_t *v)

21302218

return;

21312219

}

213222202221+

size_t min_sz = (u->sz < v->sz) ? u->sz : v->sz;

2222+

size_t max_sz = (u->sz > v->sz) ? u->sz : v->sz;

2223+2224+

/*

2225+

* Balance multiplication for highly asymmetric operands.

2226+

* When max >= 2*min and the smaller is above Toom-3 threshold,

2227+

* split the larger operand into chunks for better efficiency.

2228+

*/

2229+

if (max_sz >= 2 * min_sz && should_use_toom3(min_sz)) {

2230+

mpz_mul_balance(ctx, ww, u, v);

2231+

return;

2232+

}

2233+21332234

/*

21342235

* Toom-3 requires both operands to be reasonably similar in size.

2135-

* When operands are highly asymmetric, the algorithm writes beyond

2136-

* the result buffer (e.g., 4*third > x_len + y_len when y_len << x_len).

2137-

* Use schoolbook for asymmetric cases where the smaller operand is

2138-

* below threshold.

2236+

* Use schoolbook for cases where the smaller operand is below threshold.

21392237

*/

2140-

size_t min_sz = (u->sz < v->sz) ? u->sz : v->sz;

2141-

size_t max_sz = (u->sz > v->sz) ? u->sz : v->sz;

21422238

if (!should_use_toom3(min_sz)) {

21432239

mpz_mul_basic(ctx, ww, u, v);

21442240

return;

Read the original on github.com ↗