Function was only called once and contained just 2 lines of code.
Inlining directly reduces code size and improves clarity.
Co-authored-by: Claude <noreply@anthropic.com>
Add CPython-style parsing for base-10 string to integer conversion:
- Parse 9 digits at a time into decimal-base array
- Convert decimal-base to binary in single pass
- Use memory pool for temporary decimal buffer
- Use realloc for result buffer to reduce allocations
Also add digit_pairs lookup table for faster to_s output.
Performance: 2-5x faster for to_i, 60% fewer allocations.
Co-authored-by: Claude <noreply@anthropic.com>
This improves to_s performance for medium-sized bigints (40-50 limbs,
~800-1000 digits) by approximately 5x by enabling the divide-and-conquer
algorithm earlier.
Benchmark results:
40 limbs (772 digits): 88 us -> 18 us (5x faster)
50 limbs (964 digits): 134 us -> 25 us (5.4x faster)
Co-authored-by: Claude <noreply@anthropic.com>
Benchmarks show the previous threshold of 50 was too low, causing
Toom-3's setup overhead to outweigh its asymptotic benefits for
medium-sized numbers. Raising to 100 limbs provides:
- 2x faster at 300 limbs (192 -> 96 us)
- 2.5x faster at 120 limbs (42 -> 17 us)
- 2.6x faster at 80 limbs (31 -> 12 us)
Co-authored-by: Claude <noreply@anthropic.com>
When multiplying numbers where one is significantly larger than the other
(at least 2x size difference), split the larger number into chunks matching
the smaller number's size, multiply each chunk, and combine results. This
avoids pathological performance when Toom-3 pads asymmetric operands with
zeros.
Benchmarks show 6-16x speedup for size ratios from 10:1 to 40:1, with no
regression for symmetric cases.
Co-authored-by: Claude <noreply@anthropic.com>
mpz_init_heap() already allocates the requested size, so immediately
calling mpz_realloc() with the same size is a no-op. Remove these
redundant calls from mpz_and, mpz_or, mpz_xor, mpz_mod_2exp, and
mpz_abs.
Co-authored-by: Claude <noreply@anthropic.com>
When the output and input are the same variable, avoid unnecessary
heap allocations by modifying in place:
- mpz_neg: just flip the sign
- mpz_abs: just make sign positive
- ulshift: use mpn_lshift in-place (safe since it processes high-to-low)
Co-authored-by: Claude <noreply@anthropic.com>
Add mpz_sqr_toom3() that performs Toom-3 squaring with reduced memory
and computation:
- Only evaluates x (not y), reducing evaluation buffer from 6 to 3
- Uses recursive squaring instead of multiplication for all 5 products
- Simplifies interpolation since squared values are always positive
The specialized squaring uses the same Toom-3 structure but avoids
redundant computation when both operands are the same number.
Co-authored-by: Claude <noreply@anthropic.com>
The mpn_divexact_3 function used ap[i] in the borrow computation after
writing to rp[i]. When rp == ap (in-place operation), this read the
modified value instead of the original input, causing incorrect borrow
propagation.
This bug caused Toom-3 multiplication to produce wrong results for
certain input patterns where t6 - t5 had non-zero values followed by
zeros. The corrupted r3 coefficient then propagated errors to the final
result.
Fix by saving the original ap[i] value before writing rp[i].
Co-authored-by: Claude <noreply@anthropic.com>
move in-place optimization directly into mpz_sub() so callers
just use mpz_sub(ctx, x, x, y) and get automatic optimization.
remove separate mpz_sub_inplace() function.
Co-authored-by: Claude <noreply@anthropic.com>
when destination equals source in mpz_div_2exp(), use memmove
and mpn_rshift in-place instead of allocating a temporary.
reduces sqrt allocations by 49% since Newton iteration uses
in-place division by 2 on each iteration.
Co-authored-by: Claude <noreply@anthropic.com>
add usub_inplace() and mpz_sub_inplace() for allocation-free
subtraction when the minuend is larger than the subtrahend.
apply to Mersenne multiplication which reduces allocations by
17% and improves performance by 7-9% for small numbers.
Co-authored-by: Claude <noreply@anthropic.com>
skip two's complement conversion in mpz_and, mpz_or, mpz_xor when both
operands are positive. this avoids the per-limb make_2comp overhead and
provides up to 1.6x speedup for large bigints.
Co-authored-by: Claude <noreply@anthropic.com>
convert mpz_init_heap to mpz_init_temp for temporary quotient and
remainder variables in div_limb. these variables are now allocated
from the memory pool when possible, reducing heap allocation overhead.
the div_limb function already uses pool_save/pool_restore, so these
temporary variables are proper candidates for pool allocation.
Co-authored-by: Claude <noreply@anthropic.com>
when the allocator can extend the block in place, realloc avoids
the overhead of malloc+memcpy+free. the keys are moved to their
new position with memmove and extended regions are cleared.
Co-authored-by: Claude <noreply@anthropic.com>
saves 40% memory (60 -> 36 bytes) for objects with 1-2 instance
variables, which is common for simple value objects like Point(@x, @y).
the trade-off is one extra reallocation when growing from 2 to 4 IVs,
but this is negligible since reallocations are rare compared to lookups.
Co-authored-by: Claude <noreply@anthropic.com>
Replace Karatsuba multiplication (O(n^1.585)) with Toom-3 (O(n^1.465))
for large number multiplication. Toom-3 splits numbers into thirds and
evaluates at 5 points, providing better asymptotic performance.
Threshold is 50 limbs (~1600 bits). For operands below threshold or
highly asymmetric sizes, schoolbook multiplication is used.
Co-authored-by: Claude <noreply@anthropic.com>
Remove automatic downgrade to 16-bit limbs on 32-bit Windows.
Modern compilers (including MSVC) have supported uint64_t for decades.
MRB_NO_MPZ64BIT remains available for constrained platforms.
Adjust BATCH_DIVISOR and BATCH_DIGITS for 16-bit limb compatibility:
- 32-bit limbs: 10^9 (9 digits per batch)
- 16-bit limbs: 10^4 (4 digits per batch)
Co-authored-by: Claude <noreply@anthropic.com>
Add trim() after mpz_set in early return paths to prevent propagation
of inflated sz values. When an mpz_t has sz larger than actual allocated
limbs, copying it without trim causes subsequent operations to read
beyond allocated memory.
Fixed functions:
- mpz_add: when one operand is zero
- mpz_neg: when copying operand
- mpz_mod_2exp: when x < 2^e
Co-authored-by: Claude <noreply@anthropic.com>
Document that this header is for mruby core internal use only and
should not be included in user code or mrbgems. When MRB_USE_CXX_EXCEPTION
is defined, C source files including this header fail to compile.
Add example showing mrb_protect_error() as the recommended alternative.
Co-authored-by: Claude <noreply@anthropic.com>
Use mrb_protect_error API instead of direct MRB_TRY/MRB_CATCH to handle
exceptions in mpz_mul_all_ones and mpz_to_s_dc. This maintains C++
compatibility (issue #6702) while ensuring temporary mpz_t allocations
are properly freed even when exceptions occur.
Co-authored-by: Claude <noreply@anthropic.com>
Remove MRB_TRY/MRB_CATCH exception handling from bigint.c to fix
compilation errors when using mruby-bigint in C++ projects with
MRB_USE_CXX_EXCEPTION enabled.
The exception handling was added for cleanup on error, but it requires
throw.h which doesn't work when a C file is compiled in a C++ context
with C++ exceptions enabled. Accepting potential memory leaks on
exception (rare) is preferable to breaking C++ builds.
Fixes#6702
Co-authored-by: Claude <noreply@anthropic.com>
The JMPNOT-to-JMPIF optimization assumed fail_pos always came from a
4-byte JMPNOT instruction. When a pinned variable is undefined,
NODE_PAT_PIN generates a 3-byte OP_JMP instead, causing fail_pos - 2
to point into the previous instruction and corrupt its operand.
Add a check to verify the instruction at fail_pos - 2 is actually
OP_JMPNOT before modifying it.
Fixes#6701
Co-authored-by: Claude <noreply@anthropic.com>
Replace the two-buffer swap pattern in D&C to_s base case with
in-place division using new mpn_div10_9 function. This eliminates
the q_base scratch buffer and reduces per-iteration overhead.
Compilers optimize the constant division by 10^9 to multiplication
and shift operations for better performance.
Co-authored-by: Claude <noreply@anthropic.com>
Replace per-call lo allocation with depth-indexed lo_stack buffers
that are reused across recursion levels. Each buffer is allocated
on first use at that depth with appropriate size.
This reduces 493 malloc/free calls (5%) and 580KB of memory (2%)
for large number to_s conversions while maintaining performance.
Co-authored-by: Claude <noreply@anthropic.com>
Instead of allocating a separate hi buffer for the upper part of the
split, reuse q5 by shifting it in place after extracting the lower
bits to q5_low.
This eliminates one mpz_t allocation per recursive call:
- Extract q5_low = q5 mod 2^k first (copy lower bits)
- Shift q5 right in place (memmove + mpn_rshift) to get hi
- q5 now serves as hi for the recursive call
Benchmark results (2.4M bit number):
- Memory: -3.5% (5.58MB -> 5.38MB peak heap)
- Instructions: -16.5%
- Speed: unchanged (within measurement noise)
Co-authored-by: Claude <noreply@anthropic.com>
Replace array indexing x.p[i+j] with pointer arithmetic *xp++ in the
hot inner loop of Knuth Algorithm D division. This avoids recalculating
the index i+j on every iteration.
Profiling showed the inner loop accounts for ~80% of udiv execution time,
with the array indexing contributing significant overhead.
Benchmark improvement: ~4% faster (7.80s -> 7.48s for 2.4M bit to_s).
Co-authored-by: Claude <noreply@anthropic.com>
When converting large numbers to strings using D&C algorithm, the
base case extracts digits in batches of 9 (for 32-bit limbs). The
extraction logic: 1 leading digit + 4 pairs (8 digits) = 9 digits.
The pair extraction loop condition `pos >= 2` exits when pos < 2,
but when the remaining batch still has value and pos == 1, that
final digit was being lost and replaced with '0' by the padding loop.
This caused roundtrip failures (x.to_s.to_i != x) for numbers just
above the D&C threshold (1000 digits), where the split boundary
produced a lo part requiring exactly the right number of digits to
trigger this edge case.
Co-authored-by: Claude <noreply@anthropic.com>
mpz_to_s_dc_recur fills in exactly num_digits characters but did not
add a null terminator. This caused valgrind errors when strlen was
called on the resulting string.
Co-authored-by: Claude <noreply@anthropic.com>
Add mpn_add_n, mpn_add, mpn_add_1, mpn_sub_n, mpn_sub, and mpn_sub_1
functions that operate directly on limb arrays, following GMP's mpn
layer design. These functions support in-place operation and return
carry/borrow.
Refactor uadd and usub to use these new mpn functions, simplifying the
code significantly (134 lines deleted, replaced with cleaner mpn calls).
Also update limb_sub to delegate to mpn_sub_n.
Co-authored-by: Claude <noreply@anthropic.com>
Add mpn_rshift and mpn_lshift functions that operate directly on limb
arrays, following GMP's mpn layer design. These functions support
in-place operation and return shifted-out bits.
Refactor urshift and ulshift to use these new mpn functions, simplifying
the code and improving reusability.
Co-authored-by: Claude <noreply@anthropic.com>
Use scratch buffers for q5, r5, and q5_low in the recursive case of
D&C decimal string conversion. These temporaries are only needed
during the computation of hi and lo values, not during the recursive
calls, so they can be safely reused at each recursion level.
This eliminates 3 allocations per recursion level (approximately
log2(digits/1000) levels for large numbers), providing an additional
2-3% performance improvement on top of the base case optimization.
Co-authored-by: Claude <noreply@anthropic.com>
Add dc_to_s_scratch_t structure to preallocate work buffers for the
base case of divide-and-conquer decimal string conversion. This
eliminates repeated malloc/free calls in the inner loop where digits
are extracted 9 at a time.
Previously, each iteration of the base case loop allocated and freed
a quotient buffer. Now the same two buffers are reused with pointer
swapping, reducing allocation overhead by ~10-18% for large numbers.
Co-authored-by: Claude <noreply@anthropic.com>
When mpz_or or mpz_xor copies an operand when the other is zero,
the copied mpz_t may have an inflated sz field (larger than actual
allocated limbs). Add trim() after mpz_set to normalize the size.
This is a follow-up fix to commit 61aa2234d8 which addressed the
same issue in shift operations.
Co-authored-by: Claude <noreply@anthropic.com>
Add trim() calls after mpz_set/mpz_move in shift operations where
actual bit manipulation is skipped:
- mpz_mul_2exp when e==0 (no shift needed)
- mpz_mul_2exp when bs==0 (limb-only shift)
- mpz_div_2exp when e==0 (no shift needed)
- mpz_div_2exp when bs==0 (limb-only shift)
This prevents inflated sz values from propagating through operations,
complementing the earlier fix to urshift/ulshift when n==0.
Co-authored-by: Claude <noreply@anthropic.com>
When shift amount is 0, urshift() and ulshift() called mpz_set() which
copies data without trimming leading zero limbs. This caused bigint
values to have inflated sz fields, making ucmp() comparisons incorrect.
For example, a 256-bit remainder from division could have sz=18 instead
of sz=8 because the divisor had 18 limbs. This made it compare greater
than values with fewer limbs, even when numerically smaller.
The bug also caused memory leaks when the incorrect comparison led to
taking wrong code paths in division, triggering size overflow exceptions
after memory was allocated.
Co-authored-by: Claude <noreply@anthropic.com>
rational_eq_b was using wrong struct fields (p1->numerator/denominator
which access i.num/i.den) for bigint-backed rationals that use b.num/b.den.
Also added missing MRB_TT_BIGINT case to prevent fallthrough to default
case which caused ping-pong recursion between Rational#== and Integer#==.
Co-authored-by: Claude <noreply@anthropic.com>
Add MRB_TRY/MRB_CATCH to ensure local mpz_t variables are freed when
an exception (e.g., RangeError from shift overflow) occurs during
the all-ones multiplication optimization.
Co-authored-by: Claude <noreply@anthropic.com>
Use stack allocation with zero-initialization and MRB_TRY/MRB_CATCH
to ensure heap-allocated mpz_t data is freed even when an exception
occurs during conversion.
Co-authored-by: Claude <noreply@anthropic.com>