// Copyright (C) 2026 Kiyotsugu Arai // SPDX-License-Identifier: LGPL-3.0-or-later // IntDivision.hpp // Division algorithms for multi-precision integers // // This file provides division algorithms for the multi-precision integer // library. The implementation is based on Knuth's division algorithm // (Algorithm D). // // Main features: // - Knuth's division algorithm // - Efficient quotient and remainder computation // - Special-case optimizations // - Integration with state management #ifndef SANGI_INT_DIVISION_HPP #define SANGI_INT_DIVISION_HPP #include #include #include #include #include #include #include #include namespace sangi { /** * @brief Selection of division algorithm * * An enum used to switch between different division algorithms in tests * and benchmarks. The algorithm chosen via the global variable * g_division_algorithm is used at runtime. */ enum class DivisionAlgorithm { Auto, // Automatic selection (chooses the optimal algorithm based on size) BitByBit, // Bit-by-bit long division (O(n^2 * w), reliable but slow) SingleWordOnly, // Only optimize the single-word divisor case (otherwise BitByBit) Knuth, // Knuth Algorithm D (O(n^2), fast and accurate) Newton, // Newton's method based division (reserved for future extension) BurnikelZiegler, // Burnikel-Ziegler method (reserved for future extension) PowerOfTwo, // Power-of-two specialized (right shift) PowerOfTen // Power-of-ten specialized (internally uses Knuth) }; /** * @brief Global division algorithm setting * * The default is Auto (automatic selection). * By changing this variable in tests or benchmarks, the division * algorithm in use can be switched. * * Example: * sangi::g_division_algorithm = DivisionAlgorithm::Knuth; * Int result = a / b; // Uses Knuth Algorithm D * * sangi::g_division_algorithm = DivisionAlgorithm::BitByBit; * Int result2 = a / b; // Uses bit-by-bit division */ inline DivisionAlgorithm g_division_algorithm = DivisionAlgorithm::Auto; /** * @brief Class providing division algorithms for multi-precision integers */ class IntDivision { public: // ================================================================ // Exact division (fast division assuming divisibility) // Used in Toom-Cook interpolation, etc. Complexity O(n). // ================================================================ /** * @brief Exact division by 3 (assumes divisibility by 3) * * Uses a Montgomery inverse: 3^(-1) mod 2^64 = 0xAAAAAAAAAAAAAAAB * Complexity: O(n) -- an alternative to general O(n^2) division * * @param x Dividend (must be divisible by 3) * @return x / 3 */ static Int divexactBy3(const Int& x) { if (x.isZero()) return Int(0); if (x.isSpecialState()) return x; // Save the sign and operate on the absolute value int result_sign = x.getSign(); const auto& words = x.words(); size_t n = words.size(); // Montgomery inverse: 3 * INV3 == 1 (mod 2^64) static constexpr uint64_t INV3 = 0xAAAAAAAAAAAAAAABULL; std::vector out(n); uint64_t carry = 0; for (size_t i = 0; i < n; i++) { // x[i] - carry (with borrow detection) uint64_t xi = words[i]; uint64_t xi_minus_carry = xi - carry; uint64_t borrow = (xi < carry) ? 1 : 0; // y[i] = (x[i] - carry) * INV3 mod 2^64 uint64_t yi = xi_minus_carry * INV3; out[i] = yi; // carry = (yi * 3).high + borrow // Note: since 3*yi == xi_minus_carry (mod 2^64), prod.low == xi_minus_carry UInt128 prod = UInt128::multiply(yi, 3); carry = prod.high + borrow; } // Construct the result Int result; result.m_words.assign(out.data(), out.data() + out.size()); result.setSign(result_sign); result.normalize(); return result; } /** * @brief Exact division by 24 (assumes divisibility by 24) * * Decomposition: x/24 = (x >> 3) / 3 -- shift + divexactBy3 * Complexity: O(n) * * @param x Dividend (must be divisible by 24) * @return x / 24 */ static Int divexactBy24(const Int& x) { if (x.isZero()) return Int(0); if (x.isSpecialState()) return x; // Save the sign and operate on the absolute value int original_sign = x.getSign(); const auto& words = x.words(); size_t n = words.size(); // Phase 1: right shift by 3 bits (exact division by 8) std::vector shifted(n); for (size_t i = 0; i < n - 1; i++) { shifted[i] = (words[i] >> 3) | (words[i + 1] << 61); } shifted[n - 1] = words[n - 1] >> 3; // Convert the shifted result into an Int Int shifted_int; shifted_int.m_words.assign(shifted.data(), shifted.data() + shifted.size()); shifted_int.setSign(original_sign); shifted_int.normalize(); // Phase 2: divexact by 3 return divexactBy3(shifted_int); } // ================================================================ // Knuth Algorithm D // ================================================================ /** * @brief Knuth's division algorithm * Reference: D. E. Knuth, "The Art of Computer Programming", Volume 2, 4.3.1 * * @param dividend Dividend * @param divisor Divisor * @param remainder Output location for the remainder * @return Quotient */ static Int divKnuth(const Int& dividend, const Int& divisor, Int& remainder) { // Handle special states if (divisor.isZero()) { // Division by zero handling if (dividend.isZero()) { // 0/0 is NaN remainder = Int::fromState(NumericState::NaN, NumericError::DivideByZero); return Int::fromState(NumericState::NaN, NumericError::DivideByZero); } else { // x/0 (x != 0) is also NaN (no integer quotient is defined) remainder = Int::fromState(NumericState::NaN, NumericError::DivideByZero); return Int::fromState(NumericState::NaN, NumericError::DivideByZero); } } if (dividend.isZero()) { remainder = Int(0); return Int(0); } // Handle NaN, infinities, and similar special states if (dividend.getState() != NumericState::Normal || divisor.getState() != NumericState::Normal) { return IntSpecialStates::handleDivision(dividend, divisor); } // Special handling for divisors with absolute value 1 if (abs(divisor).isOne()) { remainder = Int(0); Int q = abs(dividend); if (dividend.getSign() != divisor.getSign() && !q.isZero()) { q.negate(); } return q; } // Sign handling bool quotient_negative = dividend.getSign() != divisor.getSign(); bool remainder_negative = dividend.getSign() < 0; Int abs_dividend = abs(dividend); Int abs_divisor = abs(divisor); // If the dividend is smaller than the divisor if (abs_dividend < abs_divisor) { remainder = dividend; // The remainder is the dividend itself return Int(0); } // For small values, compute directly if (abs_dividend.size() == 1 && abs_divisor.size() == 1) { uint64_t a = abs_dividend.word(0); uint64_t b = abs_divisor.word(0); uint64_t q = a / b; uint64_t r = a % b; // Build the result remainder = Int(static_cast(r)); if (remainder_negative && r != 0) { remainder.negate(); } Int quotient(static_cast(q)); if (quotient_negative && q != 0) { quotient.negate(); } return quotient; } // From here on, Knuth's division algorithm // Prepare input data (little-endian layout) std::vector U = abs_dividend.words(); // Dividend std::vector V = abs_divisor.words(); // Divisor // Normalization - adjust top digit so divisor[n-1] >= 2^32 int normalization_shift = 0; if (!V.empty() && V.back() != 0) { // Normalization shift amount: shift until the MSB is set // Knuth Algorithm D requires the MSB of the top divisor word to be set normalization_shift = static_cast(std::countl_zero(V.back())); } if (normalization_shift > 0) { // Normalize both dividend and divisor (left shift) Int normalized_dividend = abs_dividend << normalization_shift; Int normalized_divisor = abs_divisor << normalization_shift; U = normalized_dividend.words(); V = normalized_divisor.words(); } // Compute digit counts size_t m = U.size(); size_t n = V.size(); // Allocate storage for the quotient // Maximum quotient length is dividend length - divisor length + 1 std::vector Q; if (m >= n) { Q.resize(m - n + 1, 0); } else { Q.resize(1, 0); } // Working array (copy of U) std::vector U_work = U; // Append 0 to the top to prevent overflow U_work.push_back(0); // Implementation of Knuth's Algorithm D (multi-precision integer division) for (int j = static_cast(m - n); j >= 0; j--) { // Bounds checks size_t idx_high = j + n; size_t idx_mid = j + n - 1; if (idx_high >= U_work.size() || idx_mid >= U_work.size()) { throw std::out_of_range("U_work index out of bounds"); } // Compute the trial quotient estimate uint64_t dividend_high = U_work[j + n]; uint64_t dividend_mid = U_work[j + n - 1]; uint64_t divisor_high = V[n - 1]; // Trial quotient computation uint64_t q_hat; uint64_t r_hat; bool skip_correction = false; // Improved single-word division if (dividend_high == 0) { // Easy case - ordinary single-word division q_hat = dividend_mid / divisor_high; r_hat = dividend_mid % divisor_high; } else if (dividend_high >= divisor_high) { // Trial quotient saturates at the maximum value (B-1) q_hat = UINT64_MAX; // Correct r_hat computation: // r_hat = (dividend_high * B + dividend_mid) - (B-1) * divisor_high // = (dividend_high - divisor_high) * B + dividend_mid + divisor_high // // When dividend_high > divisor_high: r_hat >= B -> does not fit in 64 bits // -> trial quotient correction is unnecessary // (r_hat >= B implies q_hat*v2 < r_hat*B always holds) if (dividend_high > divisor_high) { // r_hat >= B -> skip the correction loop r_hat = 0; skip_correction = true; } else { // dividend_high == divisor_high // r_hat = dividend_mid + divisor_high r_hat = dividend_mid + divisor_high; // If r_hat overflowed (r_hat >= B), correction is also unnecessary if (r_hat < dividend_mid) { skip_correction = true; } } } else { // Two-word / one-word division // divmod_fast correctly handles the overflow flag // (UInt128::divide has a 64-bit overflow bug in its intermediate variable r) auto div_result = UInt128::divmod_fast(dividend_high, dividend_mid, divisor_high); q_hat = div_result.first; r_hat = div_result.second; } // Trial quotient correction (when a second word is present) // skip_correction: no correction needed when r_hat >= B (does not fit in 64 bits) if (n >= 2 && !skip_correction) { uint64_t divisor_next = V[n - 2]; uint64_t dividend_low = j + n - 2 < U_work.size() ? U_work[j + n - 2] : 0; // Correction loop bool need_adjustment = true; int adjustment_count = 0; while (need_adjustment) { // Check q_hat * divisor_next > r_hat * 2^64 + dividend_low UInt128 qd_product = UInt128::multiply(q_hat, divisor_next); UInt128 r_dividend(r_hat, dividend_low); if (qd_product.high < r_dividend.high || (qd_product.high == r_dividend.high && qd_product.low <= r_dividend.low)) { need_adjustment = false; } else { q_hat--; uint64_t old_r_hat = r_hat; r_hat += divisor_high; // If r_hat overflowed, stop the loop if (r_hat < old_r_hat) { need_adjustment = false; } adjustment_count++; if (adjustment_count > 2) { need_adjustment = false; } } } } // Subtract q_hat * divisor from the dividend (single-pass, Knuth Algorithm D / GMP style) // Fixes an overflow bug in the old "product.low + borrow" code uint64_t mul_carry = 0; for (size_t i = 0; i < n; i++) { // Bounds check size_t idx = j + i; if (idx >= U_work.size()) { throw std::out_of_range("U_work index out of bounds in mul-sub"); } // Compute q_hat * V[i] UInt128 product = UInt128::multiply(q_hat, V[i]); // product.low + mul_carry (carry from the previous iteration) uint64_t sum_low = product.low + mul_carry; uint64_t carry_from_add = (sum_low < product.low) ? 1 : 0; // Subtract sum_low from U_work[j+i] uint64_t prev = U_work[j + i]; U_work[j + i] = prev - sum_low; uint64_t borrow_from_sub = (prev < sum_low) ? 1 : 0; // Next carry = product.high + carry_from_add + borrow_from_sub // Note: product.high <= 2^64-2 (maximum of a 64-bit x 64-bit multiplication) // carry_from_add + borrow_from_sub <= 2, but // when both are 1 we have product.high < 2^64-2, so the // total is always at most 2^64-1 (no overflow). mul_carry = product.high + carry_from_add + borrow_from_sub; } // Subtract the remaining carry from the top word { uint64_t prev = U_work[j + n]; U_work[j + n] = prev - mul_carry; // If prev < mul_carry, the result went negative (add-back needed) mul_carry = (prev < mul_carry) ? 1 : 0; } // Adjustment when the subtraction result is negative (q_hat was too large) if (mul_carry != 0) { // Decrement q_hat by 1 and add the divisor back q_hat--; uint64_t carry = 0; for (size_t i = 0; i < n; i++) { uint64_t u = U_work[j + i]; uint64_t v = V[i]; // Two-stage addition to track the carry accurately uint64_t sum = u + v; uint64_t c1 = (sum < u) ? 1ULL : 0ULL; uint64_t sum2 = sum + carry; uint64_t c2 = (sum2 < sum) ? 1ULL : 0ULL; U_work[j + i] = sum2; carry = c1 + c2; } // Add the carry into the top digit U_work[j + n] += carry; } // Set the quotient digit Q[j] = q_hat; } // Set the remainder (the first n digits of U_work) std::vector R; for (size_t i = 0; i < n && i < U_work.size(); i++) { R.push_back(U_work[i]); } // Restore the remainder from before the normalization shift Int normalizedRemainder = Int::fromRawWords(R, +1); normalizedRemainder.normalize(); if (normalization_shift > 0) { remainder = normalizedRemainder >> normalization_shift; } else { remainder = normalizedRemainder; } // Build the quotient Int quotient = Int::fromRawWords(Q, +1); quotient.normalize(); // Apply signs if (remainder_negative && !remainder.isZero()) { remainder.negate(); } if (quotient_negative && !quotient.isZero()) { quotient.negate(); } return quotient; } /** * @brief Division implementation (quotient only) * @param dividend Dividend * @param divisor Divisor * @return Quotient */ static Int divide(const Int& dividend, const Int& divisor) { Int remainder; return divKnuth(dividend, divisor, remainder); } /** * @brief Modulo implementation * @param dividend Dividend * @param divisor Divisor * @return Remainder */ static Int modulo(const Int& dividend, const Int& divisor) { Int remainder; divKnuth(dividend, divisor, remainder); return remainder; } /** * @brief Fast division by a power of two * * Division by a power of two can be performed quickly as a right shift. * * @param dividend Dividend * @param power Power-of-two exponent (2^power) * @param remainder Output location for the remainder * @return Quotient */ static Int divPowerOfTwo(const Int& dividend, size_t power, Int& remainder) { if (power == 0) { // Division by 2^0 = 1 remainder = Int(0); return dividend; } // Save the sign int sign = dividend.getSign(); // Handle special states if (dividend.getState() != NumericState::Normal) { if (dividend.isNaN() || NumericStateTraits::isInfinite(dividend.getState())) { remainder = Int::fromState(NumericState::NaN, NumericError::NaNPropagation); return dividend; // NaN or infinity passes through } } // Take the absolute value Int abs_dividend = abs(dividend); // Compute the remainder (extract the low `power` bits) size_t word_shift = power / 64; size_t bit_shift = power % 64; std::vector remainder_words; std::vector words = abs_dividend.words(); if (!words.empty()) { // Copy whole words for (size_t i = 0; i < std::min(words.size(), word_shift); ++i) { remainder_words.push_back(words[i]); } // Mask the leftover bits if (bit_shift > 0 && word_shift < words.size()) { uint64_t mask = (1ULL << bit_shift) - 1; remainder_words.push_back(words[word_shift] & mask); } } remainder = Int::fromRawWords(remainder_words, sign); // Compute the quotient (right shift) Int quotient = abs_dividend >> static_cast(power); // Adjust the sign if (sign < 0 && !quotient.isZero()) { quotient.negate(); } return quotient; } /** * @brief Fast division by a power of ten * * Used when shifting digits in the decimal representation * * @param dividend Dividend * @param power Power-of-ten exponent (10^power) * @param remainder Output location for the remainder * @return Quotient */ static Int divPowerOfTen(const Int& dividend, size_t power, Int& remainder) { // Compute 10^power Int divisor(10); // Use the unsigned-int overload of pow Int ten_power = sangi::pow(divisor, static_cast(power)); // Use ordinary division return divKnuth(dividend, ten_power, remainder); } /** * @brief Newton-Raphson approximation of the reciprocal * * Uses double precision to obtain an initial approximation, then * refines it through Newton-Raphson iteration. Even for huge * integers, the exponent is adjusted so the value fits in the * double range before computation. * * Algorithm: * 1. Normalize divisor into the double range (adjust by bit shift) * 2. Compute an initial approximation of 1/divisor in double precision * 3. Newton-Raphson iteration: X_{n+1} = X_n * (2 - D * X_n / 2^k) * 4. Return the result in fixed-point: floor(2^scale / divisor) * * @param divisor Divisor (positive integer) * @param scale Scale factor (result is 2^scale / divisor) * @return floor(2^scale / divisor) - fixed-point reciprocal */ static Int newtonRaphsonReciprocal(const Int& divisor, size_t scale) { // Handle special cases if (divisor.isZero()) { return Int::fromState(NumericState::NaN, NumericError::DivideByZero); } if (divisor.isNaN() || NumericStateTraits::isInfinite(divisor.getState())) { return Int::fromState(NumericState::NaN, NumericError::NaNPropagation); } size_t divisor_bits = divisor.bitLength(); // Small divisors are computed directly if (divisor_bits <= 64) { Int numerator = Int(1) << static_cast(scale); return numerator / divisor; } // Step 1: normalize the divisor into the double range // double's mantissa is 53 bits const size_t DOUBLE_MANTISSA_BITS = 53; // Extract the top ~53 bits of the divisor int shift_down = static_cast(divisor_bits) - static_cast(DOUBLE_MANTISSA_BITS); if (shift_down < 0) shift_down = 0; Int divisor_normalized = divisor >> shift_down; // Convert to double double d_approx; if (divisor_normalized.size() >= 2) { uint64_t high = divisor_normalized.word(divisor_normalized.size() - 1); uint64_t low = divisor_normalized.word(divisor_normalized.size() - 2); // Approximate high * 2^64 + low as a double d_approx = static_cast(high) * 18446744073709551616.0 + static_cast(low); } else if (divisor_normalized.size() == 1) { d_approx = static_cast(divisor_normalized.word(0)); } else { d_approx = 1.0; } // Step 2: compute the initial reciprocal in double precision double recip_double = 1.0 / d_approx; // Convert recip_double into a fixed-point Int // recip_double is the reciprocal of divisor_normalized // Since divisor_normalized = divisor >> shift_down, // 1/divisor_normalized = (1/divisor) * 2^shift_down // i.e. recip_double ~= (1/divisor) * 2^shift_down // working_scale is the same as scale // The reciprocal X needs ~2^(scale-divisor_bits) bits of precision // for 2^working_scale / D. If working_scale is too small, the // integer representation of X has too few bits and the quotient // loses precision (the correction loop spins forever). const size_t working_scale = scale; // Initial approximation: X_0 = floor(2^working_scale / divisor) // recip_double ~= (1/divisor) * 2^shift_down = 2^shift_down / divisor // We want X_0 = floor(2^working_scale / divisor) // X_0 = recip_double * 2^(working_scale - shift_down) // Extract mantissa and exponent from recip_double using frexp int exponent; double mantissa = std::frexp(recip_double, &exponent); // Now recip_double = mantissa * 2^exponent, where 0.5 <= |mantissa| < 1.0 // Convert mantissa to fixed-point (multiply by 2^53 to get all 53 bits) uint64_t recip_int = static_cast(mantissa * (1ULL << 53)); Int X = Int(recip_int); // Total shift = (working_scale - shift_down) + exponent - 53 int total_shift = static_cast(working_scale) - shift_down + exponent - 53; if (total_shift > 0) { X = X << total_shift; } else if (total_shift < 0) { X = X >> (-total_shift); } // Step 3: improve precision quadratically via Newton-Raphson iteration // Iteration: X_{n+1} = X_n * (2*2^k - D * X_n) / 2^k // Each iteration doubles the number of accurate bits (quadratic convergence) // // Initial precision: 53-bit double mantissa // Required precision: working_scale bits (until diff < D) // Compute the number of iterations: 53 -> 106 -> 212 -> ... -> working_scale size_t initial_precision = 50; // Conservative estimate from double precision size_t iterations_needed = 0; { size_t current = initial_precision; while (current < working_scale) { current *= 2; iterations_needed++; } } Int R = Int(1) << static_cast(working_scale); for (size_t iter = 0; iter < iterations_needed; ++iter) { // Compute D * X_n (scale 2^k) Int DX = divisor * X; // 2 * 2^k Int two_R = R << 1; // Correction: E = 2*2^k - D*X_n Int E = two_R - DX; // X_{n+1} = X_n * E / 2^k X = (X * E) >> static_cast(working_scale); } // Since working_scale == scale, no scale adjustment is needed return X; } /** * @brief Newton-based division (fast algorithm for very large numbers) * * Computes 1/divisor via Newton-Raphson iteration and multiplies it * by the dividend to perform the division. * Complexity is O(M(n)) where M(n) is multiplication time * (O(n^1.585) for Karatsuba, O(n log n) for FFT). * * Reference: "Modern Computer Arithmetic" Algorithm 1.6 * * @param dividend Dividend * @param divisor Divisor * @param remainder Output location for the remainder * @return Quotient */ static Int divNewton(const Int& dividend, const Int& divisor, Int& remainder) { // Handle special cases if (divisor.isZero()) { remainder = Int::fromState(NumericState::NaN, NumericError::DivideByZero); return Int::fromState(NumericState::NaN, NumericError::DivideByZero); } if (dividend.isZero()) { remainder = Int(0); return Int(0); } if (dividend.isNaN() || divisor.isNaN() || NumericStateTraits::isInfinite(dividend.getState()) || NumericStateTraits::isInfinite(divisor.getState())) { remainder = Int::fromState(NumericState::NaN, NumericError::NaNPropagation); return Int::fromState(NumericState::NaN, NumericError::NaNPropagation); } // Save signs int dividend_sign = dividend.getSign(); int divisor_sign = divisor.getSign(); int quotient_sign = dividend_sign * divisor_sign; // Operate on absolute values Int abs_dividend = abs(dividend); Int abs_divisor = abs(divisor); // If the divisor is larger than the dividend if (abs_dividend < abs_divisor) { remainder = dividend; // Preserve the sign return Int(0); } // Newton division algorithm // Compute Q = floor(A / D) // 1. Compute R = 1/D (fixed-point: 2^k / D) // 2. Q_approx = A * R / 2^k // 3. Compute the remainder and correct the quotient size_t dividend_bits = abs_dividend.bitLength(); // Choose the scale: dividend_bits + headroom // For the reciprocal R = 2^scale / D to carry quotient_bits of // precision, we need scale >= dividend_bits + margin. // divisor_bits + 64 is insufficient precision, causing the // correction loop to spin forever. size_t scale = dividend_bits + 64; // Step 1: compute 1/divisor Int reciprocal = newtonRaphsonReciprocal(abs_divisor, scale); // Step 2: compute the quotient approximation // Q ~= A * (2^scale / D) / 2^scale = A * reciprocal / 2^scale Int quotient = (abs_dividend * reciprocal) >> static_cast(scale); // Step 3: compute the remainder Int rem = abs_dividend - (quotient * abs_divisor); // If the remainder is negative, decrement the quotient // (usually only 1-2 adjustments are required) while (rem.getSign() < 0) { quotient -= 1; rem = rem + abs_divisor; } // If the remainder is >= divisor, increment the quotient // (usually only 1-2 adjustments are required) while (rem >= abs_divisor) { quotient += 1; rem = rem - abs_divisor; } // Apply signs if (quotient_sign < 0 && !quotient.isZero()) { quotient.negate(); } if (dividend_sign < 0 && !rem.isZero()) { rem.negate(); } remainder = rem; return quotient; } private: // Internal implementation helper functions }; } // namespace sangi #endif // SANGI_INT_DIVISION_HPP