@@ -83,6 +83,7 @@ static void mpz_mul_2exp(mpz_ctx_t *ctx, mpz_t *z, mpz_t *x, mrb_int e);
8383static void mpz_div_2exp(mpz_ctx_t *ctx, mpz_t *z, mpz_t *x, mrb_int e);
8484static void mpz_mod_2exp(mpz_ctx_t *ctx, mpz_t *z, mpz_t *x, mrb_int e);
8585static 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);
86878788static mp_limb*
8889pool_alloc(mpz_pool_t *pool, size_t limbs)
@@ -2065,6 +2066,93 @@ mpz_sqr(mpz_ctx_t *ctx, mpz_t *ww, mpz_t *u)
20652066trim(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 */
20692157static void
20702158mpz_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)
21302218return;
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;
21422238if (!should_use_toom3(min_sz)) {
21432239mpz_mul_basic(ctx, ww, u, v);
21442240return;