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>
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>
use the mathematical identity 10^k = 2^k * 5^k to speed up the
divide-and-conquer decimal string conversion. dividing by 5^k
is faster than dividing by 10^k because 5^k has ~30% fewer bits
(log2(5) ≈ 2.32 vs log2(10) ≈ 3.32). the 2^k component is handled
with fast bit shifts.
benchmarks show 3-8% improvement for large numbers:
- 800K bits: 1.00s -> 0.97s
- 1.6M bits: 3.95s -> 3.84s
- 2.4M bits: 8.79s -> 8.46s
Co-authored-by: Claude <noreply@anthropic.com>
Apply Lemire's small table technique: use a 200-byte lookup table to
convert digit pairs (00-99) instead of computing each digit separately.
Reduces operations from 9 to 5 per 9-digit batch in the base case.
Co-authored-by: Claude <noreply@anthropic.com>
Follow the codebase convention of using _recur suffix for recursive
functions (e.g., codedump_recur, dump_recur).
Co-authored-by: Claude <noreply@anthropic.com>
Extract 9 decimal digits at once by dividing by 10^9 instead of 10.
This reduces the number of divisions in the base case by 9x, improving
performance of large bigint to_s conversion by approximately 2x.
Co-authored-by: Claude <noreply@anthropic.com>
For base-10 conversion of numbers with >1000 digits, use a recursive
divide-and-conquer algorithm that splits the number using precomputed
powers of 10. This reduces complexity from O(n^2) to O(n log^2 n).
The algorithm:
1. Precompute 10^1, 10^2, 10^4, 10^8, ... by repeated squaring
2. Find the largest power that splits digits roughly in half
3. Divide by this power to get high and low parts
4. Recursively convert each part, padding low part with zeros
5. Base case: use simple divide-by-10 for <= 1000 digits
Co-authored-by: Claude <noreply@anthropic.com>
The final carry was stored at z->p[y->sz], but when x is larger
than y, this index falls within the already-computed result and
corrupts it. Store at z->p[i] instead, which correctly points to
max(x->sz, y->sz) after all loops complete.
This bug caused incorrect results when adding a small number to
an all-ones number with 1124+ limbs (35968+ bits).
Co-authored-by: Claude <noreply@anthropic.com>
The udiv function had two buggy modifications to Knuth's Algorithm D:
1. A "three-limb pre-adjustment" that only decremented qhat once
2. A "3-limb refinement" loop with incorrect carry handling
These caused incorrect quotients for certain decimal divisions like
10^52 / 10^26. Restored standard Knuth Algorithm D which uses only
2-limb qhat refinement with correction via subtract and add-back.
Co-authored-by: Claude <noreply@anthropic.com>
Numbers with few bits set (popcount <= 8) are multiplied using
shift-add instead of Karatsuba. This is O(k*n) where k is the
popcount, much faster than O(n^1.585) for sparse patterns like
2^100000 + 2^50000 commonly generated by fuzzers.
Co-authored-by: Claude <noreply@anthropic.com>
Add optimized squaring algorithm that exploits symmetry for ~1.5x speedup
over general multiplication. Includes both schoolbook and Karatsuba variants.
- mpz_sqr_basic_limbs: O(n(n+1)/2) multiplications instead of O(n^2)
- mpz_sqr_karatsuba: 3 recursive squarings instead of 3 multiplications
- mpz_sqr: high-level wrapper with fast paths for power-of-2 and all-ones
The optimization triggers when mpz_mul is called with identical pointers
(u == v), which occurs in internal operations like mpz_pow.
Co-authored-by: Claude <noreply@anthropic.com>
Add fast path for multiplying by powers of 2 (2^n). Uses left shift
instead of Karatsuba multiplication: x * 2^n = x << n.
This optimizes "mostly-zero" patterns common in fuzzing tests, where
numbers like 2^2097150 (single bit set) would otherwise trigger slow
Karatsuba multiplication.
Co-authored-by: Claude <noreply@anthropic.com>
Add fast path for multiplying numbers of form 2^n - 1 (all bits set).
Uses algebraic identities:
- (2^n - 1) * (2^m - 1) = 2^(n+m) - 2^n - 2^m + 1
- (2^n - 1) * y = (y << n) - y
These are O(n) operations instead of O(n^1.585) for Karatsuba.
Fuzzing test cases using all-ones patterns now complete in 0.01s
instead of 13+ seconds.
Also raises KARATSUBA_THRESHOLD from 8 to 32 for ~32% speedup
on general large number multiplication.
Co-authored-by: Claude <noreply@anthropic.com>
Reduces recursion overhead for large number multiplication.
Benchmarks show ~32% speedup for million-bit operands.
Co-authored-by: Claude <noreply@anthropic.com>
Add MRB_BIGINT_BIT_LIMIT (1 billion bits / 128MB) to prevent
unreasonably large allocations when left-shifting by huge amounts.
Raises RangeError instead of attempting multi-GB allocations.
Co-authored-by: Claude <noreply@anthropic.com>
mpz_and, mpz_or, and mpz_xor were not calling trim() on their results,
causing inflated size values with trailing zero limbs. This led to
incorrect comparisons in ucmp() and caused udiv() to take wrong code
paths, resulting in memory leaks when exceptions occurred.
Also added defensive overflow checks in mpz_init_heap and udiv.
Co-authored-by: Claude <noreply@anthropic.com>
prevent resource exhaustion when computing power with extremely large
exponents (e.g., 81.pow(51742871469327219)). the check estimates the
result size and raises RangeError if it would exceed 1 million bits.
Co-authored-by: Claude <noreply@anthropic.com>
mpz_div_2exp() was calling mpz_init_heap() on output parameter z
without first freeing z's existing memory. When called from
mpz_barrett_reduce() with pre-allocated temporaries, this caused
memory leaks.
Add mpz_clear(ctx, z) before mpz_init_heap() in both affected code
paths, matching the pattern already used in mpz_mod_2exp().
Fixes ClusterFuzz issue detected with input "8.pow 7*2515881+186,8 ^4>>-509".
Co-authored-by: Claude <noreply@anthropic.com>
Support negative modulus in Integer#pow(exp, mod) with proper Ruby
semantics. Previously, negative modulus caused an infinite loop in
Barrett reduction. Now:
- Use absolute value of modulus for computation
- Apply signed modulo adjustment (result + m for non-zero result
when m is negative)
- Add early return for zero base with positive exponent (0^n = 0)
Co-authored-by: Claude <noreply@anthropic.com>
When right-shifting by more bits than the number contains, the loop
condition `i < x->sz - digs` would underflow (since size_t is unsigned),
causing out-of-bounds memory access.
Fixed by checking if digs >= x->sz upfront and returning zero in that
case, since shifting right by more bits than the number has always
yields zero.
Discovered via ClusterFuzz with input "7<<78<<-772".
Co-authored-by: Claude <noreply@anthropic.com>
Fixed three bugs that caused infinite loops in GCD calculations:
1. mpz_set_int() didn't shrink sz when setting a smaller value.
mpz_realloc() only grows allocations, so setting a 1-limb value
to an mpz_t with sz=3 would leave sz=3, breaking algorithms
that depend on correct sz values.
2. mpz_set_uint64() had the same issue.
3. mpz_gcd() used mpz_init_set() which preserves the sign.
GCD should work with absolute values since gcd(a,b) = gcd(|a|,|b|).
With negative inputs, the sign would oscillate during mod operations,
preventing the Euclidean algorithm from converging.
4. mpz_div_2exp() when e==0 and z==x would corrupt data by calling
mpz_init_heap() which overwrites z->p before copying from x.
These bugs were discovered via ClusterFuzz with complex rational
number calculations.
Co-authored-by: Claude <noreply@anthropic.com>
Fix two functions that could create bigints with sn != 0 but value of 0:
- mpz_mod_limb: single-limb case set r->sn = x->sn even when result was 0
- mpz_mul_2exp: set z->sn = sn unconditionally after zero-producing ops
This inconsistent state caused GCD loop (!zero_p(&b)) to continue with
a zero divisor, eventually causing FPE in mpz_mod_limb with m = 0.
Co-authored-by: Claude <noreply@anthropic.com>
when mpz_mod_2exp() is called with z == x (in-place operation), the
function was calling mpz_clear(ctx, z) which freed x's memory, then
attempting to access x->p[i] - reading freed memory. this caused
Barrett reduction to produce incorrect results in modular
exponentiation.
the fix checks if z == x and handles in-place modification by
adjusting the size and masking directly, without clearing. this is
similar to the memory leak fix for pool→heap transitions.
Co-authored-by: Claude <noreply@anthropic.com>
mpz_mod_2exp() was reinitializing its output parameter without clearing
existing heap memory. When the parameter contained heap allocations from
pool->heap transitions in mpz_mul()->mpz_realloc(), reinitializing would
overwrite the pointer and leak memory. Added mpz_clear() before each
mpz_init() or mpz_init_heap() call to properly free existing heap memory.
Co-authored-by: Claude <noreply@anthropic.com>
the previous fixed safety margin of 8 limbs was insufficient for certain
edge cases involving deep recursion levels in karatsuba multiplication,
as discovered by oss-fuzz. changed to proportional margin (~12.5% plus
fixed overhead of 16) that scales with input size.
this prevents potential buffer overruns in deeply nested karatsuba
multiplications while maintaining efficiency for typical cases.
Co-authored-by: Claude <noreply@anthropic.com>
fix out-of-bounds read when adding bigints of different sizes. the
unrolled loop accessed both operands up to the size of x without
checking if y had enough limbs. when y->sz < x->sz, this caused reads
beyond y's allocation. now use min(x->sz, y->sz) for the overlap
region and handle remaining limbs from the larger operand separately.
Co-authored-by: Claude <noreply@anthropic.com>
mrb_bint_mod() and mrb_bint_rem() were missing conversion of the first
operand x to bigint before calling bint_as_mpz(). this caused crashes
when x was not already a bigint. added mrb_as_bint(mrb, x) calls to
ensure both operands are properly converted.
Co-authored-by: Claude <noreply@anthropic.com>
after left-shifting the divisor in udiv(), trailing zero limbs could
remain, causing division by zero. added trim(&y) after ulshift() to
remove zero limbs, and safety check to handle edge cases where divisor
becomes zero after normalization.
Co-authored-by: Claude <noreply@anthropic.com>
mpz_mod() was calling mpz_init_heap() on its output parameter, assuming it
was uninitialized. However, callers like mpz_powm_i() pass already-
initialized variables, causing the old allocations to leak. Changed to use
mpz_realloc() which properly handles both cases.
Co-authored-by: Claude <noreply@anthropic.com>
prevents buffer overrun in karatsuba multiplication scratch space due to
rounding errors in recursive partitioning. empirically determined 8-limb
margin fixes valgrind-detected overrun with large exponentiations.
Co-authored-by: Claude <noreply@anthropic.com>
refactored the stack-use-after-return fix to encapsulate pool memory
handling in mpz_move instead of bint_set, providing cleaner code and
automatic protection for all 22 callers of mpz_move; ref #6651
Co-authored-by: Claude <noreply@anthropic.com>
The fix is to modify `bint_set` to ensure that the data stored in the persistent `RBigint` object is allocated on the heap if it's not embedded. We check if the source `mpz_t` uses memory from the stack pool using `is_pool_memory`. If it does, we must perform a deep copy (`mpz_set`) to allocate new heap memory and copy the data, instead of moving the pointer (`mpz_move`). If the source is already on the heap, we retain the efficient `mpz_move`.
OSS-Fuzz testcase: https://oss-fuzz.com/testcase-detail/5279371075321856
add explicit cast when assigning mrb_int to mp_limb. the value is
already validated to fit within mp_limb range by checking against
DIG_BASE, but explicit cast silences msvc warning c4244.
Co-authored-by: Claude <noreply@anthropic.com>
mrb_bint_copy was creating reference to destination then destroying it
with mpz_init, causing copy to happen in orphaned memory. this made
clone return 0 instead of copying the bigint value.
fix extracts common mpz_t-to-rbigint transfer logic into bint_set
helper, used by both bint_new and mrb_bint_copy. eliminates code
duplication and properly copies source data to destination rbigint
structure, handling both embedded and heap storage cases.
Co-authored-by: Claude <noreply@anthropic.com>
when xoring bigint with small integer, the fast path assumes source
bigint has allocated limbs. malformed bigints with sn > 0 but sz == 0
caused null pointer access. add defensive check to allocate storage
before accessing c.p[0].
Co-authored-by: Claude <noreply@anthropic.com>
Fix mrb_bint_new_str to normalize bigint objects to regular integers
when possible. This ensures consistent object types for values that
fit in mrb_int range, fixing comparison failures in tests.
Co-authored-by: Claude <noreply@anthropic.com>
The MSVC _umul128 code path was designed for 64-bit limbs but mruby's
bigint implementation uses 32-bit limbs even on 64-bit builds. This
fundamental mismatch caused incorrect bigint calculations on VC 64-bit
builds, producing results like "100000000000000000000" -> "1661992960".
Removed the MSVC optimization to fall back to the portable double-limb
arithmetic which correctly handles 32-bit limbs.
Co-authored-by: Claude <noreply@anthropic.com>
The MSVC-specific _umul128 code path had incorrect carry propagation
when adding three values (rp[i] + lo + carry). The original code:
carry = hi + (sum < lo);
only detected overflow between sum and lo, missing overflow in the
first addition rp[i] + lo. This caused incorrect bigint calculations
on VC 64-bit builds.
Fixed by splitting three-way addition into two two-way additions
with proper overflow detection for each step:
temp = rp_val + lo;
sum = temp + carry;
carry = hi + (temp < rp_val) + (sum < temp);
Co-authored-by: Claude <noreply@anthropic.com>
Replaced mathematical symbols in comments with ASCII equivalents:
- multiplication sign to *
- Greek mu to mu
- approximately equal to ~
- subscript 2 to 2
- less than or equal to <=
This complies with the coding standard to use English and ASCII
characters in all code comments and documentation.
Co-authored-by: Claude <noreply@anthropic.com>
Cast base parameter to uint64_t in mpz_get_str power-of-2 path to
resolve C4018 warning about signed/unsigned mismatch. The comparison
now properly compares two unsigned values: ((uint64_t)1 << shift)
with (uint64_t)base.
Co-authored-by: Claude <noreply@anthropic.com>
Fix C4334 and C4244 warnings that caused test failures on Windows VS 2022:
- Use uint64_t for shift operation to avoid undefined behavior
- Add explicit mp_limb casts for type conversions
Co-authored-by: Claude <noreply@anthropic.com>
When creating a bigint with embedded storage, the array wasn't being
initialized when x->p was NULL but x->sz > 0. This could leave garbage
memory in the embedded array, which VS 2022 might interpret differently
than VS 2019, causing test failures.
This fix ensures the embedded array is always properly initialized with
zeros when x->p is NULL, preventing potential undefined behavior.
Co-authored-by: Claude <noreply@anthropic.com>