@@ -2390,6 +2390,155 @@ static const mp_limb base_limit[34*2] = {
23902390#endif
23912391};
239223922393+/*
2394+ * Divide-and-conquer decimal string conversion.
2395+ * For numbers with > DC_TO_S_THRESHOLD digits, this is O(n log^2 n)
2396+ * instead of O(n^2) for the simple algorithm.
2397+ */
2398+#define DC_TO_S_THRESHOLD 1000
2399+2400+/*
2401+ * Recursive D&C conversion helper.
2402+ * Converts x to decimal string, writing exactly num_digits characters.
2403+ * The caller must ensure num_digits >= actual digits in x.
2404+ * Leading zeros are added if x has fewer digits than num_digits.
2405+ */
2406+static void
2407+mpz_to_s_dc_rec(mpz_ctx_t *ctx, char *s, mpz_t *x, size_t num_digits,
2408+mpz_t *powers, size_t num_powers)
2409+{
2410+/* Base case: use simple conversion for small numbers */
2411+if (num_digits <= DC_TO_S_THRESHOLD || num_powers == 0) {
2412+/* Convert to string in reverse order first */
2413+size_t pos = num_digits;
2414+mpz_t tmp;
2415+mpz_init_set(ctx, &tmp, x);
2416+2417+while (pos > 0 && !zero_p(&tmp)) {
2418+mpz_t q;
2419+mpz_init_heap(ctx, &q, tmp.sz);
2420+mp_dbl_limb r = 0;
2421+2422+/* Divide by 10 */
2423+for (size_t i = tmp.sz; i > 0; i--) {
2424+r = (r << DIG_SIZE) | tmp.p[i-1];
2425+q.p[i-1] = (mp_limb)(r / 10);
2426+r %= 10;
2427+ }
2428+q.sz = tmp.sz;
2429+q.sn = tmp.sn;
2430+trim(&q);
2431+2432+s[--pos] = '0' + (char)r;
2433+mpz_set(ctx, &tmp, &q);
2434+mpz_clear(ctx, &q);
2435+ }
2436+2437+/* Fill remaining positions with zeros */
2438+while (pos > 0) {
2439+s[--pos] = '0';
2440+ }
2441+2442+mpz_clear(ctx, &tmp);
2443+return;
2444+ }
2445+2446+/* Find appropriate power of 10 to split on */
2447+/* We want the largest power that gives roughly half the digits */
2448+size_t split_idx = 0;
2449+size_t split_digits = 1;
2450+for (size_t i = 0; i < num_powers; i++) {
2451+size_t d = (size_t)1 << i; /* digits for this power */
2452+if (d * 2 <= num_digits) {
2453+split_idx = i;
2454+split_digits = d;
2455+ }
2456+ }
2457+2458+/* Split: x = hi * 10^split_digits + lo */
2459+mpz_t hi, lo;
2460+mpz_init(ctx, &hi);
2461+mpz_init(ctx, &lo);
2462+2463+mpz_mdivmod(ctx, &hi, &lo, x, &powers[split_idx]);
2464+lo.sn = (lo.sn < 0) ? -lo.sn : lo.sn; /* lo is always positive */
2465+2466+/* Recursively convert high part */
2467+size_t hi_digits = num_digits - split_digits;
2468+mpz_to_s_dc_rec(ctx, s, &hi, hi_digits, powers, split_idx);
2469+2470+/* Recursively convert low part (exactly split_digits digits with padding) */
2471+mpz_to_s_dc_rec(ctx, s + hi_digits, &lo, split_digits, powers, split_idx);
2472+2473+mpz_clear(ctx, &hi);
2474+mpz_clear(ctx, &lo);
2475+}
2476+2477+/*
2478+ * D&C decimal string conversion entry point.
2479+ * Returns pointer to start of string (after optional sign).
2480+ */
2481+static char*
2482+mpz_to_s_dc(mpz_ctx_t *ctx, char *s, mpz_t *x)
2483+{
2484+mrb_state *mrb = MPZ_MRB(ctx);
2485+2486+/* Handle sign */
2487+char *result = s;
2488+if (x->sn < 0) {
2489+*s++ = '-';
2490+ }
2491+2492+/* Calculate number of decimal digits needed */
2493+/* Use log10(2) ≈ 0.30103, so bits * 0.30103 + 1 gives upper bound */
2494+size_t bits = digits(x) * DIG_SIZE;
2495+size_t num_digits = (size_t)(bits * 30103UL / 100000UL) + 2;
2496+2497+/* Build table of powers of 10: 10^1, 10^2, 10^4, 10^8, ... */
2498+size_t max_powers = 32; /* Enough for 10^(2^32) which is huge */
2499+mpz_t *powers = (mpz_t*)mrb_malloc(mrb, max_powers * sizeof(mpz_t));
2500+size_t num_powers = 0;
2501+2502+/* 10^1 */
2503+mpz_init(ctx, &powers[0]);
2504+mpz_set_int(ctx, &powers[0], 10);
2505+num_powers = 1;
2506+2507+/* Build powers by squaring: 10^(2^k) = (10^(2^(k-1)))^2 */
2508+while (num_powers < max_powers) {
2509+size_t power_digits = (size_t)1 << num_powers;
2510+if (power_digits > num_digits) break;
2511+2512+mpz_init(ctx, &powers[num_powers]);
2513+mpz_sqr(ctx, &powers[num_powers], &powers[num_powers - 1]);
2514+num_powers++;
2515+ }
2516+2517+/* Make a copy of x for conversion (to preserve original) */
2518+mpz_t tmp;
2519+mpz_init_set(ctx, &tmp, x);
2520+tmp.sn = 1; /* Work with absolute value */
2521+2522+/* Do the recursive conversion */
2523+mpz_to_s_dc_rec(ctx, s, &tmp, num_digits, powers, num_powers);
2524+2525+/* Clean up powers table */
2526+for (size_t i = 0; i < num_powers; i++) {
2527+mpz_clear(ctx, &powers[i]);
2528+ }
2529+mrb_free(mrb, powers);
2530+mpz_clear(ctx, &tmp);
2531+2532+/* Remove leading zeros (but keep at least one digit) */
2533+char *p = s;
2534+while (*p == '0' && *(p+1) != '\0') p++;
2535+if (p > s) {
2536+memmove(s, p, strlen(p) + 1);
2537+ }
2538+2539+return result;
2540+}
2541+23932542static char*
23942543mpz_get_str(mpz_ctx_t *ctx, char *s, mrb_int sz, mrb_int base, mpz_t *x)
23952544{
@@ -2443,6 +2592,12 @@ mpz_get_str(mpz_ctx_t *ctx, char *s, mrb_int sz, mrb_int base, mpz_t *x)
24432592mrb_raise(mrb, E_RUNTIME_ERROR, "bigint size too large for string conversion");
24442593 }
244525942595+/* Use D&C algorithm for large base-10 numbers */
2596+size_t est_digits = (size_t)(xlen * DIG_SIZE * 30103UL / 100000UL) + 2;
2597+if (base == 10 && est_digits > DC_TO_S_THRESHOLD) {
2598+return mpz_to_s_dc(ctx, s, x);
2599+ }
2600+24462601mp_limb *t = (mp_limb*)mrb_malloc(mrb, xlen * sizeof(mp_limb));
2447260224482603mp_limb *tend = t + xlen;