// Copyright (C) 2026 Kiyotsugu Arai
// SPDX-License-Identifier: LGPL-3.0-or-later

// Float.cpp
// Implementation of the multiple-precision floating-point class

#include <math/core/mp/Float.hpp>
#include <math/core/mp/FloatOps.hpp>
#include <math/core/mp/Float/FloatMath.hpp>
#include <math/core/mp/Int/IntMultiplication.hpp>
#include <math/core/mp/Int/IntOps.hpp>
#include <cmath>
#include <sstream>
#include <iomanip>
#include <limits>
#include <string>
#include <algorithm>
#include <thread>
#include <math/core/mp/ThreadPool.hpp>
#include <math/core/mp/Int/PrimeNtt.hpp>

namespace sangi {

// Forward declarations (used by operator+= / -=)
static int mergeRequested(int req_a, int req_b);
static int mergeEffective(int eff_a, int eff_b);

// Compute 5^n by repeated squaring (O(M(n) log n))
// Since 10^n = 5^n * 2^n, handle 2^n with a shift and only compute 5^n
static Int pow5_fast(size_t n) {
    if (n == 0) return Int(1);
    if (n == 1) return Int(5);
    Int base(5);
    Int result(1);
    size_t exp = n;
    while (exp > 0) {
        if (exp & 1) result = result * base;
        exp >>= 1;
        if (exp > 0) base = base * base;
    }
    return result;
}

// Compute 10^n by repeated squaring (O(M(n) log n))
[[maybe_unused]] static Int pow10_fast(size_t n) {
    if (n == 0) return Int(1);
    if (n == 1) return Int(10);
    Int base(10);
    Int result(1);
    size_t exp = n;
    while (exp > 0) {
        if (exp & 1) result = result * base;
        exp >>= 1;
        if (exp > 0) base = base * base;
    }
    return result;
}



// defaultPrecision() and rounding_mode_ are already defined as inline static thread_local in Float.hpp.
// The variable exists in the caller's TU even across DLL boundaries, so no export is needed.
thread_local int64_t Float::emin_ = Float::EXPONENT_MIN;
thread_local int64_t Float::emax_ = Float::EXPONENT_MAX;
thread_local unsigned Float::exception_flags_ = 0;

void Float::checkExponentBounds() {
    if (is_infinity_ || is_nan_ || mantissa_.isZero()) {
        return;
    }
    if (exponent_ > EXPONENT_MAX) {
        // Exponent overflow → ±∞
        is_infinity_ = true;
        effective_bits_ = 0;
        requested_bits_ = 0;
        mantissa_ = Int(0);
        exponent_ = 0;
    } else if (exponent_ < EXPONENT_MIN) {
        // Exponent underflow → 0
        mantissa_ = Int(0);
        exponent_ = 0;
        is_negative_ = false;
        effective_bits_ = 0;
        requested_bits_ = 0;
    }
}

// Convert decimal digit count to bit count (log₂(10) ≈ 3.32)
int Float::precisionToBits(int precision) {
    // To accurately guarantee N decimal digits, in addition to N * log2(10) bits
    // guard bits are required. Without guard bits the final 1-2 digits become inaccurate due to rounding error.
    // 8 guard bits ≈ 2.4 digits protect the trailing digit.
    constexpr int GUARD = 8;
    return static_cast<int>(std::ceil(precision * 3.32192809488736)) + GUARD;
}

// Convert bit count to decimal digit count
int Float::bitsToPrecision(int bits) {
    if (bits <= 0) return 1;

    // Subtract the guard bits added by precisionToBits before converting to digit count
    // Since precisionToBits(N) = ceil(N * log2(10)) + GUARD,
    // the inverse conversion subtracts GUARD
    constexpr int GUARD = 8;  // same value as in precisionToBits
    int effective_bits = std::max(1, bits - GUARD);
    int precision = static_cast<int>(std::floor(effective_bits / 3.32192809488736));

    return std::max(1, precision);
}

//======================================================================
// Constructors
//======================================================================

Float::Float()
    : mantissa_(0), exponent_(0), is_negative_(false),
      is_infinity_(false), is_nan_(false),
      effective_bits_(INT_MAX), requested_bits_(INT_MAX) {
}

Float::Float(int value)
    : mantissa_(std::abs(value)), exponent_(0), is_negative_(value < 0),
      is_infinity_(false), is_nan_(false),
      effective_bits_(INT_MAX), requested_bits_(INT_MAX) {
    normalize();
}

Float::Float(int64_t value)
    : mantissa_(std::abs(value)), exponent_(0), is_negative_(value < 0),
      is_infinity_(false), is_nan_(false),
      effective_bits_(INT_MAX), requested_bits_(INT_MAX) {
    normalize();
}

Float::Float(int64_t mantissa, int64_t exponent, bool is_negative)
    : mantissa_(std::abs(mantissa)), exponent_(exponent), is_negative_(mantissa < 0 ? !is_negative : is_negative),
    is_infinity_(false), is_nan_(false),
    effective_bits_(INT_MAX), requested_bits_(INT_MAX) {
    // Flip the sign if a negative value was passed
    if (mantissa < 0) {
        mantissa_ = Int(-mantissa);
    }
    normalize();
}

Float::Float(const Int& value)
    : mantissa_(abs(value)), exponent_(0), is_negative_(value.isNegative()),
      is_infinity_(false), is_nan_(false),
      effective_bits_(INT_MAX), requested_bits_(INT_MAX) {
    normalize();
}

Float::Float(double value) : is_infinity_(false), is_nan_(false),
    effective_bits_(53), requested_bits_(precisionToBits(defaultPrecision())) {
    // Handle special values
    if (std::isnan(value)) {
        is_nan_ = true;
        is_negative_ = false;
        mantissa_ = Int(0);
        exponent_ = 0;
        effective_bits_ = 0;
        requested_bits_ = 0;
        return;
    }

    is_negative_ = std::signbit(value);
    value = std::abs(value);

    if (std::isinf(value)) {
        is_infinity_ = true;
        mantissa_ = Int(0);
        exponent_ = 0;
        effective_bits_ = 0;
        requested_bits_ = 0;
        return;
    }

    // Handle zero
    if (value == 0.0) {
        mantissa_ = Int(0);
        exponent_ = 0;
        effective_bits_ = INT_MAX;
        requested_bits_ = INT_MAX;
        return;
    }

    // Handle the double directly (without using frexp)
    // Obtain the bit representation of the floating-point number
    uint64_t bits;
    std::memcpy(&bits, &value, sizeof(double));

    // Extraction from IEEE 754 double-precision format
    // sign bit (1bit), exponent (11bits), mantissa (52bits)
    uint64_t exponent_bits = (bits >> 52) & 0x7FF;
    uint64_t mantissa_bits = bits & 0x000FFFFFFFFFFFFF;

    // Biased exponent (1023 in IEEE 754)
    int64_t true_exponent = static_cast<int64_t>(exponent_bits) - 1023;

    // Add the implicit leading bit (1) to the mantissa
    mantissa_ = Int(mantissa_bits | 0x10000000000000); // add 2^52

    // Adjust the exponent (since the mantissa is multiplied by 2^52)
    exponent_ = true_exponent - 52;

    // Determining the precision fields: binary exponent analysis
    // value = full_mantissa × 2^(true_exponent - 52)
    // Remove trailing zeros from the mantissa to get the form odd_part × 2^e_adj
    // If e_adj >= 0 it is an integer → exact (INT_MAX)
    // If e_adj < 0 and |e_adj| <= 20 it is a clean decimal fraction → exact (INT_MAX)
    if (value == 0.0) {
        effective_bits_ = INT_MAX;
        requested_bits_ = INT_MAX;
    } else if (exponent_bits != 0x7FF) {  // other than inf, NaN
        uint64_t m = mantissa_bits | 0x10000000000000;
        int trailing_zeros = 0;
        while ((m & 1) == 0) {
            m >>= 1;
            trailing_zeros++;
        }
        int64_t e_adj = true_exponent - 52 + trailing_zeros;
        if ((e_adj >= 0) || ((-e_adj) <= 20)) {
            effective_bits_ = INT_MAX;
            requested_bits_ = INT_MAX;
        }
        // otherwise: effective_bits_ = 53, requested_bits_ = default (already set in the initializer list)
    }

    normalize();
}

Float::Float(std::string_view str)
    : is_infinity_(false), is_nan_(false),
      effective_bits_(0), requested_bits_(0) {
    initFromDecimalString(str, -1);
}

Float::Float(std::string_view str, int precision)
    : is_infinity_(false), is_nan_(false),
      effective_bits_(0), requested_bits_(0) {
    initFromDecimalString(str, precision);
}

// If requested_precision >= 0, round non-exact values to that decimal precision.
// If requested_precision < 0 (default), use max(string significant digits, defaultPrecision()).
void Float::initFromDecimalString(std::string_view str, int requested_precision) {
    // Check for special values
    std::string s(str);
    std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return std::tolower(c); });

    if (s == "nan" || s == "inf" || s == "+inf" || s == "-inf" || s == "infinity" || s == "+infinity" || s == "-infinity") {
        if (s == "nan") {
            is_nan_ = true;
            is_negative_ = false;
        } else {
            is_infinity_ = true;
            is_negative_ = (s[0] == '-');
        }
        mantissa_ = 0;
        exponent_ = 0;
        return;
    }

    // High-precision decimal string parsing
    // Handle the sign
    size_t pos = 0;
    is_negative_ = false;
    if (pos < str.size() && (str[pos] == '+' || str[pos] == '-')) {
        is_negative_ = (str[pos] == '-');
        pos++;
    }

    // Collect the digits of the integer and fractional parts
    std::string digits;
    int decimal_digits = 0;
    bool found_dot = false;
    int exponent_10 = 0;

    for (; pos < str.size(); pos++) {
        if (str[pos] == '.') {
            found_dot = true;
        } else if (str[pos] == 'e' || str[pos] == 'E') {
            // Exponent part
            pos++;
            exponent_10 = std::stoi(std::string(str.substr(pos)));
            break;
        } else if (str[pos] >= '0' && str[pos] <= '9') {
            digits += str[pos];
            if (found_dot) decimal_digits++;
        } else {
            break;
        }
    }

    if (digits.empty()) {
        // Parse failure
        is_nan_ = true;
        mantissa_ = 0;
        exponent_ = 0;
        is_negative_ = false;
        return;
    }

    // Remove leading zeros
    size_t first_nonzero = digits.find_first_not_of('0');
    if (first_nonzero == std::string::npos) {
        // All zeros
        mantissa_ = Int(0);
        exponent_ = 0;
        is_negative_ = false;
        effective_bits_ = INT_MAX;
        requested_bits_ = INT_MAX;
        return;
    }

    // Compute the precision bit count from the significant digit count
    int significant_digits = static_cast<int>(digits.size() - first_nonzero);
    int string_precision_bits;

    if (requested_precision >= 0) {
        // Two-argument version: round non-exact values to the explicitly specified decimal precision.
        // If the string has more significant digits than that, prefer the string side (do not discard information).
        string_precision_bits = std::max(precisionToBits(requested_precision),
                                         precisionToBits(significant_digits));
    } else {
        // One-argument version: guarantee at least defaultPrecision even for non-exact strings
        // (max(string, default)).
        // Motivation: addressing the problem where effective_bits of Float("0.1") was interpreted
        //       as "1 digit = 12 bits", causing precision degradation in LinAlg use.
        //   - Float("0.1") is mathematically 1/10; the user expects "0.1 at full precision",
        //     not "a 3-digit value"
        //   - to explicitly state a precision constraint, use the two-argument version or setPrecision
        //   - integer/exact strings are INT_MAX, so this branch does not affect them
        string_precision_bits = precisionToBits(significant_digits);
        int default_precision_bits = precisionToBits(defaultPrecision());
        if (default_precision_bits > string_precision_bits) {
            string_precision_bits = default_precision_bits;
        }
    }

    // Convert the decimal digit string to an Int
    Int numerator(digits);

    // value = numerator * 10^(exponent_10 - decimal_digits)
    int net_exp10 = exponent_10 - decimal_digits;

    // "Mathematically exact value" determination:
    //   - integer strings (e.g. "1", "100", "1e3", net_exp10 >= 0) are exact
    //   - pure binary finite fractions (e.g. "0.5", "0.25", net_exp10 < 0 where 5^D divides
    //     numerator) are also exact
    // When judged exact, set effective_bits_ = INT_MAX to preserve arithmetic precision.
    // This ensures that in the typical case of using Float("1") as input to LinAlg etc., later
    // operations (= 1/5 etc.) are executed at default precision.
    bool is_exact = false;

    if (net_exp10 == 0) {
        // value = numerator (integer) — always exact
        mantissa_ = numerator;
        exponent_ = 0;
        is_exact = true;
        normalize();
    } else if (net_exp10 > 0) {
        // value = numerator * 10^net_exp10 = numerator * 5^e * 2^e — integer and exact
        Int five_pow = pow5_fast(static_cast<size_t>(net_exp10));
        mantissa_ = numerator * five_pow;
        exponent_ = net_exp10; // 2^net_exp10
        is_exact = true;
        normalize();
    }

    if (is_exact) {
        effective_bits_ = INT_MAX;
        requested_bits_ = INT_MAX;
    } else if (net_exp10 < 0) {
        // value = numerator / 10^D = numerator / (5^D * 2^D)
        // Divide by 5^D, and handle 2^D via exponent adjustment
        size_t D = static_cast<size_t>(-net_exp10);
        Int five_pow = pow5_fast(D);

        // Pure binary finite-fraction test: exact if numerator is divisible by 5^D.
        // e.g. "0.5" (5/10 = 1/2), "0.25" (25/100 = 1/4), "1.5" (15/10 = 3/2)
        // e.g. "0.1" (1/10) is not exact, since 5^1=5 does not divide 1
        Int remainder_check = numerator % five_pow;
        if (remainder_check.isZero()) {
            // Perfect binary finite fraction → exact
            mantissa_ = numerator / five_pow;
            exponent_ = -static_cast<int64_t>(D);
            effective_bits_ = INT_MAX;
            requested_bits_ = INT_MAX;
            normalize();
        } else {
            int num_bits = static_cast<int>(numerator.bitLength());
            int den_bits = static_cast<int>(five_pow.bitLength());
            // Set so the quotient secures string_precision_bits + guard bits
            // quotient_bits ≈ num_bits + target_bits - den_bits ≥ string_precision_bits + 20
            int target_bits = std::max(den_bits - num_bits, 0) + string_precision_bits + 20;

            Int scaled = numerator << target_bits;
            Int remainder;
            Int quotient = IntOps::divmod(scaled, five_pow, remainder);

            mantissa_ = std::move(quotient);
            exponent_ = -static_cast<int64_t>(target_bits) - static_cast<int64_t>(D);
            effective_bits_ = string_precision_bits;
            requested_bits_ = string_precision_bits;
            normalize();
        }
    }
}

Float::Float(const Int& mantissa, int64_t exponent, bool is_negative)
    : mantissa_(mantissa), exponent_(exponent), is_negative_(is_negative),
      is_infinity_(false), is_nan_(false),
      effective_bits_(INT_MAX), requested_bits_(INT_MAX) {
    normalize();
}

Float::Float(Int&& mantissa, int64_t exponent, bool is_negative)
    : mantissa_(std::move(mantissa)), exponent_(exponent), is_negative_(is_negative),
      is_infinity_(false), is_nan_(false),
      effective_bits_(INT_MAX), requested_bits_(INT_MAX) {
    normalize();
}

//======================================================================
// Assignment operators
//======================================================================

Float& Float::operator=(int64_t value) {
    mantissa_ = Int(std::abs(value));
    exponent_ = 0;
    is_negative_ = (value < 0);
    is_infinity_ = false;
    is_nan_ = false;
    effective_bits_ = INT_MAX;
    requested_bits_ = INT_MAX;
    normalize();
    return *this;
}

Float& Float::operator=(double value) {
    *this = Float(value);
    return *this;
}

//======================================================================
// Operator implementations
//======================================================================

Float& Float::operator+=(const Float& rhs) {
    // Delegate special values to the legacy path
    if (isNaN() || rhs.isNaN()) [[unlikely]] { *this = Float::nan(); return *this; }
    if (isInfinity() || rhs.isInfinity()) [[unlikely]] {
        *this = static_cast<const Float&>(*this) + rhs; return *this;
    }
    if (isZero()) [[unlikely]] { *this = rhs; return *this; }
    if (rhs.isZero()) [[unlikely]] return *this;

    // Read the precision fields first (before this is overwritten)
    int eff = mergeEffective(effective_bits_, rhs.effective_bits_);
    int req = mergeRequested(requested_bits_, rhs.requested_bits_);

    if (is_negative_ == rhs.is_negative_) {
        // Same sign: mantissa addition (no cancellation)
        addUnsignedInPlace(rhs);
        effective_bits_ = eff;
        requested_bits_ = req;
    } else {
        // Opposite signs: legacy path, since cancellation computation is complex
        int64_t mag_this = static_cast<int64_t>(mantissa_.bitLength()) + exponent_;
        int64_t mag_rhs = static_cast<int64_t>(rhs.mantissa_.bitLength()) + rhs.exponent_;
        int64_t max_mag = std::max(mag_this, mag_rhs);
        bool this_neg = is_negative_;

        subtractUnsignedInPlace(rhs);

        if (isZero()) {
            if (eff >= INT_MAX) {
                effective_bits_ = INT_MAX;
                requested_bits_ = INT_MAX;
            } else {
                effective_bits_ = 0;
                requested_bits_ = req;
            }
        } else {
            is_negative_ = is_negative_ ? rhs.isNegative() : this_neg;
            int64_t result_mag = static_cast<int64_t>(mantissa_.bitLength()) + exponent_;
            int lost_bits = static_cast<int>(std::max(int64_t(0), max_mag - result_mag));
            if (eff >= INT_MAX) {
                effective_bits_ = INT_MAX;
            } else {
                effective_bits_ = std::max(1, eff - lost_bits);
            }
            requested_bits_ = req;
        }
    }
    return *this;
}

Float& Float::operator-=(const Float& rhs) {
    // Delegate special values to the legacy path
    if (isNaN() || rhs.isNaN()) [[unlikely]] { *this = Float::nan(); return *this; }
    if (isInfinity() || rhs.isInfinity()) [[unlikely]] {
        *this = static_cast<const Float&>(*this) - rhs; return *this;
    }
    if (isZero()) [[unlikely]] {
        *this = rhs;
        is_negative_ = !is_negative_;
        return *this;
    }
    if (rhs.isZero()) [[unlikely]] return *this;

    int eff = mergeEffective(effective_bits_, rhs.effective_bits_);
    int req = mergeRequested(requested_bits_, rhs.requested_bits_);

    if (is_negative_ != rhs.is_negative_) {
        // Opposite signs: (+a)-(-b)=a+b → mantissa addition (no cancellation)
        addUnsignedInPlace(rhs);
        effective_bits_ = eff;
        requested_bits_ = req;
    } else {
        // Same sign: mantissa subtraction (with cancellation)
        int64_t mag_this = static_cast<int64_t>(mantissa_.bitLength()) + exponent_;
        int64_t mag_rhs = static_cast<int64_t>(rhs.mantissa_.bitLength()) + rhs.exponent_;
        int64_t max_mag = std::max(mag_this, mag_rhs);
        bool this_neg = is_negative_;

        subtractUnsignedInPlace(rhs);

        if (isZero()) {
            if (eff >= INT_MAX) {
                effective_bits_ = INT_MAX;
                requested_bits_ = INT_MAX;
            } else {
                effective_bits_ = 0;
                requested_bits_ = req;
            }
        } else {
            // subtractUnsigned: is_negative_=true means |this| < |rhs|
            // operator-: same sign with |this|>=|rhs| → sign=this_neg, |this|<|rhs| → sign=!this_neg
            is_negative_ = is_negative_ ? !this_neg : this_neg;
            int64_t result_mag = static_cast<int64_t>(mantissa_.bitLength()) + exponent_;
            int lost_bits = static_cast<int>(std::max(int64_t(0), max_mag - result_mag));
            if (eff >= INT_MAX) {
                effective_bits_ = INT_MAX;
            } else {
                effective_bits_ = std::max(1, eff - lost_bits);
            }
            requested_bits_ = req;
        }
    }
    return *this;
}

Float& Float::operator*=(const Float& rhs) {
    *this = std::move(*this) * rhs;
    return *this;
}

Float& Float::operator/=(const Float& rhs) {
    *this = std::move(*this) / rhs;
    return *this;
}

//======================================================================
// Method implementations
//======================================================================

bool Float::isZero() const {
    return !is_infinity_ && !is_nan_ && mantissa_.isZero();
}

int Float::precision() const {
    if (isZero() || isInfinity() || isNaN()) {
        return defaultPrecision();
    }
    int eff = std::min(effective_bits_, requested_bits_);
    if (eff >= INT_MAX) {
        // exact value (integer-derived) → defaultPrecision compatible
        return defaultPrecision();
    }
    return Float::bitsToPrecision(eff);
}

Float& Float::setPrecision(int precision) {
    if (isZero() || isInfinity() || isNaN()) {
        return *this;
    }

    int precision_bits = precisionToBits(precision);
    int current_bits = static_cast<int>(mantissa_.bitLength());

    // Update requested_bits_
    requested_bits_ = precision_bits;

    if (current_bits < precision_bits) {
        // If precision is insufficient, left-shift the mantissa to pad
        // The value does not change: (mantissa << shift) * 2^(exponent - shift) = mantissa * 2^exponent
        // Do not change effective_bits_ (padding does not increase reliability)
        int shift = precision_bits - current_bits;
        mantissa_ <<= shift;
        exponent_ -= shift;
        checkExponentBounds();
    }

    round(precision_bits, roundingMode());
    return *this;
}

Float& Float::setResultPrecision(int precision) {
    setPrecision(precision);
    if (!isZero() && !isInfinity() && !isNaN()) {
        effective_bits_ = precisionToBits(precision);
    }
    return *this;
}

void Float::truncateToApprox(int precision) {
    if (isZero() || isInfinity() || isNaN()) [[unlikely]] return;

    int target_bits = precisionToBits(precision);
    requested_bits_ = target_bits;
    int bl = static_cast<int>(mantissa_.bitLength());

    if (bl < target_bits) {
        // Padding: extend the mantissa as in setPrecision
        int shift = target_bits - bl;
        mantissa_ <<= shift;
        exponent_ -= shift;
        checkExponentBounds();
        return;
    }

    if (bl <= target_bits) return;

    // Truncate the low part word-by-word (no bit shift or rounding)
    // floor division: keep up to 63 bits of excess → absorbed by guard bits
    int words_to_drop = (bl - target_bits) / 64;
    if (words_to_drop <= 0) return;

    int shift = words_to_drop * 64;
    mantissa_ >>= shift;  // bit_shift==0 → erase only (O(1) memmove)
    exponent_ += shift;
}

void Float::normalize() {
    if (is_infinity_ || is_nan_ || mantissa_.isZero()) [[unlikely]] {
        return;
    }

    // Remove zero words at both ends in-place (no alloc)
    size_t lsb_removed = mantissa_.trimZeroWords();
    if (lsb_removed > 0) {
        exponent_ += static_cast<int64_t>(64 * lsb_removed);
    }

    if (mantissa_.isZero()) [[unlikely]] {
        exponent_ = 0;
        is_negative_ = false;
    }

    checkExponentBounds();
}

void Float::round(int precision_bits, RoundingMode mode) {
    if (isZero() || isInfinity() || isNaN()) {
        return;
    }

    int bit_length = static_cast<int>(mantissa_.bitLength());
    if (bit_length <= precision_bits) {
        return;  // already at or below the required precision
    }

    int shift = bit_length - precision_bits;

    // ── Extract guard and sticky bits directly from the raw limbs ──
    // Avoid Int copies and mask generation; make the rounding decision with O(n) reads only.
    const uint64_t* data = mantissa_.data();
    size_t n = mantissa_.size();

    // guard bit = bit (shift - 1): the bit just below the precision boundary
    bool guard_bit = false;
    if (shift >= 1) {
        size_t gw = static_cast<size_t>(shift - 1) / 64;
        unsigned gb = static_cast<unsigned>((shift - 1) % 64);
        if (gw < n) {
            guard_bit = ((data[gw] >> gb) & 1) != 0;
        }
    }

    // sticky bits = bits 0..(shift-2): true if any of them is non-zero
    bool sticky = false;
    if (shift >= 2) {
        size_t full_words = static_cast<size_t>(shift - 1) / 64;
        for (size_t i = 0; i < full_words && i < n; ++i) {
            if (data[i] != 0) { sticky = true; break; }
        }
        if (!sticky && full_words < n) {
            unsigned partial = static_cast<unsigned>((shift - 1) % 64);
            if (partial > 0) {
                uint64_t mask = (1ULL << partial) - 1;
                if (data[full_words] & mask) sticky = true;
            }
        }
    }

    // Remove the low bits
    mantissa_ >>= shift;
    exponent_ += shift;

    // Determine the rounding direction
    bool any_discarded = guard_bit || sticky;
    bool round_up = false;

    switch (mode) {
        case RoundingMode::ToNearest:
            // Round to even: round up when guard=1 and (sticky=1 or result LSB=1)
            if (guard_bit) {
                round_up = sticky || ((mantissa_.word(0) & 1) != 0);
            }
            break;

        case RoundingMode::TowardZero:
            break;

        case RoundingMode::TowardPositive:
            round_up = !is_negative_ && any_discarded;
            break;

        case RoundingMode::TowardNegative:
            round_up = is_negative_ && any_discarded;
            break;

        case RoundingMode::AwayFromZero:
            round_up = any_discarded;
            break;
    }

    if (round_up) {
        IntOps::addDelta(mantissa_, 1);
        if (static_cast<int>(mantissa_.bitLength()) > precision_bits) {
            mantissa_ >>= 1;
            exponent_ += 1;
        }
    }

    // Since we truncated during rounding, limit effective_bits_ to at most precision_bits
    if (effective_bits_ > precision_bits && effective_bits_ < INT_MAX) {
        effective_bits_ = precision_bits;
    }

    // MIN policy: rounding clamps effective_bits_ to at most precision_bits
    if constexpr (PRECISION_POLICY == PrecisionPolicy::MIN_PROPAGATION) {
        if (effective_bits_ < INT_MAX) {
            effective_bits_ = std::min(effective_bits_, precision_bits);
        }
    }

    // Range check the exponent
    checkExponentBounds();
}

void Float::subnormalize(RoundingMode mode) {
    // Do nothing for special values and zero
    if (isZero() || isInfinity() || isNaN()) return;

    // Do nothing if within the normal range
    // IEEE 754: the minimum exponent of normal numbers is emin; with p mantissa bits,
    // subnormal condition: exponent < emin + p - 1 (= the leading mantissa bit does not reach emin)
    int bit_length = static_cast<int>(mantissa_.bitLength());
    // effective exponent = exponent_ + bit_length - 1 (position of MSB)
    int64_t msb_exp = exponent_ + static_cast<int64_t>(bit_length) - 1;

    if (msb_exp >= emin_) return;  // normal number

    // Shift amount: shift the mantissa right to align with emin
    int64_t shift = emin_ - msb_exp;

    if (shift >= bit_length) {
        // Complete underflow → zero
        mantissa_ = Int(0);
        exponent_ = 0;
        is_negative_ = false;
        effective_bits_ = 0;
        requested_bits_ = 0;
        raiseException(FE_UNDERFLOW | FE_INEXACT);
        return;
    }

    // Extract guard/sticky bits for rounding
    int shift_int = static_cast<int>(shift);
    bool guard_bit = mantissa_.getBit(shift_int - 1);
    bool sticky = false;
    for (int i = 0; i < shift_int - 1; ++i) {
        if (mantissa_.getBit(i)) { sticky = true; break; }
    }
    bool any_discarded = guard_bit || sticky;

    // Perform the shift
    mantissa_ >>= shift_int;
    exponent_ += shift_int;  // = emin - (bit_length - 1) + shift = emin

    // Rounding decision
    bool round_up = false;
    switch (mode) {
        case RoundingMode::ToNearest:
            if (guard_bit) {
                round_up = sticky || ((mantissa_.word(0) & 1) != 0);
            }
            break;
        case RoundingMode::TowardZero:
            break;
        case RoundingMode::TowardPositive:
            round_up = !is_negative_ && any_discarded;
            break;
        case RoundingMode::TowardNegative:
            round_up = is_negative_ && any_discarded;
            break;
        case RoundingMode::AwayFromZero:
            round_up = any_discarded;
            break;
    }

    if (round_up) {
        IntOps::addDelta(mantissa_, 1);
    }

    if (mantissa_.isZero()) {
        exponent_ = 0;
        is_negative_ = false;
        effective_bits_ = 0;
        requested_bits_ = 0;
    }

    if (any_discarded) {
        raiseException(FE_UNDERFLOW | FE_INEXACT);
    }
}

std::string Float::toString(int precision) const {
    if (isNaN()) {
        return "NaN";
    }
    
    if (isInfinity()) {
        return is_negative_ ? "-Infinity" : "Infinity";
    }
    
    if (isZero()) {
        return "0.0";
    }

    // By default use the current precision
    if (precision < 0) {
        precision = this->precision();
    }

    // Decide the display format based on the size of the exponent
    if (exponent_ >= 0 && exponent_ < precision) {
        return toDecimalString(precision);
    } else {
        return toScientificString(precision);
    }
}

std::string Float::toDecimalString(int precision) const {
    if (isNaN()) return "NaN";
    if (isInfinity()) return is_negative_ ? "-Infinity" : "Infinity";
    if (isZero()) return "0.0";

    if (precision < 0) {
        precision = std::max(1, this->precision());
    }

    // value = mantissa * 2^exponent  (mantissa >= 0)
    // |value| * 10^D = mantissa * 10^D * 2^exponent
    //                = mantissa * (2^D * 5^D) * 2^exponent
    //                = mantissa * 5^D * 2^(D + exponent)
    //
    // shift = D + exponent
    //   shift >= 0: N = (mantissa * 5^D) << shift    (exact)
    //   shift <  0: N = (mantissa * 5^D) >> (-shift)  (truncation)
    //
    // Convert N = floor(|value| * 10^D) to a decimal string and insert the decimal point.

    int D = precision;

    // Guard bits: to prevent the trailing digit from becoming inaccurate due to
    // rounding in the binary→decimal conversion (truncation of the right shift), compute with extra bits added to the mantissa.
    constexpr int GUARD_BITS = 20;  // 20 bits ≈ 6 digits of guard
    Int extended_mantissa = mantissa_ << GUARD_BITS;
    int64_t adjusted_exponent = exponent_ - GUARD_BITS;

    Int pow5 = pow(Int(5), static_cast<unsigned int>(D));
    Int product = extended_mantissa * pow5;

    int64_t shift = static_cast<int64_t>(D) + adjusted_exponent;

    Int N;
    if (shift >= 0) {
        N = product << static_cast<int>(shift);
    } else {
        int rshift = static_cast<int>(-shift);
        N = product >> rshift;
        // Round half up: carry if the MSB of the discarded bit string is 1
        if (rshift >= 1 && product.getBit(rshift - 1)) {
            N += 1;
        }
    }

    // N = round(|value| * 10^D)
    std::string digits = N.toString();

    // Compute the number of integer-part digits independently from the magnitude of |value|
    // (the digit count of N can be larger than expected due to binary rounding)
    int intDigits;
    {
        double val = std::abs(toDouble());
        if (val >= 1.0)
            intDigits = static_cast<int>(std::floor(std::log10(val))) + 1;
        else
            intDigits = 0;
    }
    int expectedLen = intDigits + D;
    if (static_cast<int>(digits.size()) > expectedLen && expectedLen > 0) {
        digits.resize(expectedLen);
    }

    std::string result;
    if (is_negative_) result += "-";

    if (static_cast<int>(digits.size()) <= D) {
        // Integer part is 0 (|value| < 1)
        result += "0.";
        if (D > static_cast<int>(digits.size())) {
            result.append(D - digits.size(), '0');
        }
        result += digits;
    } else {
        // Has an integer part
        size_t intLen = digits.size() - D;
        result += digits.substr(0, intLen);
        if (D > 0) {
            result += ".";
            result += digits.substr(intLen);
        } else {
            result += ".0";
        }
    }

    return result;
}

std::string Float::toString(int base, int fracDigits) const {
    if (base < 2 || base > 36) throw std::invalid_argument("base must be 2..36");
    if (isNaN()) return "NaN";
    if (isInfinity()) return is_negative_ ? "-Infinity" : "Infinity";
    if (isZero()) {
        std::string r = "0.";
        r.append(fracDigits > 0 ? fracDigits : 1, '0');
        return r;
    }

    // |value| = mantissa * 2^exponent
    // integer part = mantissa >> (-exponent)  (when exponent < 0)
    // fractional part = convert the remaining bits to base
    Int absVal = mantissa_;
    int64_t exp = exponent_;

    // Separate the integer and fractional parts
    Int intPart;
    Int fracBits;  // binary representation of the fractional part (the high bit is the first fractional place)
    int fracBitCount = 0;

    if (exp >= 0) {
        intPart = absVal << static_cast<int>(exp);
        fracBitCount = 0;
    } else {
        int shift = static_cast<int>(-exp);
        intPart = absVal >> shift;
        // fractional part: the low shift bits
        fracBits = absVal - (intPart << shift);
        fracBitCount = shift;
    }

    std::string result;
    if (is_negative_) result += "-";

    // Integer part
    result += intPart.isZero() ? "0" : intPart.toString(base);

    // Fractional part
    if (fracDigits < 0) {
        // Automatic: estimate from the number of mantissa bits
        double bitsPerDigit = std::log(2.0) / std::log(static_cast<double>(base));
        fracDigits = static_cast<int>(std::ceil(fracBitCount * bitsPerDigit)) + 1;
    }

    if (fracDigits > 0) {
        result += ".";
        // Convert the fractional part to base: repeatedly ×base on fracBits / 2^fracBitCount
        // numerator = fracBits, denominator = 2^fracBitCount
        Int num = fracBits;
        Int den = Int(1) << fracBitCount;
        static const char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz";

        for (int i = 0; i < fracDigits; ++i) {
            num = num * Int(base);
            Int digit = num / den;
            num = num - digit * den;
            int d = digit.isZero() ? 0 : static_cast<int>(digit.toInt64());
            result += digits[d];
        }
    }

    return result;
}

std::string Float::toScientificString(int precision) const {
    // Handle special cases
    if (isNaN()) return "NaN";
    if (isInfinity()) return is_negative_ ? "-Infinity" : "Infinity";
    if (isZero()) return "0.0e+0";

    // Adjust the precision
    if (precision < 1) {
        precision = std::max(1, this->precision());
    }

    // Scientific notation outputs `precision` as the "number of significant digits".
    //
    // N = round(|value| * 10^Dpoint) is an integer representing "Dpoint digits after the decimal point",
    // and its significant digit count is decimal_exponent + Dpoint + 1. The old implementation fixed
    // Dpoint = precision, so for small values (decimal_exponent < 0, e.g. exp(-100)≈4e-44 or
    // erfc(5)≈1.5e-12) only precision + decimal_exponent significant digits were produced, with the rest
    // padded with zeros (= the trailing |decimal_exponent| digits were false zeros).
    // → Estimate the decimal exponent from the binary exponent and choose Dpoint so that N reliably
    //   has precision + GUARD_DIGITS significant digits.

    // Decimal exponent estimate: |value| ∈ [2^(msb-1), 2^msb), msb = exponent_ + bitLength
    // floor(log10|value|) ≈ floor((msb-1)·log10 2). The ±1 error is absorbed by GUARD_DIGITS.
    int64_t msb = exponent_ + static_cast<int64_t>(mantissa_.bitLength());
    int64_t e_dec_est = static_cast<int64_t>(
        std::floor((static_cast<double>(msb) - 1.0) * 0.30102999566398114));

    constexpr int GUARD_DIGITS = 12;  // e_dec estimation error (±1) + absorption of trailing rounding
    constexpr int GUARD_BITS = 24;    // protection against right-shift truncation rounding in binary→decimal

    // Dpoint: number of digits after the decimal point. Choose it so that the significant digits
    //   = decimal_exponent + Dpoint + 1 are at least precision + GUARD_DIGITS:
    //   Dpoint >= precision + GUARD_DIGITS - 1 - e_dec_est
    int64_t Dpoint = static_cast<int64_t>(precision) + GUARD_DIGITS - 1 - e_dec_est;
    if (Dpoint < 0) Dpoint = 0;       // huge value: the integer part alone is enough

    // Binary→decimal conversion with guard bits (same algorithm as toDecimalString)
    Int extended_mantissa = mantissa_ << GUARD_BITS;
    int64_t adj_exp = exponent_ - GUARD_BITS;

    Int pow5 = pow(Int(5), static_cast<unsigned int>(Dpoint));
    Int product = extended_mantissa * pow5;
    int64_t shift = Dpoint + adj_exp;

    Int N;
    if (shift >= 0) {
        N = product << static_cast<int>(shift);
    } else {
        int rshift = static_cast<int>(-shift);
        N = product >> rshift;
        if (rshift >= 1 && product.getBit(rshift - 1)) {
            N += 1;
        }
    }

    std::string digits = N.toString();
    // Since N = round(|value| * 10^Dpoint), |value| = N · 10^{-Dpoint},
    // so the decimal exponent of the leading digit is digits.size() - Dpoint - 1.
    int64_t decimal_exponent = static_cast<int64_t>(digits.size()) - Dpoint - 1;

    // Round to precision significant digits (round half up at the precision+1-th digit)
    if (static_cast<int>(digits.size()) > precision) {
        bool round_up = digits[precision] >= '5';
        digits.resize(precision);
        if (round_up) {
            int i = precision - 1;
            for (; i >= 0; --i) {
                if (digits[i] == '9') { digits[i] = '0'; }
                else { digits[i]++; break; }
            }
            if (i < 0) {
                // All digits carried 9→0 (e.g. 999..→1000..): one more digit, exponent +1
                digits.insert(digits.begin(), '1');
                digits.resize(precision);
                decimal_exponent += 1;
            }
        }
    } else {
        // Pad only when N is strictly fewer than precision digits (finite binary fraction with definite trailing zeros)
        while (static_cast<int>(digits.size()) < precision) {
            digits += '0';
        }
    }

    // Build the result string
    std::string result;
    if (is_negative_) result += "-";
    result += digits.substr(0, 1);  // integer part (1 digit)
    if (precision > 1) {
        result += ".";
        result += digits.substr(1, precision - 1);  // fractional part
    } else {
        result += ".0";
    }
    result += "e";
    result += (decimal_exponent >= 0 ? "+" : "-");
    result += std::to_string(std::abs(decimal_exponent));

    return result;
}

double Float::toDouble() const {
    if (isNaN()) return std::numeric_limits<double>::quiet_NaN();
    if (isInfinity()) return is_negative_ ? -std::numeric_limits<double>::infinity() : std::numeric_limits<double>::infinity();
    if (isZero()) return 0.0;

    // Use only the top 53 bits of the mantissa and absorb the rest into the exponent
    // This avoids precision loss even with a multiple-precision mantissa
    int bits = static_cast<int>(mantissa_.bitLength());
    int shift = 0;
    Int mantissa_top = mantissa_;

    if (bits > 53) {
        shift = bits - 53;
        mantissa_top = mantissa_ >> shift;
    }

    double result = mantissa_top.toDouble();  // 53 bits or fewer, so it fits exactly in a double

    // Apply the total exponent = exponent_ + shift via ldexp
    int64_t total_exp = exponent_ + static_cast<int64_t>(shift);
    if (total_exp > 1023) {
        return is_negative_ ? -std::numeric_limits<double>::infinity() : std::numeric_limits<double>::infinity();
    }
    if (total_exp < -1074) {
        return is_negative_ ? -0.0 : 0.0;
    }
    result = std::ldexp(result, static_cast<int>(total_exp));

    return is_negative_ ? -result : result;
}

Int Float::toInt() const {
    // Handle special values
    if (isNaN() || isInfinity()) {
        throw std::domain_error("Cannot convert NaN or Infinity to Int");
    }

    // Handle zero
    if (isZero()) return Int(0);

    // Convert the floating-point number to an integer (truncating the fractional part)
    Int result = mantissa_;

    // Apply the exponent
    if (exponent_ > 0) {
        // Positive exponent: left shift (multiplication)
        result <<= static_cast<int>(exponent_);
    }
    else if (exponent_ < 0) {
        // Negative exponent: right shift (division)
        result >>= static_cast<int>(-exponent_);
    }

    // Apply the sign
    if (is_negative_) {
        result = -result;
    }

    return result;
}

int Float::bitLength() const {
    if (isZero() || isNaN() || isInfinity()) {
        return 0;
    }

    // Account for the bit length of the multiple-precision integer part and the exponent
    int mantissa_bits = static_cast<int>(mantissa_.bitLength());

    // Compute the bit length accounting for the exponent
    return mantissa_bits + static_cast<int>(exponent_);
}

Float& Float::operator<<=(int shift) {
    if (isZero() || isNaN() || isInfinity()) {
        return *this;
    }

    // A left shift adds to the exponent
    exponent_ += shift;
    checkExponentBounds();
    return *this;
}

Float& Float::operator>>=(int shift) {
    if (isZero() || isNaN() || isInfinity()) {
        return *this;
    }

    // A right shift subtracts from the exponent
    exponent_ -= shift;
    checkExponentBounds();
    return *this;
}

Float operator<<(const Float& value, int shift) {
    Float result = value;
    result <<= shift;
    return result;
}

Float operator>>(const Float& value, int shift) {
    Float result = value;
    result >>= shift;
    return result;
}

//======================================================================
// Special value generation
//======================================================================

Float Float::positiveInfinity() {
    Float result;
    result.is_infinity_ = true;
    result.is_negative_ = false;
    result.effective_bits_ = 0;
    result.requested_bits_ = 0;
    return result;
}

Float Float::negativeInfinity() {
    Float result;
    result.is_infinity_ = true;
    result.is_negative_ = true;
    result.effective_bits_ = 0;
    result.requested_bits_ = 0;
    return result;
}

Float Float::nan() {
    Float result;
    result.is_nan_ = true;
    result.effective_bits_ = 0;
    result.requested_bits_ = 0;
    return result;
}

Float Float::epsilon(int precision) {
    if (precision <= 0) {
        precision = defaultPrecision();
    }
    
    // Return 2^(-precision)
    int precision_bits = precisionToBits(precision);
    Int mantissa(1);
    Float result(mantissa, -precision_bits, false);
    result.effective_bits_ = precision_bits;
    result.requested_bits_ = precision_bits;
    return result;
}

Float Float::zero(int precision) {
    Float result;
    // The precision is not retained internally, but the argument is kept for interface consistency
    static_cast<void>(precision);  
    return result;
}

Float Float::one(int precision) {
    if (precision <= 0) {
        precision = defaultPrecision();
    }
    
    Int mantissa(1);
    Float result(mantissa, 0, false);
    result.setResultPrecision(precision);
    return result;
}

//======================================================================
// Algorithmic computation of mathematical constants (internal implementation)
//======================================================================
//
// The following algorithms compute mathematical constants using only the basic
// four arithmetic operations and sqrt, to avoid circular dependencies with math functions such as exp/log/sin.
//
// Dependencies:
//   π   ← Chudnovsky series (four arithmetic operations + sqrt only)
//   e   ← factorial series Σ 1/n! (four arithmetic operations only)
//   log2  ← 2·atanh(1/3)  (four arithmetic operations only)
//   log10 ← 6·atanh(1/3) + 2·atanh(1/9) (four arithmetic operations only)
//
// This breaks the exp→log2, log→exp cycle.
//======================================================================

namespace {

// Decimal digit count → bit count conversion (equivalent to precisionToBits, a replacement for the private method)
inline int decimalToBits(int precision) {
    return static_cast<int>(std::ceil(precision * 3.32192809488736));
}

// ==========================================
// Binary Splitting (BS) helper functions
// Speeds up constant computation: recursively split-evaluate the series with Int integer arithmetic,
// performing a single Float division only at the final stage. Computation cost O(M(n)·log N).
// ==========================================

// BS for Chudnovsky π
// π = 426880·√10005 · Q / T
// where T/Q = Σ_{k=0}^{N} (-1)^k · (6k)!·(A+Bk) / ((3k)!·(k!)³·C^{3k})
// A = 13591409, B = 545140134, C = 640320
// Parallel binary splitting threshold: if the term count is at least this, compute left and right on separate threads
// 1M digits → ~71K terms; with threshold 1000, the top ~6 levels are parallelized
static constexpr int64_t BS_PARALLEL_THRESHOLD = 1000;

// 2-way merge: T = TL*QR + PL*TR, Q = QL*QR, P = PL*PR
// When parallel=true, execute TL*QR and PL*TR in parallel
// Since QR is used in both TL*QR and QL*QR, cache and reuse the forward NTT
static void bs_merge2(Int& P, Int& Q, Int& T,
                      Int& PL, Int& QL, Int& TL,
                      Int& PR, Int& QR, Int& TR,
                      bool need_P, bool parallel) {
    if (parallel) {
        // NTT cache for QR: reuse from TL*QR → QL*QR (saves 1 NTT)
        // PL*TR is computed after TL*QR + cache reuse
        prime_ntt::NttCache qr_cache;
        IntOps::mulAbsCached(TL, QR, T, qr_cache);
        if (TL.isNegative() != QR.isNegative()) T = -T;
        T += PL * TR;

        IntOps::mulAbsCached(QL, QR, Q, qr_cache);
        if (QL.isNegative() != QR.isNegative()) Q = -Q;
    } else {
        // YC-2: compute T = TL*QR + PL*TR with NTT fusion
        // (falls back internally when the signs differ or below the NTT threshold)
        IntOps::mulAdd(TL, QR, PL, TR, T);
        Q = QL * QR;
    }
    if (need_P) P = PL * PR;
}

// Generic BS template: call LeafFn(k, P, Q, T) for each term and
// merge recursively with bs_merge2. Supports 4-way parallel unrolling.
// When need_P=false, omit computing P at the outermost merge.
template<typename LeafFn>
static void bs_recurse(int64_t a, int64_t b, Int& P, Int& Q, Int& T,
                       const LeafFn& leaf, bool need_P = true) {
    if (b - a == 1) {
        leaf(a, P, Q, T);
        return;
    }

    // 4-way recursive unrolling
    if (b - a >= BS_PARALLEL_THRESHOLD && b - a >= 4) {
        int64_t len = b - a;
        int64_t m1 = a + len / 4, m2 = a + len / 2, m3 = a + 3 * len / 4;

        Int P1,Q1,T1, P2,Q2,T2, P3,Q3,T3, P4,Q4,T4;
        auto f2 = threadPool().submit([&]() { bs_recurse(m1, m2, P2, Q2, T2, leaf); });
        auto f3 = threadPool().submit([&]() { bs_recurse(m2, m3, P3, Q3, T3, leaf); });
        auto f4 = threadPool().submit([&]() { bs_recurse(m3, b,  P4, Q4, T4, leaf); });
        bs_recurse(a, m1, P1, Q1, T1, leaf);
        f2.get(); f3.get(); f4.get();

        Int PL,QL,TL, PR,QR,TR;
        auto fR = threadPool().submit([&]() {
            bs_merge2(PR, QR, TR, P3, Q3, T3, P4, Q4, T4, true, true);
        });
        bs_merge2(PL, QL, TL, P1, Q1, T1, P2, Q2, T2, true, true);
        fR.get();

        bs_merge2(P, Q, T, PL, QL, TL, PR, QR, TR, need_P, true);
        return;
    }

    int64_t m = (a + b) / 2;
    Int PL, QL, TL, PR, QR, TR;

    if (b - a >= BS_PARALLEL_THRESHOLD) {
        auto future_right = threadPool().submit([&]() {
            bs_recurse(m, b, PR, QR, TR, leaf);
        });
        bs_recurse(a, m, PL, QL, TL, leaf);
        future_right.get();
        bs_merge2(P, Q, T, PL, QL, TL, PR, QR, TR, need_P, true);
    } else {
        bs_recurse(a, m, PL, QL, TL, leaf);
        bs_recurse(m, b, PR, QR, TR, leaf);
        bs_merge2(P, Q, T, PL, QL, TL, PR, QR, TR, need_P, false);
    }
}

// Chudnovsky leaf: k=0 → (1,1,A), k≥1 → (p(k), q(k), (-1)^k·p(k)·(A+Bk))
static constexpr auto chudnovsky_leaf = [](int64_t k, Int& P, Int& Q, Int& T) {
    if (k == 0) {
        P = Int(1);
        Q = Int(1);
        T = Int(int64_t(13591409));
    } else {
        int64_t k6 = 6 * k;
        int64_t p_lo = (k6 - 5) * (k6 - 4) * (k6 - 3);
        int64_t p_hi = (k6 - 2) * (k6 - 1) * k6;
        P = Int(p_lo) * Int(p_hi);

        int64_t k3 = 3 * k;
        int64_t q_fac = (k3 - 2) * (k3 - 1) * k3;
        int64_t k_cubed = k * k * k;
        Q = Int(q_fac) * Int(k_cubed);
        Q *= Int(int64_t(262537412640768000LL)); // 640320³

        T = P * Int(int64_t(13591409 + 545140134LL * k));
        if (k % 2 != 0) T = -T;
    }
};

// BS for e = Σ_{k=0}^{N} 1/k!
// p(k) = 1 (constant), q(k) = k+1
// Since P is always 1, omit it and track only (Q, T)
// 2-way merge for factorial BS: T = TL*QR + TR, Q = QL*QR
static void fac_merge2(Int& Q, Int& T,
                       Int& QL, Int& TL, Int& QR, Int& TR) {
    // NTT cache for QR: reuse from TL*QR → QL*QR
    size_t min_limbs = std::min({TL.size(), QL.size(), QR.size()});
    if (min_limbs >= mpn::PRIME_NTT_THRESHOLD) {
        prime_ntt::NttCache qr_cache;
        IntOps::mulAbsCached(TL, QR, T, qr_cache);
        if (TL.isNegative() != QR.isNegative()) T = -T;
        T += TR;

        IntOps::mulAbsCached(QL, QR, Q, qr_cache);
        if (QL.isNegative() != QR.isNegative()) Q = -Q;
    } else {
        T = TL * QR;
        T += TR;
        Q = QL * QR;
    }
}

static void factorial_bs(int64_t a, int64_t b, Int& Q, Int& T) {
    if (b - a == 1) {
        Q = Int(a + 1);
        T = Int(a + 1);
        return;
    }

    // 4-way unrolling
    if (b - a >= BS_PARALLEL_THRESHOLD && b - a >= 4) {
        int64_t len = b - a;
        int64_t m1 = a + len / 4, m2 = a + len / 2, m3 = a + 3 * len / 4;

        Int Q1, T1, Q2, T2, Q3, T3, Q4, T4;
        auto f2 = threadPool().submit([&]() { factorial_bs(m1, m2, Q2, T2); });
        auto f3 = threadPool().submit([&]() { factorial_bs(m2, m3, Q3, T3); });
        auto f4 = threadPool().submit([&]() { factorial_bs(m3, b,  Q4, T4); });
        factorial_bs(a, m1, Q1, T1);
        f2.get(); f3.get(); f4.get();

        Int QL, TL, QR, TR;
        auto fR = threadPool().submit([&]() { fac_merge2(QR, TR, Q3, T3, Q4, T4); });
        fac_merge2(QL, TL, Q1, T1, Q2, T2);
        fR.get();

        fac_merge2(Q, T, QL, TL, QR, TR);
        return;
    }

    int64_t m = (a + b) / 2;
    Int QL, TL, QR, TR;

    if (b - a >= BS_PARALLEL_THRESHOLD) {
        auto future_right = threadPool().submit([&]() {
            factorial_bs(m, b, QR, TR);
        });
        factorial_bs(a, m, QL, TL);
        future_right.get();
    } else {
        factorial_bs(a, m, QL, TL);
        factorial_bs(m, b, QR, TR);
    }

    fac_merge2(Q, T, QL, TL, QR, TR);
}

// BS for atanh(1/n) = (1/n) · Σ_{k=0}^{N} (1/n²)^k / (2k+1)
// t_{k+1}/t_k = (2k+1) / ((2k+3)·n²)
// p(k) = 2k+1, q(k) = (2k+3)·n²
// atanh_recip_bs: capture n_sq via bs_recurse + lambda
static void atanh_recip_bs(int64_t a, int64_t b, int64_t n_sq,
                           Int& P, Int& Q, Int& T) {
    bs_recurse(a, b, P, Q, T, [n_sq](int64_t k, Int& P, Int& Q, Int& T) {
        P = Int(int64_t(2 * k + 1));
        Q = Int(int64_t((2 * k + 3) * n_sq));
        T = Q;
    });
}

} // anonymous namespace (temporarily closed: give computeAtanhReciprocal external linkage)

// Computation of atanh(1/n) (Binary Splitting)
// atanh(1/n) = Σ_{k=0}^{∞} 1/((2k+1) · n^(2k+1))
// Convergence rate: each term yields 2·log₂(n) bits of precision
// External linkage: referenced by the log multi-prime in FloatMath.cpp
Float computeAtanhReciprocal(int n, int precision) {
    int wp = precision + 15;
    int precision_bits = decimalToBits(wp);
    int N = static_cast<int>(std::ceil(
        static_cast<double>(precision_bits) / (2.0 * std::log2(static_cast<double>(n))))) + 5;

    int64_t n_sq = static_cast<int64_t>(n) * n;
    Int P, Q, T;
    atanh_recip_bs(0, N, n_sq, P, Q, T);

    // atanh(1/n) = (1/n) · T / Q
    Float t_f(T);
    t_f.truncateToApprox(wp);
    Float q_f(Q);
    q_f.truncateToApprox(wp);
    Float result = t_f / q_f / Float(n);
    result.setResultPrecision(precision);
    return result;
}

namespace { // anonymous namespace reopened

// Computation of π: Chudnovsky algorithm
//
// π = 426880·√10005 / Σ_{k=0}^{N} S_k
//
// Here S_k = P_k · Q_k:
//   Q_k = 13591409 + 545140134·k
//   P_0 = 1
//   P_{k+1}/P_k = -(6k+1)(6k+2)(6k+3)(6k+4)(6k+5)(6k+6) / [(3k+1)(3k+2)(3k+3)·(k+1)³·640320³]
//
// Each term yields about 14.18 digits of precision (very fast convergence)
//
// [Source] Chudnovsky brothers (1988)
//         640320 = 2⁶·3·5·23·29, 640320³/2 = 426880²·10005
Float computePi(int precision) {
    int wp = precision + 20;

    // Each term ≈ 14.18 digits → number of terms = precision/14 + margin
    int max_terms = precision / 14 + 3;

    // Run √10005 and BS in parallel (sqrt is completely independent of BS)
    Float sqrt10005;
    std::thread sqrt_thread([&sqrt10005, wp]() {
        sqrt10005 = sqrt(Float(10005), wp);
        sqrt10005.truncateToApprox(wp);
    });

    // Compute Σ (-1)^k·(6k)!·(A+Bk)/((3k)!·(k!)³·C^{3k}) by Binary Splitting
    // At the top level P is not needed (only Q, T used) → one fewer large multiplication
    Int P, Q, T;
    bs_recurse(0, max_terms + 1, P, Q, T, chudnovsky_leaf, /*need_P=*/false);

    // Wait for √10005 to complete
    sqrt_thread.join();

    Float t_f(T);
    t_f.truncateToApprox(wp);
    T = Int();  // immediately release the BS-result Int
    Float q_f(Q);
    q_f.truncateToApprox(wp);
    Q = Int();  // immediately release the BS-result Int

    Float result = Float(426880) * sqrt10005 * q_f / t_f;
    result.setResultPrecision(precision);
    return result;
}

// Computation of e: factorial series
// e = Σ_{n=0}^{∞} 1/n! = 1 + 1 + 1/2 + 1/6 + 1/24 + ...
// Using exp(1) would create a log2 → exp → log2 cycle, so compute directly from the series
Float computeE(int precision) {
    int wp = precision + 15;

    // Term count estimate: N! > 2^{wp_bits} → by Stirling, N·log₂(N/e) > wp_bits
    // Fixed-point iteration: N = wp_bits / (log₂N - log₂e) + 10
    // Allow oscillation and judge convergence with |difference| ≤ 2 (old: early exit at N_new >= N → insufficient N)
    int wp_bits = decimalToBits(wp);
    int N = wp_bits;
    for (int i = 0; i < 20; ++i) {
        double logN = std::log2(std::max(static_cast<double>(N), 2.0));
        int N_new = static_cast<int>(wp_bits / std::max(logN - 1.4427, 1.0)) + 10;
        if (std::abs(N_new - N) <= 2) { N = std::max(N_new, N); break; }
        N = N_new;
    }

    // Compute Σ_{k=0}^{N} 1/k! by Binary Splitting
    Int Q, T;
    factorial_bs(0, N, Q, T);

    // e = T / Q
    Float t_f(T);
    t_f.truncateToApprox(wp);
    Float q_f(Q);
    q_f.truncateToApprox(wp);
    Float result = t_f / q_f;
    result.setResultPrecision(precision);
    return result;
}

// Computation of log(2)
// log(2) = 2·atanh(1/3)
// Derivation: (1+1/3)/(1-1/3) = (4/3)/(2/3) = 2 → log(2) = 2·atanh(1/3)
Float computeLog2(int precision) {
    int wp = precision + 10;
    Float atanh3 = computeAtanhReciprocal(3, wp);
    Float result = atanh3 * Float(2);
    result.setResultPrecision(precision);
    return result;
}

// Computation of log(10)
// log(10) = 6·atanh(1/3) + 2·atanh(1/9)
// Derivation:
//   log(2)   = 2·atanh(1/3)
//   log(5/4) = 2·atanh(1/9)  ∵ (1+1/9)/(1-1/9) = (10/9)/(8/9) = 5/4
//   log(10)  = log(2) + log(5) = log(2) + 2·log(2) + log(5/4)
//            = 3·log(2) + 2·atanh(1/9)
//            = 6·atanh(1/3) + 2·atanh(1/9)
Float computeLog10(int precision) {
    int wp = precision + 10;
    Float atanh3 = computeAtanhReciprocal(3, wp);
    Float atanh9 = computeAtanhReciprocal(9, wp);
    Float result = Float(6) * atanh3 + Float(2) * atanh9;
    result.setResultPrecision(precision);
    return result;
}

// Extended BS for Brent-McMillan γ
// u_k = (N^k/k!)^2, U = Σ u_k, S = Σ u_k·H_k
// p(k) = N², q(k) = (k+1)²
// 6 variables: P (ratio product), Q (denominator product), D (harmonic denominator=Π(j+1)),
//        B (harmonic numerator=D·H), T (U numerator), S_bs (S numerator)
// γ = S_bs/(T·D) - log(N)
// 2-way merge for euler BS
static void euler_merge2(Int& P, Int& Q, Int& D, Int& B, Int& T, Int& S_bs,
                         Int& PL, Int& QL, Int& DL, Int& BL, Int& TL, Int& SL,
                         Int& PR, Int& QR, Int& DR, Int& BR, Int& TR, Int& SR) {
    P = PL * PR;
    Q = QL * QR;
    D = DL * DR;
    B = BL * DR + DL * BR;
    T = TL * QR;
    T += PL * TR;
    Int QR_DR = QR * DR;
    Int TR_DR = TR * DR;
    S_bs = SL * QR_DR + PL * (SR * DL + BL * TR_DR);
}

static void euler_bs(int64_t a, int64_t b, int64_t N_sq,
                     Int& P, Int& Q, Int& D, Int& B, Int& T, Int& S_bs) {
    if (b - a == 1) {
        int64_t k = a;
        P = Int(N_sq);
        Q = Int((k + 1) * (k + 1));
        D = Int(k + 1);
        B = Int(1);
        T = Q;
        S_bs = Int(0);
        return;
    }

    // 4-way unrolling
    if (b - a >= BS_PARALLEL_THRESHOLD && b - a >= 4) {
        int64_t len = b - a;
        int64_t m1 = a + len / 4, m2 = a + len / 2, m3 = a + 3 * len / 4;

        Int P1,Q1,D1,B1,T1,S1, P2,Q2,D2,B2,T2,S2;
        Int P3,Q3,D3,B3,T3,S3, P4,Q4,D4,B4,T4,S4;
        auto f2 = threadPool().submit([&]() { euler_bs(m1, m2, N_sq, P2,Q2,D2,B2,T2,S2); });
        auto f3 = threadPool().submit([&]() { euler_bs(m2, m3, N_sq, P3,Q3,D3,B3,T3,S3); });
        auto f4 = threadPool().submit([&]() { euler_bs(m3, b,  N_sq, P4,Q4,D4,B4,T4,S4); });
        euler_bs(a, m1, N_sq, P1,Q1,D1,B1,T1,S1);
        f2.get(); f3.get(); f4.get();

        Int PL,QL,DL,BL,TL,SL, PR,QR,DR,BR,TR,SR;
        auto fR = threadPool().submit([&]() {
            euler_merge2(PR,QR,DR,BR,TR,SR, P3,Q3,D3,B3,T3,S3, P4,Q4,D4,B4,T4,S4);
        });
        euler_merge2(PL,QL,DL,BL,TL,SL, P1,Q1,D1,B1,T1,S1, P2,Q2,D2,B2,T2,S2);
        fR.get();

        euler_merge2(P,Q,D,B,T,S_bs, PL,QL,DL,BL,TL,SL, PR,QR,DR,BR,TR,SR);
        return;
    }

    int64_t m = (a + b) / 2;
    Int PL, QL, DL, BL, TL, SL;
    Int PR, QR, DR, BR, TR, SR;

    if (b - a >= BS_PARALLEL_THRESHOLD) {
        auto future_right = threadPool().submit([&]() {
            euler_bs(m, b, N_sq, PR, QR, DR, BR, TR, SR);
        });
        euler_bs(a, m, N_sq, PL, QL, DL, BL, TL, SL);
        future_right.get();
    } else {
        euler_bs(a, m, N_sq, PL, QL, DL, BL, TL, SL);
        euler_bs(m, b, N_sq, PR, QR, DR, BR, TR, SR);
    }

    euler_merge2(P,Q,D,B,T,S_bs, PL,QL,DL,BL,TL,SL, PR,QR,DR,BR,TR,SR);
}

// Computation of the Euler-Mascheroni constant γ (Brent-McMillan + Binary Splitting)
// γ = S/U - log(N) where U = Σ(N^k/k!)^2, S = Σ(N^k/k!)^2·H_k
Float computeEulerGamma(int precision) {
    int wp = precision + 20;

    // Brent-McMillan B1 parameter. To make the error ~ π·e^{-4N} at most 2^{-p_bits},
    // 4N > p_bits·ln2 → N > p_bits/5.77. To be safe, take N ≈ p_bits/4.
    // BUGFIX (2026-05-30): previously N = wp/4 misapplied wp (still a decimal digit count) to a
    // bit-based formula (the same kind of unit mixup as efe6db2c). As a result euler(200) produced 112 digits
    // and euler(400) produced 199 digits, only ~56% of the request. Fixed to be based on p_bits.
    int p_bits = decimalToBits(wp);
    int N = p_bits / 4 + 10;
    int max_k = 4 * N + 100;

    int64_t N_sq = static_cast<int64_t>(N) * N;
    Int P, Q, D, B, T, S;
    euler_bs(0, max_k, N_sq, P, Q, D, B, T, S);

    // γ = S/(T·D) - log(N)
    Float s_f(S); s_f.truncateToApprox(wp);
    Float t_f(T); t_f.truncateToApprox(wp);
    Float d_f(D); d_f.truncateToApprox(wp);
    Float ratio = s_f / (t_f * d_f);

    Float logN = sangi::log(Float(N), wp);
    Float result = ratio - logN;
    result.setResultPrecision(precision);
    return result;
}

// Computation of the Catalan constant G
// G = Σ_{n=0}^{∞} (-1)^n / (2n+1)^2
// Accelerated with the Euler summation method:
//   partial sum S_k = Σ_{j=0}^{k} (-1)^j / (2j+1)^2
//   E_n = Σ_{k=0}^{n} w_k · S_k  where w_k = C(n,k) / 2^n
//   (3+2√2)^n > 2^wp achieves wp bits of precision
//======================================================================
// Catalan constant — Lupas (2000) Binary Splitting formula
//======================================================================
// K = (1/64) Σ_{k=1}^∞ (-1)^{k-1} · 2^{8k} · (40k²-24k+3) · (2k)!³ · k!²
//     / (k³ · (2k-1) · (4k)!²)
// Convergence: ~2 bits/term (ratio → 1/4)
// BS complexity: O(M(p) · log²(p))
//
// BSR decomposition (j = k-1, j≥0):
//   j=0: P=1, Q=1, T = poly(1) = 19
//   j≥1: ratio w_k/w_{k-1} where k = j+1:
//     p(j) = 2048 · (2j+1)² · (j+1)² · j³ · (2j-1)
//     q(j) = [(4j+1)(4j+2)(4j+3)(4j+4)]²
//     poly(j) = 40(j+1)² - 24(j+1) + 3 = 40j² + 56j + 19
//   K = T / (18 · Q)

// Catalan leaf (shifted index j = k-1)
static constexpr auto catalan_lupas_leaf = [](int64_t j, Int& P, Int& Q, Int& T) {
    int64_t jp1 = j + 1;
    int64_t poly = 40 * jp1 * jp1 - 24 * jp1 + 3;
    if (j == 0) {
        P = Int(1);
        Q = Int(1);
        T = Int(poly);  // 19
    } else {
        Int j_int(j);
        Int j3 = j_int * j_int * j_int;
        Int jp1_int(jp1);
        Int twoj_p1(2 * j + 1);
        Int twoj_m1(2 * j - 1);

        P = Int(2048) * twoj_p1 * twoj_p1 * jp1_int * jp1_int
            * j3 * twoj_m1;

        Int q_base = Int(4*j+1) * Int(4*j+2) * Int(4*j+3) * Int(4*j+4);
        Q = q_base * q_base;

        T = P * Int(poly);
        if (j % 2 != 0) T = -T;
    }
};

Float computeCatalan(int precision) {
    int wp = precision + 20;
    int precision_bits = decimalToBits(wp);

    int max_terms = precision_bits / 2 + 20;

    Int P, Q, T;
    bs_recurse(0, max_terms + 1, P, Q, T, catalan_lupas_leaf);

    // K = T / (18 · Q)
    // (w_1 = 32/9, K = (1/64)·w_1·T/Q = 32/(576)·T/Q = T/(18Q))
    Float t_f(T);
    t_f.truncateToApprox(wp);
    Float q_f(Q);
    q_f.truncateToApprox(wp);
    Float result = t_f / (q_f * Float(18));
    result.setResultPrecision(precision);
    return result;
}

//======================================================================
// ζ(3) — Amdeberhan-Zeilberger formula (Binary Splitting)
//======================================================================
// ζ(3) = Σ_{n=0}^∞ (-1)^n · (n!)^{10} · (205n² + 250n + 77) / (64 · ((2n+1)!)^5)
// Convergence: ~10 bits/term (ratio → 1/1024)
// BS complexity: O(M(p) · log²(p))
// BSR decomposition:
//   ratio: a_n/a_{n-1} = (-1) · n^{10} / ((2n)(2n+1))^5
//   poly(n) = 205n² + 250n + 77
//   a_0 = poly(0)/64 = 77/64
//   ζ(3) = (1/64) · T/Q (BS result)

// ζ(3) leaf: p(k) = k^{10}, q(k) = (2k(2k+1))^5, poly(k) = 205k²+250k+77
static constexpr auto zeta3_leaf = [](int64_t k, Int& P, Int& Q, Int& T) {
    int64_t poly = 205 * k * k + 250 * k + 77;
    if (k == 0) {
        P = Int(1);
        Q = Int(1);
        T = Int(poly);  // 77
    } else {
        if (k <= 6000) {
            int64_t k5 = k * (int64_t)k * k * k * k;
            P = Int(k5) * Int(k5);
        } else {
            Int ki(k);
            Int k2 = ki * ki;
            Int k5 = k2 * k2 * ki;
            P = k5 * k5;
        }
        int64_t base_q = 2 * k * (2 * k + 1);
        if (base_q <= 6000) {
            int64_t b5 = base_q * base_q * base_q * base_q * base_q;
            Q = Int(b5);
        } else if (base_q <= 100000LL) {
            int64_t b2 = base_q * base_q;
            Q = Int(b2) * Int(b2) * Int(base_q);
        } else {
            Int bq(base_q);
            Int b2 = bq * bq;
            Q = b2 * b2 * bq;
        }
        T = P * Int(poly);
        if (k % 2 != 0) T = -T;
    }
};

Float computeZeta3(int precision) {
    int wp = precision + 20;
    int precision_bits = decimalToBits(wp);

    int max_terms = static_cast<int>(std::ceil(
        static_cast<double>(precision_bits) / 10.0)) + 10;

    Int P, Q, T;
    bs_recurse(0, max_terms + 1, P, Q, T, zeta3_leaf);

    // ζ(3) = (1/64) · T/Q
    Float t_f(T);
    t_f.truncateToApprox(wp);
    Float q_f(Q);
    q_f.truncateToApprox(wp);
    Float result = t_f / (q_f * Float(64));
    result.setResultPrecision(precision);
    return result;
}

//======================================================================
// ζ(5) — Borwein acceleration (Cohen-Villegas-Zagier 2000)
//======================================================================
// Chebyshev acceleration of η(s) = Σ_{k=0}^∞ (-1)^k / (k+1)^s:
//   d_k = Σ_{j=0}^{k} C(n, j)  (partial sum of binomial coefficients, d_n = 2^n)
//   η(s) ≈ (-1/d_n) · Σ_{k=0}^{n-1} (-1)^k · (d_k - d_n) / (k+1)^s
//   ζ(s) = η(s) / (1 - 2^{1-s})
// Precision: ~2.54 bits/term, complexity: O(n · p) where n ≈ p_bits / 2.54

Float computeZeta5(int precision) {
    int wp = precision + 20;
    int precision_bits = decimalToBits(wp);

    // n = ceil(precision_bits / 2.54) + margin
    int n = static_cast<int>(std::ceil(
        static_cast<double>(precision_bits) / 2.54)) + 10;

    // Compute d_k = Σ_{j=0}^{k} C(n,j) as an integer
    // d_n = 2^n, C(n, j+1) = C(n,j) · (n-j) / (j+1)
    Int d_n = Int(1) << n;

    // ζ(5) = (-16/(15·d_n)) · Σ_{k=0}^{n-1} (-1)^k · (d_k - d_n) / (k+1)^5
    Float sum(0);
    Int binom(1);   // C(n, 0) = 1
    Int d_k(1);     // d[0] = C(n,0) = 1

    for (int k = 0; k < n; k++) {
        // (d_k - d_n) is negative (d_k < d_n for k < n)
        Int diff = d_k - d_n;

        // Compute (k+1)^5
        int64_t kp1 = static_cast<int64_t>(k) + 1;
        Int denom5;
        if (kp1 <= 6000) {
            int64_t kp1_5 = kp1 * kp1 * kp1 * kp1 * kp1;
            denom5 = Int(kp1_5);
        } else {
            Int ki(kp1);
            Int k2 = ki * ki;
            denom5 = k2 * k2 * ki;
        }

        // term = diff / denom5 (integer division → equivalent to Rational)
        // Convert to Float and then divide (fast since denom5 is small)
        Float term_f(diff);
        term_f.truncateToApprox(wp);
        term_f = term_f / Float(denom5);
        term_f.truncateToApprox(wp);

        if (k % 2 == 0) {
            sum = sum + term_f;
        } else {
            sum = sum - term_f;
        }

        // d_{k+1} = d_k + C(n, k+1)
        binom = binom * Int(static_cast<int64_t>(n - k));
        binom = binom / Int(static_cast<int64_t>(k + 1));  // exact integer division
        d_k = d_k + binom;
    }

    // ζ(5) = -16·sum / (15·d_n)
    //       = -16/(15·d_n) · sum
    Float d_n_f(d_n);
    d_n_f.truncateToApprox(wp);
    Float result = Float(-16) * sum / (Float(15) * d_n_f);
    result.setResultPrecision(precision);
    return result;
}

} // anonymous namespace

//======================================================================
// Public interface for mathematical constants (with thread_local cache)
//======================================================================

Float Float::pi(int precision) {
    if (precision <= 0) precision = defaultPrecision();

    thread_local Float cached;
    thread_local int cached_prec = 0;

    if (cached_prec > 0 && precision <= cached_prec) {
        Float result = cached;
        result.setResultPrecision(precision);
        return result;
    }

    cached = computePi(precision);
    cached_prec = precision;
    return cached;
}

Float Float::e(int precision) {
    if (precision <= 0) precision = defaultPrecision();

    thread_local Float cached;
    thread_local int cached_prec = 0;

    if (cached_prec > 0 && precision <= cached_prec) {
        Float result = cached;
        result.setResultPrecision(precision);
        return result;
    }

    cached = computeE(precision);
    cached_prec = precision;
    return cached;
}

Float Float::log2(int precision) {
    if (precision <= 0) precision = defaultPrecision();

    thread_local Float cached;
    thread_local int cached_prec = 0;

    if (cached_prec > 0 && precision <= cached_prec) {
        Float result = cached;
        result.setResultPrecision(precision);
        return result;
    }

    cached = computeLog2(precision);
    cached_prec = precision;
    return cached;
}

Float Float::log10(int precision) {
    if (precision <= 0) precision = defaultPrecision();

    thread_local Float cached;
    thread_local int cached_prec = 0;

    if (cached_prec > 0 && precision <= cached_prec) {
        Float result = cached;
        result.setResultPrecision(precision);
        return result;
    }

    cached = computeLog10(precision);
    cached_prec = precision;
    return cached;
}

Float Float::euler(int precision) {
    if (precision <= 0) precision = defaultPrecision();

    thread_local Float cached;
    thread_local int cached_prec = 0;

    if (cached_prec > 0 && precision <= cached_prec) {
        Float result = cached;
        result.setResultPrecision(precision);
        return result;
    }

    cached = computeEulerGamma(precision);
    cached_prec = precision;
    return cached;
}

Float Float::catalan(int precision) {
    if (precision <= 0) precision = defaultPrecision();

    thread_local Float cached;
    thread_local int cached_prec = 0;

    if (cached_prec > 0 && precision <= cached_prec) {
        Float result = cached;
        result.setResultPrecision(precision);
        return result;
    }

    cached = computeCatalan(precision);
    cached_prec = precision;
    return cached;
}

Float Float::sqrt2(int precision) {
    if (precision <= 0) precision = defaultPrecision();

    thread_local Float cached;
    thread_local int cached_prec = 0;

    if (cached_prec > 0 && precision <= cached_prec) {
        Float result = cached;
        result.setResultPrecision(precision);
        return result;
    }

    int wp = precision + 10;
    cached = sqrt(Float(2), wp);
    cached.setResultPrecision(precision);
    cached_prec = precision;
    return cached;
}

//======================================================================
// Three AGM constants: Lemniscate ω, Γ(1/4)
// From the converged values a,b,t of AGM(1, 1/√2):
//   π = (a+b)² / t
//   ω = 4(a+b) / (t·√2)  (lemniscate constant)
//   Γ(1/4) = √(ω·√(2π))
//======================================================================

Float Float::lemniscate(int precision) {
    if (precision <= 0) precision = defaultPrecision();

    thread_local Float cached;
    thread_local int cached_prec = 0;

    if (cached_prec > 0 && precision <= cached_prec) {
        Float result = cached;
        result.setResultPrecision(precision);
        return result;
    }

    // ω = π · √2 / AGM(1, 1/√2)  ... an equivalent derivation
    // Brent-Salamin AGM: a₀=1, b₀=1/√2, t₀=1/4
    // → π = (a+b)²/(4t), ω = 2(a+b)/(t·√2) · 1/2 = ...
    // Simplest relation: ω = 2π / Γ(1/4)² ... but this needs Γ(1/4)
    // Direct computation: ω = 2·AGM(1, √2)·π / ...
    //
    // Exact formula (Borwein):
    //   ω/2 = ∫₀¹ dx/√(1-x⁴) = π/(2·AGM(1, √2))
    //   ω = π / AGM(1, √2)
    int wp = precision + 20;
    Float pi_val = Float::pi(wp);
    Float sqrt2_val = Float::sqrt2(wp);
    Float ag = agm(Float::one(wp), sqrt2_val, wp);
    cached = pi_val / ag;
    cached.setResultPrecision(precision);
    cached_prec = precision;
    return cached;
}

Float Float::gamma14(int precision) {
    if (precision <= 0) precision = defaultPrecision();

    thread_local Float cached;
    thread_local int cached_prec = 0;

    if (cached_prec > 0 && precision <= cached_prec) {
        Float result = cached;
        result.setResultPrecision(precision);
        return result;
    }

    // Γ(1/4) = √(ω · √(2π))  where ω = lemniscate constant
    // Equivalent: Γ(1/4)² = ω·√(2π) = (2π)^{3/2} / ω  ... no
    // Exact relation: Γ(1/4)² · ω = 2π√2  (Legendre's relation)
    // → Γ(1/4) = √(2π√2 / ω)  ... wait
    //
    // Correct relation:
    //   ω = 2π / Γ(1/4)²  ... wrong
    //   Exact: ω/2 = B(1/4, 1/2) / 4 = Γ(1/4)·Γ(1/2) / (4·Γ(3/4))
    //   Γ(1/2) = √π, Γ(3/4)·Γ(1/4) = π/sin(π/4) = π√2
    //   → ω/2 = Γ(1/4)·√π / (4·π√2/Γ(1/4)) = Γ(1/4)²·√π / (4π√2)
    //   → ω = Γ(1/4)²·√π / (2π√2) = Γ(1/4)² / (2√(2π))
    //   → Γ(1/4)² = 2ω·√(2π)
    //   → Γ(1/4) = √(2ω·√(2π))
    int wp = precision + 20;
    Float omega = Float::lemniscate(wp);
    Float pi_val = Float::pi(wp);
    Float two_pi = Float(2) * pi_val;
    Float sqrt_2pi = sqrt(two_pi, wp);
    Float inner = Float(2) * omega * sqrt_2pi;
    cached = sqrt(inner, wp);
    cached.setResultPrecision(precision);
    cached_prec = precision;
    return cached;
}

Float Float::zeta3(int precision) {
    if (precision <= 0) precision = defaultPrecision();

    thread_local Float cached;
    thread_local int cached_prec = 0;

    if (cached_prec > 0 && precision <= cached_prec) {
        Float result = cached;
        result.setResultPrecision(precision);
        return result;
    }

    cached = computeZeta3(precision);
    cached_prec = precision;
    return cached;
}

Float Float::zeta5(int precision) {
    if (precision <= 0) precision = defaultPrecision();

    thread_local Float cached;
    thread_local int cached_prec = 0;

    if (cached_prec > 0 && precision <= cached_prec) {
        Float result = cached;
        result.setResultPrecision(precision);
        return result;
    }

    // BUGFIX (2026-05-31): computeZeta5 plateaus at ~51 digits for precision=100
    // (detected by a self-consistency audit). Delegate to the full-precision generic zeta(Float(5)).
    cached = zeta(Float(5), precision);
    cached_prec = precision;
    return cached;
}

//======================================================================
// New constants (60+ additions)
//======================================================================

// Definition macro for cached constants
#define DEFINE_CACHED_CONSTANT(name, body) \
Float Float::name(int precision) { \
    if (precision <= 0) precision = defaultPrecision(); \
    thread_local Float cached; \
    thread_local int cached_prec = 0; \
    if (cached_prec > 0 && precision <= cached_prec) { \
        Float result = cached; \
        result.setResultPrecision(precision); \
        return result; \
    } \
    int wp = precision + 15; \
    body \
    cached.setResultPrecision(precision); \
    cached_prec = precision; \
    return cached; \
}

// --- π-derived constants ---
DEFINE_CACHED_CONSTANT(half_pi,
    cached = ldexp(Float::pi(wp), -1);
)
DEFINE_CACHED_CONSTANT(quarter_pi,
    cached = ldexp(Float::pi(wp), -2);
)
DEFINE_CACHED_CONSTANT(two_pi,
    cached = ldexp(Float::pi(wp), 1);
)
DEFINE_CACHED_CONSTANT(inv_pi,
    cached = Float(1) / Float::pi(wp);
)
DEFINE_CACHED_CONSTANT(two_inv_pi,
    cached = Float(2) / Float::pi(wp);
)
DEFINE_CACHED_CONSTANT(inv_sqrt_pi,
    cached = Float(1) / sqrt(Float::pi(wp), wp);
)
DEFINE_CACHED_CONSTANT(two_inv_sqrt_pi,
    cached = Float(2) / sqrt(Float::pi(wp), wp);
)

// --- Square roots and nth roots ---
DEFINE_CACHED_CONSTANT(sqrt3,
    cached = sqrt(Float(3), wp);
)
DEFINE_CACHED_CONSTANT(sqrt5,
    cached = sqrt(Float(5), wp);
)
DEFINE_CACHED_CONSTANT(inv_sqrt2,
    cached = Float(1) / Float::sqrt2(wp);
)
DEFINE_CACHED_CONSTANT(cbrt2,
    // ∛2 = 2^(1/3): Newton's method x_{n+1} = (2x_n + 2/x_n²) / 3
    // BUGFIX (2026-05-31): seed's setPrecision does not propagate the working precision, causing a plateau
    // → setResultPrecision(wp) + residual convergence loop (same form as plastic/omega/salem).
    Float x(1.2599210498948732);  // double approximation
    x.setResultPrecision(wp);
    const int wp_bits = Float::precisionToBits(wp);
    Float prev = x;
    for (int it = 0; it < 200; it++) {
        x = divScalarF(ldexp(x, 1) + ldexp(Float(1) / (x * x), 1), int64_t(3));
        Float corr = x - prev;
        if (corr.isZero()) break;
        int64_t cb = corr.exponent() + static_cast<int64_t>(corr.mantissa().bitLength());
        if (cb < -(static_cast<int64_t>(wp_bits) + 4)) break;
        prev = x;
    }
    cached = x;
)

// --- Logarithms ---
DEFINE_CACHED_CONSTANT(ln3,
    cached = log(Float(3), wp);
)
DEFINE_CACHED_CONSTANT(ln5,
    cached = log(Float(5), wp);
)
DEFINE_CACHED_CONSTANT(log2e,
    cached = Float(1) / Float::log2(wp);   // 1/ln2 — cannot be changed, it is a Float/Float division
)
DEFINE_CACHED_CONSTANT(log10e,
    cached = Float(1) / Float::log10(wp);  // 1/ln10 — same as above
)

// --- Golden ratio and basic constants ---
DEFINE_CACHED_CONSTANT(phi,
    cached = ldexp(Float(1) + Float::sqrt5(wp), -1);
)
DEFINE_CACHED_CONSTANT(sin1,
    cached = sin(Float(1), wp);
)
DEFINE_CACHED_CONSTANT(cos1,
    cached = cos(Float(1), wp);
)
DEFINE_CACHED_CONSTANT(degree,
    cached = divScalarF(Float::pi(wp), int64_t(180));
)
DEFINE_CACHED_CONSTANT(egamma_exp,
    cached = exp(Float::euler(wp), wp);
)

// --- ζ(7) ---
// Delegate to the generic zeta(s, precision) (Euler-transform accelerated, full-precision convergence).
// The old "simplified" Euler-Maclaurin had only one correction term and plateaued at ~26 digits
// (2026-05-30, a remaining roadmap P0 task). zeta() was made full-precision in this session.
DEFINE_CACHED_CONSTANT(zeta7,
    cached = zeta(Float(7), wp);
)

// --- Glaisher-Kinkelin A ---
// log(A) = 1/12 - ζ'(-1) = 1/12 - (ζ'(−1))
// ζ'(-1) = -1/12 + ln(2π)/2 - 1/2 - (12·ζ'(2))/(2π²) ... complex
// Simplified: log(A) = 1/12 - ln(2π)/2 + γ/2 + ln(2)/12 ... no
// Exact: log(A) = 1/12 − ln(Γ(1/2)) = ...
// Cohen (2000): 12·log(A) = 1 + 6(computation of ζ'(−1)/ζ(−1)) ...
// Most practical: log A = 1/12 − ζ'(−1)
//   ζ'(−1) = −1/12 + ∫₀^∞ t/(e^{2πt}−1) · ln(t) dt  ... avoid numerical integration
// Sondow-Hadjicostas: log A = 1/12 − Σ_{n=1}^{∞} (ln n)/(n²+n)·(−1)^{n+1}·something
// Practical approach: numerical computation of ζ'(s) at s = −1
//
// Simple implementation: approximate by Newton iteration from the known few-digit value 1.28242712910...
// OR: log(A) = -ζ'(-1) + 1/12
//   compute ζ'(-1) by Borwein's acceleration method
//
// Simplest: Gosper (1997): log(A) = 1/12 − (γ + log(2π))/2 + Σ ζ(2k+1)/(4k+2)·(-1)^k/(2π)^{2k}
// Weisstein: log(A) = 1/8 − Σ_{n=1}^{∞} log(n)·n·(−1)^{n+1}·...
//
// Practical: Amdeberhan-Zeilberger + binary splitting is too complex
// Here we use Adamchik (2003)'s formula:
// log(A) = 1/4 − Σ_{k=2}^{∞} (−1)^k · k · ζ(1−k) · log(k)/(k−1)  ... diverges
//
// Last resort: direct definition + acceleration
// log A = Σ_{k=1}^{N} k·log(k) − (N²/2 + N/2 + 1/12)·log(N) + N²/4
// + Σ Bernoulli correction
DEFINE_CACHED_CONSTANT(glaisher,
    // log A = Σ_{k=1}^N k·log k − (N²/2 + N/2 + 1/12)·log N + N²/4
    //         + Σ_{m≥1} B_{2m+2}/((2m+2)(2m+1)(2m)·N^{2m})
    // The final Σ is the Euler-Maclaurin Bernoulli correction (derived from the EM expansion of f(x)=x ln x).
    // BUGFIX (2026-05-30, P0 remaining task): the old implementation lacked the correction term and plateaued at ~10 digits
    // (and 1/12 was miscalculated due to a divScalarF bug, giving a catastrophically wrong value). Adding the correction series
    // makes it full-precision. With the correction, N in the direct sum can also be reduced.
    const int wp_bits = Float::precisionToBits(wp);
    int N = std::max(wp, 60);
    Float sum(0);
    for (int k = 1; k <= N; k++) {
        Float kf(k);
        kf.setPrecision(wp);
        sum += kf * log(kf, wp);
    }
    Float Nf(N);
    Nf.setPrecision(wp);
    Float logN = log(Nf, wp);
    Float N2 = Nf * Nf;
    Float inv12(1); inv12.setResultPrecision(wp); inv12 = inv12 / Float(12);
    sum -= (ldexp(N2, -1) + ldexp(Nf, -1) + inv12) * logN;
    sum += ldexp(N2, -2);  // up to here is D(N) → an approximation of ln A

    // The Bernoulli numbers B_{2k} reuse FloatMath.cpp's verified computeBernoulliNumbers
    // (result[k]=B_{2k}). Since convergence/divergence detection terminates early, max_m is an upper bound.
    int max_m = wp / 2 + 30;
    std::vector<Float> bern = computeBernoulliNumbers(max_m + 1, wp);

    // Euler-Maclaurin correction series: Σ_{m≥1} B_{2m+2}/((2m+2)(2m+1)(2m)) · N^{-2m}
    Float Ninv2 = Float(1) / N2;  // N^{-2} (N2 is at wp precision → Float/Float gives full precision)
    Ninv2.truncateToApprox(wp);
    Float Npow = Ninv2;
    int64_t prev_bits = (std::numeric_limits<int64_t>::max)();
    for (int m = 1; m <= max_m; m++) {
        int64_t denom_i = static_cast<int64_t>(2 * m + 2) * (2 * m + 1) * (2 * m);
        Float term = bern[m + 1] / Float(denom_i) * Npow;  // B_{2m+2}/denom · N^{-2m}
        term.truncateToApprox(wp);
        int64_t tb = term.isZero() ? (std::numeric_limits<int64_t>::min)()
                   : term.exponent() + static_cast<int64_t>(term.mantissa().bitLength());
        if (m >= 2 && tb > prev_bits) break;  // divergence detection (passed the smallest term of the asymptotic series)
        sum += term;
        prev_bits = tb;
        if (tb < -(static_cast<int64_t>(wp_bits) + 5)) break;  // converged
        Npow = Npow * Ninv2;
        Npow.truncateToApprox(wp);
    }
    cached = exp(sum, wp);
)

//======================================================================
// Prime-zeta/ζ(2n) acceleration helpers (full precision for constants that plateau with prime products/sieves, A-3 2026-05-31)
//
// khinchin / meissel_mertens / twin_prime / landau_ramanujan produced only ~3-7 digits with prime products or
// slow logarithmic sums (convergence ~1/N). Rewriting them all in terms of the "prime zeta
//   P(s)=Σ_p p^{-s},  Q(s)=Σ_{p≥3}p^{-s},  P_χ(s)=Σ_{p odd}χ(p)p^{-s}"
// or ζ(2n) accelerated series makes them converge geometrically with the full-precision zeta()/hurwitzZeta().
// The prime zetas are computed via Möbius inversion
//   Q(s)=Σ_m μ(m)/m·ln λ(ms),  λ(t)=(1-2^{-t})ζ(t)  (Euler product of odd primes)
//   P_χ(s)=Σ_m μ(m)/m·ln L(ms,χ^m),  L=β (m odd) / (1-2^{-t})ζ(t) (m even)
// (rapid convergence since ζ(t)→1). β is the Dirichlet beta, χ is the non-principal mod-4 character.
//======================================================================

// Möbius function μ(m)
static int mobiusMu(int m) {
    if (m == 1) return 1;
    int cnt = 0, x = m;
    for (int p = 2; static_cast<long long>(p) * p <= x; p++) {
        if (x % p == 0) {
            x /= p;
            if (x % p == 0) return 0;  // has a square factor
            cnt++;
        }
    }
    if (x > 1) cnt++;
    return (cnt & 1) ? -1 : 1;
}

// ln ζ(n) at relative wp digits. Since ζ(n)-1~2^{-n}, guard the cancellation amount (0.302·n digits).
static Float lnZetaInt(int n, int wp) {
    int wp_local = wp + static_cast<int>(0.302 * n) + 10;
    return log(zeta(Float(n), wp_local), wp_local);
}

// ln λ(t) = ln((1-2^{-t})ζ(t)) at relative wp digits (λ-1~3^{-t}, guard 0.4771·t digits of cancellation).
// Build the product at full precision and then take log → avoid cancellation of the 2^{-t} leading term.
static Float lnLambdaInt(int t, int wp) {
    int wp_local = wp + static_cast<int>(0.4771 * t) + 12;
    Float z = zeta(Float(t), wp_local);
    // ★ Since pw=2^{-t} and Float(1) are both exact, for t>1000 the early-return threshold of add/subtract
    //   (exact is treated as requested_bits=0 → floor 1000) discarded 2^{-t}, making (1-2^{-t})=1,
    //   a fatal bug. Give pw a finite requested_bits to raise the threshold.
    Float pw = ldexp(Float(1), -t);  // 2^{-t}
    pw.truncateToApprox(wp_local);   // requested_bits = precisionToBits(wp_local) (>t)
    Float lam = (Float(1) - pw) * z;  // (1-2^{-t})ζ(t)
    return log(lam, wp_local);
}

// ln β(t) at relative wp digits. β = Dirichlet beta = 4^{-t}(ζ(t,1/4)-ζ(t,3/4)).
// Since β-1~-3^{-t}, guard 0.4771·t digits of cancellation.
static Float lnBetaInt(int t, int wp) {
    int wp_local = wp + static_cast<int>(0.4771 * t) + 12;
    Float quarter = Float(1) / Float(4);
    Float three_q = Float(3) / Float(4);
    Float hz1 = hurwitzZeta(Float(t), quarter, wp_local);
    Float hz2 = hurwitzZeta(Float(t), three_q, wp_local);
    Float beta = ldexp(hz1 - hz2, -2 * t);  // 4^{-t}(ζ(t,1/4)-ζ(t,3/4))
    return log(beta, wp_local);
}

// Odd prime zeta Q(s) = Σ_{p≥3} p^{-s} = Σ_m μ(m)/m·ln λ(ms)
static Float oddPrimeZetaInt(int s, int wp) {
    const int wp_bits = Float::precisionToBits(wp);
    Float Q(0);
    int64_t lead_bits = (std::numeric_limits<int64_t>::max)();
    for (int m = 1; static_cast<int64_t>(m) * s <= 2 * (wp_bits + 16); m++) {
        int mu = mobiusMu(m);
        if (mu == 0) continue;
        Float term = lnLambdaInt(m * s, wp) / Float(m);  // ln λ(ms) ~ 3^{-ms}
        if (mu < 0) term = -term;
        term.truncateToApprox(wp);
        Q += term;
        if (!term.isZero()) {
            int64_t tb = term.exponent() + static_cast<int64_t>(term.mantissa().bitLength());
            if (lead_bits == (std::numeric_limits<int64_t>::max)()) lead_bits = tb;
            else if (tb < lead_bits - static_cast<int64_t>(wp_bits) - 8) break;
        }
    }
    return Q;
}

// mod-4 character prime zeta P_χ(s) = Σ_{p odd} χ(p) p^{-s} = Σ_m μ(m)/m·ln L(ms,χ^m)
// χ^m = χ (m odd, L=β) / χ_0 (m even, L=(1-2^{-t})ζ(t)).
static Float chiPrimeZetaInt(int s, int wp) {
    const int wp_bits = Float::precisionToBits(wp);
    Float P(0);
    int64_t lead_bits = (std::numeric_limits<int64_t>::max)();
    for (int m = 1; static_cast<int64_t>(m) * s <= 2 * (wp_bits + 16); m++) {
        int mu = mobiusMu(m);
        if (mu == 0) continue;
        int t = m * s;
        Float lnL = (m & 1) ? lnBetaInt(t, wp) : lnLambdaInt(t, wp);
        Float term = lnL / Float(m);
        if (mu < 0) term = -term;
        term.truncateToApprox(wp);
        P += term;
        if (!term.isZero()) {
            int64_t tb = term.exponent() + static_cast<int64_t>(term.mantissa().bitLength());
            if (lead_bits == (std::numeric_limits<int64_t>::max)()) lead_bits = tb;
            else if (tb < lead_bits - static_cast<int64_t>(wp_bits) - 8) break;
        }
    }
    return P;
}

// --- Khinchin K ≈ 2.6854520010... ---
// ln K = (1/ln2) · Σ_{N≥1} (ζ(2N)-1)/N · Σ_{k=1}^{2N-1} (-1)^{k+1}/k
// ζ(2N)-1 ~ 4^{-N} converges geometrically (replaces the old implementation's slow logarithmic sum Σ ln(n)ln(1-1/n²)).
DEFINE_CACHED_CONSTANT(khinchin,
    const int wp_bits = Float::precisionToBits(wp);
    Float ln2 = log(Float(2), wp);
    Float sum(0);
    Float S(1);  // S_N = Σ_{k=1}^{2N-1} (-1)^{k+1}/k,  S_1 = 1
    S.setResultPrecision(wp);
    for (int N = 1; 2 * N <= wp_bits + 20; N++) {
        if (N >= 2) {
            S -= Float(1) / Float(2 * N - 2);  // k=2N-2 (sign -)
            S += Float(1) / Float(2 * N - 1);  // k=2N-1 (sign +)
            S.truncateToApprox(wp);
        }
        Float z1 = zeta(Float(2 * N), wp) - Float(1);  // ζ(2N)-1 ~ 4^{-N}
        Float term = z1 / Float(N) * S;
        term.truncateToApprox(wp);
        sum += term;
    }
    cached = exp(sum / ln2, wp);
)

// --- Lambert W(1) = Ω (Omega constant) ---
// Ω·e^Ω = 1  →  Ω ≈ 0.5671432904...
// Halley iteration: x_{n+1} = x_n − (x_n·e^{x_n} − 1) / (e^{x_n}·(x_n+1) − (x_n+2)(x_n·e^{x_n}−1)/(2x_n+2))
DEFINE_CACHED_CONSTANT(omega,
    // BUGFIX (2026-05-31): seed's setPrecision + a fixed 1e-100 early exit plateaued at ~30 digits
    // → setResultPrecision(wp) + residual convergence loop (Halley) gives full precision (self-consistency audit).
    Float x(0.5671432904097838);
    x.setResultPrecision(wp);
    const int wp_bits = Float::precisionToBits(wp);
    for (int iter = 0; iter < 200; iter++) {
        Float ex = exp(x, wp);
        Float xex = x * ex;
        Float f = xex - Float(1);
        // Halley: Δ = f / (ex·(x+1) − (x+2)·f/(2(x+1)))
        Float denom = ex * (x + Float(1)) - (x + Float(2)) * f / (Float(2) * (x + Float(1)));
        Float corr = f / denom;
        x -= corr;
        if (corr.isZero()) break;
        int64_t cb = corr.exponent() + static_cast<int64_t>(corr.mantissa().bitLength());
        if (cb < -(static_cast<int64_t>(wp_bits) + 4)) break;
    }
    cached = x;
)

// --- Plastic number ρ ---
// the real root of x³ = x + 1 ≈ 1.3247179572...
DEFINE_CACHED_CONSTANT(plastic,
    // BUGFIX (2026-05-31): seed's setPrecision does not propagate the working precision, plateauing at ~30 digits
    // (self-consistency audit). setResultPrecision(wp) + residual convergence loop gives full precision.
    Float x(1.3247179572447460);
    x.setResultPrecision(wp);
    const int wp_bits = Float::precisionToBits(wp);
    for (int it = 0; it < 200; it++) {
        // Newton: x ← x − (x³−x−1)/(3x²−1)
        Float x2 = x * x;
        Float x3 = x2 * x;
        Float corr = (x3 - x - Float(1)) / (mulScalarF(x2, uint64_t(3)) - Float(1));
        x -= corr;
        if (corr.isZero()) break;
        int64_t cb = corr.exponent() + static_cast<int64_t>(corr.mantissa().bitLength());
        if (cb < -(static_cast<int64_t>(wp_bits) + 4)) break;
    }
    cached = x;
)

// --- Twin prime constant C₂ ---
// C₂ = Π_{p≥3 prime} (1 − 1/(p−1)²) ≈ 0.6601618...
// BUGFIX (2026-05-31): the old implementation used a finite product of a prime sieve, tail~1/limit, plateauing at ~6 digits (self-consistency audit).
// From the cyclotomic identity 1-1/(p-1)² = Π_{n≥2}(1-p^{-n})^{I(n)},  I(n)=(1/n)Σ_{d|n}μ(d)2^{n/d}
// (number of binary Lyndon words), C₂=Π_{n≥2}λ(n)^{-I(n)}, λ(n)=(1-2^{-n})ζ(n).
// ★ The naive version that directly expands ln(1-1/(p-1)²)=Σ(2-2^n)/n·p^{-n} into prime zetas has a 2^n coefficient,
//   so the double sum does not converge absolutely (Fubini fails) and converges to a wrong value at ~46 digits. The cyclotomic identity is correct.
DEFINE_CACHED_CONSTANT(twin_prime,
    // ln C₂ = -Σ_{n≥2} I(n)·ln λ(n). Terms ~ -(2/3)^n/n converge geometrically (rate-limited by p=3).
    const int wp_bits = Float::precisionToBits(wp);
    const int eff = Float::precisionToBits(wp);
    Float lnC(0);
    for (int n = 2; n <= 3 * (wp_bits + 16); n++) {
        // I(n) = (1/n) Σ_{d|n} μ(d) 2^{n/d}  (positive integer ~2^n/n)
        Float s(0);
        for (int d = 1; d <= n; d++) {
            if (n % d) continue;
            int mu = mobiusMu(d);
            if (mu == 0) continue;
            Float t = ldexp(Float(1), n / d);  // 2^{n/d} (exact)
            if (mu < 0) s -= t; else s += t;
        }
        s.setEffectiveBits(eff);              // avoid exact/exact poison + round to relative wp
        Float In = s / Float(n);
        Float term = In * lnLambdaInt(n, wp);  // I(n)·ln λ(n) ~ (2/3)^n/n
        term.truncateToApprox(wp);
        lnC -= term;
        if (!term.isZero()) {
            int64_t tb = term.exponent() + static_cast<int64_t>(term.mantissa().bitLength());
            if (tb < -(static_cast<int64_t>(wp_bits) + 4)) break;
        }
    }
    cached = exp(lnC, wp);
)

// --- Landau-Ramanujan K ≈ 0.7642236535... ---
// K = (1/√2) · Π_{p≡3(4)} (1 − p^{-2})^{−1/2}
// BUGFIX (2026-05-31): the old implementation used a finite product of the p≡3(4) prime sieve, plateauing at ~7 digits (self-consistency audit).
// ln K = -½ln2 + ¼ Σ_{n≥1}(1/n)(Q(2n)-P_χ(2n)),  P_{3,4}(s)=½(Q(s)-P_χ(s)).
// Terms ~ 9^{-n}/n converge geometrically (full precision via the prime zeta Q and the mod-4 character P_χ).
DEFINE_CACHED_CONSTANT(landau_ramanujan,
    const int wp_bits = Float::precisionToBits(wp);
    Float ln2 = log(Float(2), wp);
    Float acc(0);  // Σ_{n≥1}(1/n)(Q(2n)-P_χ(2n))
    for (int n = 1; n <= wp_bits + 16; n++) {
        Float Q = oddPrimeZetaInt(2 * n, wp);       // Σ_{p odd} p^{-2n}
        Float Pchi = chiPrimeZetaInt(2 * n, wp);    // Σ_{p odd} χ(p) p^{-2n} (<0)
        Float term = (Q - Pchi) / Float(n);         // Q-P_χ = 2·P_{3,4}(2n) > 0, no cancellation
        term.truncateToApprox(wp);
        acc += term;
        if (!term.isZero()) {
            int64_t tb = term.exponent() + static_cast<int64_t>(term.mantissa().bitLength());
            if (tb < -(static_cast<int64_t>(wp_bits) + 4)) break;
        }
    }
    Float lnK = -ldexp(ln2, -1) + ldexp(acc, -2);  // -½ln2 + ¼·acc
    cached = exp(lnK, wp);
)

// --- Meissel-Mertens M ≈ 0.2614972128... ---
// M = γ + Σ_p (ln(1−1/p) + 1/p)
// BUGFIX (2026-05-31): the old implementation used a finite sum of a prime sieve, tail~1/limit, plateauing at ~6 digits (self-consistency audit).
// Σ_p(ln(1-1/p)+1/p) = -Σ_{k≥2}P(k)/k = Σ_{n≥2} μ(n)/n·ln ζ(n) (reduced to a single sum via Möbius).
// Terms ~ 2^{-n}/n converge geometrically.
DEFINE_CACHED_CONSTANT(meissel_mertens,
    const int wp_bits = Float::precisionToBits(wp);
    Float sum = Float::euler(wp);
    for (int n = 2; n <= wp_bits + 16; n++) {
        int mu = mobiusMu(n);
        if (mu == 0) continue;
        Float term = lnZetaInt(n, wp) / Float(n);  // ln ζ(n)/n ~ 2^{-n}/n
        if (mu < 0) term = -term;
        term.truncateToApprox(wp);
        sum += term;
        if (!term.isZero()) {
            int64_t tb = term.exponent() + static_cast<int64_t>(term.mantissa().bitLength());
            if (tb < -(static_cast<int64_t>(wp_bits) + 4)) break;
        }
    }
    cached = sum;
)

// --- Bernstein β ---
// Bernstein's constant β = lim_{n→∞} 2n·E_{2n}(|x|;[-1,1]) ≈ 0.2801694990238691...
//   E_{2n} = the degree-2n best uniform (minimax) polynomial approximation error of |x|.
// There is no analytic closed form (Bernstein's conjecture β=1/(2√π)≈0.28209 was disproven by Varga-Carpenter 1985).
// But it is not "incomputable": from the error E_m of the degree-m minimax approximation of √u on [0,1],
// s_m=2m·E_m → β can be computed by 1/m² Richardson(Neville) extrapolation.
//
// The literal is 60 digits (2026-06: extended 40→48→50→60). Independently verified:
//   ① matches OEIS A073001 (Finch, Math. Constants §4.4) in the first 50 digits (digit 49=3, 50=6),
//   ② Neville extrapolation of s_m for high m (m=4..120) over 6 independent dense even-m windows → all windows agree
//      to 62 digits (conservatively adopting 60 digits).
//
// ★[2026-06-05 operator computability] The old comment's "productionization is premised on Chebyshev-basis
//   remezExchange (offline generation)" is resolved. bernstein_detail::computeBernstein in this TU implements
//   Chebyshev-basis Remez itself and computes at arbitrary precision (self-contained in the foundation TU, same approach as GKW).
//   The old "ceiling of ~50 digits" was an artificial limit from sangi::remezExchange<Float> being monomial-basis,
//   causing the Vandermonde to become singular at m≳90. Since the extrema of √u cluster at u=0, using the basis
//   φ_k=T_{2k}(√u)=cos(2k·arccos√u) in the t=√u domain makes the matrix DCT-like and well-conditioned, and together
//   with a continuation method (using the previous m's extrema as the next initial guess) E_m can be computed stably to high m.
//   ≤56 digits returns the literal immediately; requests beyond that automatically fall back to compute (correcting the old >60-digit silent-wrong behavior).
// ----------------------------------------------------------------------------
// Arbitrary-precision computation of β (Chebyshev-basis Remez + continuation method + 1/m² Neville extrapolation).
// Find via Remez exchange the error E_m of the degree-m polynomial minimax approximation of √u on [0,1], and
// extrapolate s_m = 2m·E_m → β with 1/m². Using the basis φ_k(u)=T_{2k}(√u)=cos(2k·arccos√u)
// makes the matrix DCT-like and well-conditioned, so it can be solved stably even at high m (≳90) where the
// monomial Vandermonde becomes singular (production remezExchange is monomial-basis, so it cannot be used for β).
// LU is implemented locally to avoid pulling linalg into the foundation TU (same approach as GKW).
namespace bernstein_detail {

inline Float bfabs(const Float& x) { return (x < Float(0)) ? -x : x; }

// Partial-pivot LU (dense, Float-only)
struct BLU { std::vector<std::vector<Float>> a; std::vector<int> piv; int n; };

inline BLU blu_factor(std::vector<std::vector<Float>> a, int wp) {
    int n = static_cast<int>(a.size());
    std::vector<int> piv(n);
    for (int i = 0; i < n; ++i) piv[i] = i;
    for (int k = 0; k < n; ++k) {
        int p = k; Float best = bfabs(a[k][k]);
        for (int i = k + 1; i < n; ++i) { Float v = bfabs(a[i][k]); if (best < v) { best = v; p = i; } }
        if (p != k) { std::swap(a[p], a[k]); std::swap(piv[p], piv[k]); }
        const Float akk = a[k][k];
        for (int i = k + 1; i < n; ++i) {
            Float f = a[i][k] / akk; f.setResultPrecision(wp); a[i][k] = f;
            for (int j = k + 1; j < n; ++j) { Float t = a[i][j] - f * a[k][j]; t.setResultPrecision(wp); a[i][j] = t; }
        }
    }
    return BLU{ std::move(a), std::move(piv), n };
}

inline std::vector<Float> blu_solve(const BLU& lu, const std::vector<Float>& b, int wp) {
    int n = lu.n; std::vector<Float> x(n);
    for (int i = 0; i < n; ++i) x[i] = b[lu.piv[i]];
    for (int i = 0; i < n; ++i) { Float s = x[i]; for (int j = 0; j < i; ++j) s = s - lu.a[i][j] * x[j]; s.setResultPrecision(wp); x[i] = s; }
    for (int i = n - 1; i >= 0; --i) { Float s = x[i]; for (int j = i + 1; j < n; ++j) s = s - lu.a[i][j] * x[j]; s = s / lu.a[i][i]; s.setResultPrecision(wp); x[i] = s; }
    return x;
}

// cos(2kθ) row (k=0..M1-1), θ=arccos(tv). Recurrence: C_0=1, C_1=2tv²-1, C_k=2C_1·C_{k-1}-C_{k-2}
inline std::vector<Float> cosrow(const Float& tv, int M1, int wp) {
    std::vector<Float> row(M1, Float(0));
    if (!(tv < Float(1))) { for (int k = 0; k < M1; ++k) row[k] = Float(1); return row; }
    if (!(tv > Float(0))) { for (int k = 0; k < M1; ++k) row[k] = ((k & 1) ? Float(-1) : Float(1)); return row; }
    Float x2 = Float(2) * tv * tv - Float(1); x2.setResultPrecision(wp);
    row[0] = Float(1);
    if (M1 > 1) {
        row[1] = x2;
        for (int k = 2; k < M1; ++k) { Float t = Float(2) * x2 * row[k - 1] - row[k - 2]; t.setResultPrecision(wp); row[k] = t; }
    }
    return row;
}

inline Float evalE(const std::vector<Float>& c, int m, const Float& tv, int wp) {
    std::vector<Float> r = cosrow(tv, m + 1, wp);
    Float s(0); for (int k = 0; k <= m; ++k) s = s + c[k] * r[k];
    Float e = tv - s; e.setResultPrecision(wp); return e;
}

// Solve for coefficients c[0..m] and delta from the reference points refs
inline void solveRef(const std::vector<Float>& refs, int m, std::vector<Float>& c, Float& delta, int wp) {
    int npts = m + 2, M1 = m + 1;
    std::vector<std::vector<Float>> A(npts, std::vector<Float>(npts));
    std::vector<Float> b(npts);
    for (int i = 0; i < npts; ++i) {
        std::vector<Float> r = cosrow(refs[i], M1, wp);
        for (int k = 0; k < M1; ++k) A[i][k] = r[k];
        A[i][m + 1] = ((i & 1) ? Float(-1) : Float(1));
        b[i] = refs[i];
    }
    BLU lu = blu_factor(std::move(A), wp);
    std::vector<Float> sol = blu_solve(lu, b, wp);
    c.assign(sol.begin(), sol.begin() + M1);
    delta = sol[m + 1];
}

// e'(t) = 1 - (1/√(1-t²))·Σ_{k=1}^m c_k·2k·sin(2kθ), θ=arccos t (for internal t∈(0,1)).
// sin(2kθ): compute by recurrence S_0=0, S_1=sin2θ=2t√(1-t²), S_k=2cos2θ·S_{k-1}-S_{k-2} (cos2θ=2t²-1).
inline Float evalEp(const std::vector<Float>& c, int m, const Float& t, int wp) {
    Float t2 = t * t; t2.setResultPrecision(wp);
    Float sq = sqrt(Float(1) - t2, wp);                         // √(1-t²)
    Float x2 = Float(2) * t2 - Float(1); x2.setResultPrecision(wp);   // cos2θ
    Float s2 = Float(2) * t * sq; s2.setResultPrecision(wp);          // sin2θ
    Float Skm1(0), Sk = s2, sum(0);
    if (m >= 1) { sum = c[1] * Float(2) * Sk; sum.setResultPrecision(wp); }
    for (int k = 2; k <= m; ++k) {
        Float Sn = Float(2) * x2 * Sk - Skm1; Sn.setResultPrecision(wp);
        Skm1 = Sk; Sk = Sn;
        sum = sum + c[k] * Float(2 * k) * Sk; sum.setResultPrecision(wp);
    }
    Float ep = Float(1) - sum / sq; ep.setResultPrecision(wp); return ep;
}

// Search for the root of e'=0 (the extremum location of e) within the bracket [lo,hi] by the Illinois method (always keeps the bracket, superlinear).
// The old argmaxAbs (parabola/bisection on |e|) had an undersized den near the extremum → bisection-rate-limited, so position convergence took O(wp) iterations.
// Root-finding on the analytic e' reaches wp precision in ~log(wp) iterations (also removing the digit ceiling on extremum-location accuracy).
inline Float findExtremum(const std::vector<Float>& c, int m, const Float& lo, const Float& hi,
                          const Float& postol, const Float& dentiny, int maxit, int wp) {
    auto ep = [&](const Float& t) { return evalEp(c, m, t, wp); };
    Float a = lo, b = hi, fa = ep(a), fb = ep(b);
    if ((fa < Float(0)) == (fb < Float(0))) {   // not bracketed (rare): return the endpoint with larger |e|
        return (bfabs(evalE(c, m, a, wp)) < bfabs(evalE(c, m, b, wp))) ? b : a;
    }
    Float prev = a; int side = 0;
    for (int it = 0; it < maxit; ++it) {
        Float den = fb - fa; den.setResultPrecision(wp);
        Float cx;
        if (bfabs(den) < dentiny) { cx = (a + b) / Float(2); }
        else { cx = b - fb * (b - a) / den; }                  // regula falsi / secant
        cx.setResultPrecision(wp);
        if (!(a < cx && cx < b)) { cx = (a + b) / Float(2); cx.setResultPrecision(wp); }
        Float fc = ep(cx);
        Float step = bfabs(cx - prev); prev = cx;
        if (fc.isZero() || step < postol) return cx;
        if ((fc < Float(0)) == (fa < Float(0))) { a = cx; fa = fc; if (side == -1) fb = ldexp(fb, -1); side = -1; }
        else { b = cx; fb = fc; if (side == 1) fa = ldexp(fa, -1); side = 1; }
    }
    return (a + b) / Float(2);
}

// Chebyshev-Lobatto point (1-cos(π i/n))/2 ∈ [0,1] as a Float
inline Float lobatto(int i, int n, const Float& pi, int wp) {
    Float ang = pi * Float(i) / Float(n); ang.setResultPrecision(wp);
    Float c = cos(ang, wp);
    Float x = (Float(1) - c) / Float(2); x.setResultPrecision(wp);
    return x;
}

// Compute the degree-m minimax error E_m by "true Remez all-point exchange" (initRefs: initial reference points for the continuation method).
// Each iteration: solve → refine all maxima of e(t) (endpoints + internal local maxima of |e|) via grid detection + argmax →
//   collapse adjacent same-sign ones (keeping larger |e|) into a strict sign-alternating sequence → drop the weakest extrema from the ends
//   to select exactly m+2 points → next solve. Terminates at equiripple (spread of max/min |e| < convtol).
// ★The old implementation's Phase A single-point exchange (O(npts⁴)/m, ~2.4h for 70 digits) was removed. The old Phase B did not
//   select the alternating set and diverged depending on Phase A (corrected per codex advice 2026-06-05).
//   This all-point exchange version self-converges together with the continuation method (no Phase A needed). Extremum refinement uses root-finding on the analytic e'
//   (findExtremum), so it reaches wp position precision in ~log(wp) iterations (removing the old O(wp) iterations of |e| bisection).
inline Float remezEm(int m, std::vector<Float>& outRefs, const std::vector<Float>* initRefs,
                     const Float& pi, const Float& tol, const Float& convtol, const Float& dentiny,
                     int argit, int maxit, int wp) {
    int npts = m + 2;
    std::vector<Float> refs;
    if (initRefs && static_cast<int>(initRefs->size()) == npts) refs = *initRefs;
    else { refs.resize(npts); for (int i = 0; i < npts; ++i) refs[i] = lobatto(i, npts - 1, pi, wp); }

    int G = std::max(800, 8 * m);
    std::vector<Float> grid(G + 1);
    for (int j = 0; j <= G; ++j) grid[j] = lobatto(j, G, pi, wp);
    std::vector<Float> c; Float delta(0);

    for (int it = 0; it < maxit; ++it) {
        solveRef(refs, m, c, delta, wp);
        std::vector<Float> eg(G + 1);
        for (int j = 0; j <= G; ++j) eg[j] = evalE(c, m, grid[j], wp);
        // Candidate extrema: endpoints (boundary extrema) + internal local maxima of |e| (refined by root-finding on e'=0)
        std::vector<Float> ct, ce;
        ct.push_back(grid[0]); ce.push_back(eg[0]);
        for (int j = 1; j < G; ++j) {
            Float aj = bfabs(eg[j]);
            if (!(aj < bfabs(eg[j - 1])) && !(aj < bfabs(eg[j + 1]))) {
                Float ts = findExtremum(c, m, grid[j - 1], grid[j + 1], tol, dentiny, argit, wp);
                ct.push_back(ts); ce.push_back(evalE(c, m, ts, wp));
            }
        }
        ct.push_back(grid[G]); ce.push_back(eg[G]);
        // Collapse adjacent same-sign ones (keeping larger |e|) → strict alternating sequence
        std::vector<Float> at, ae;
        for (std::size_t k = 0; k < ct.size(); ++k) {
            bool same = !at.empty() && ((ae.back() < Float(0)) == (ce[k] < Float(0)));
            if (same) { if (bfabs(ae.back()) < bfabs(ce[k])) { at.back() = ct[k]; ae.back() = ce[k]; } }
            else { at.push_back(ct[k]); ae.push_back(ce[k]); }
        }
        // Drop the weakest extrema from the ends down to m+2 points
        while (static_cast<int>(at.size()) > npts) {
            if (bfabs(ae.front()) < bfabs(ae.back())) { at.erase(at.begin()); ae.erase(ae.begin()); }
            else { at.pop_back(); ae.pop_back(); }
        }
        if (static_cast<int>(at.size()) < npts) break;   // grid missed an extremum (rare): best effort
        Float mx(-1), mn = bfabs(ae[0]);
        for (int i = 0; i < npts; ++i) { Float v = bfabs(ae[i]); if (mx < v) mx = v; if (v < mn) mn = v; }
        Float spread = (mx - mn) / mx; spread.setResultPrecision(wp);
        refs = at;
        if (spread < convtol) break;
    }
    solveRef(refs, m, c, delta, wp);
    outRefs = refs;
    return bfabs(delta);
}

// Extend the previous m's extrema refs to target points (insert a midpoint into the largest gap)
inline std::vector<Float> growRefs(const std::vector<Float>& refs, int target, int wp) {
    std::vector<Float> r = refs; std::sort(r.begin(), r.end());
    while (static_cast<int>(r.size()) < target) {
        int gi = 0; Float gmax(-1);
        for (int i = 0; i + 1 < static_cast<int>(r.size()); ++i) { Float g = r[i + 1] - r[i]; if (gmax < g) { gmax = g; gi = i; } }
        Float mid = (r[gi] + r[gi + 1]) / Float(2); mid.setResultPrecision(wp);
        r.insert(r.begin() + gi + 1, mid);
    }
    return r;
}

// Bare Remez+Neville that computes β at degree limit m_max and working precision wp (returns at wp precision, unrounded).
// The digit count is rate-limited by m_max (extrapolation reach) and wp (arithmetic) (extrema-location precision
// automatically reaches wp via analytic e' root-finding, so a small constant argit suffices). tail window: the s_m for low m have
// coarse 1/m² asymptotics that pollute the extrapolation, so only m_start and above enter Neville (low m are still needed for the continuation method). Measured (2026-06-05):
// m140/wp100 computes β correctly to ~78 digits (agrees to 78 digits with the independent construction m200/wp160; the first 50 digits are OEIS A073001).
inline Float computeBernsteinRaw(int m_max_in, int wp) {
    const int m_max  = m_max_in + (m_max_in & 1);           // make even
    const int argit  = 40;                                  // iteration limit for e'=0 root-finding (superlinear)
    const int m_start = (m_max * 3) / 5;                    // tail window lower bound
    const int maxit  = 40;                                  // iteration limit for all-point exchange (usually converges in ~10)
    Float::PrecisionScope _ps(wp);                          // exact÷exact to wp digits
    const Float pi = Float::pi(wp);
    auto tenPow = [&](int k) { Float r(1), ten(10); for (int i = 0; i < k; ++i) { r = r / ten; r.setResultPrecision(wp); } return r; };
    const Float tol     = tenPow(wp / 2 + 2);
    const Float convtol = tenPow(wp / 2);
    const Float dentiny = tenPow(wp + 8);                   // degeneracy test for the secant denominator in e' root-finding

    std::vector<int> ms; std::vector<Float> sm;
    std::vector<Float> refs; bool have = false;
    for (int m = 4; m <= m_max; m += 2) {
        std::vector<Float> init = have ? growRefs(refs, m + 2, wp) : std::vector<Float>();
        Float E = remezEm(m, refs, have ? &init : nullptr, pi, tol, convtol, dentiny, argit, maxit, wp);
        have = true;
        if (m < m_start) continue;                          // tail window: use only high m for extrapolation
        ms.push_back(m);
        Float s = Float(2 * m) * E; s.setResultPrecision(wp); sm.push_back(s);
    }
    // 1/m² Neville extrapolation (x=1/m² → 0)
    int K = static_cast<int>(ms.size());
    std::vector<Float> x(K), T = sm;
    for (int i = 0; i < K; ++i) { Float mm(ms[i]); Float xi = Float(1) / (mm * mm); xi.setResultPrecision(wp); x[i] = xi; }
    for (int k = 1; k < K; ++k)
        for (int i = K - 1; i >= k; --i) {
            Float num = (T[i] - T[i - 1]) * (Float(0) - x[i]);
            Float den = x[i] - x[i - k];
            Float t = T[i] + num / den; t.setResultPrecision(wp); T[i] = t;
        }
    Float beta = T[K - 1]; beta.setResultPrecision(wp); return beta;   // keep wp precision (rounding is on the caller side)
}

// Compute β to the requested digit count digits (arbitrary precision). ★Runtime guarantee of the trailing digits: compute with two
// independent constructions differing in both m_max and wp, and return only after confirming agreement to digits+2. A single construction
// is correct to digits by margin (~12%), but the two-construction cross-check structurally guarantees "the returned digits are verified to agree to the end" (eliminating silent-wrong).
// The cost is ~2× a single construction, but compute runs only in the rare case of >literal digits.
inline Float computeBernstein(int digits) {
    Float b1 = computeBernsteinRaw(2 * digits,      digits + 30);
    Float b2 = computeBernsteinRaw(2 * digits + 24, digits + 50);   // independent (both m_max and wp differ)
    const int wpc = digits + 50;
    Float::PrecisionScope _ps(wpc);
    auto tenPow = [&](int k) { Float r(1), ten(10); for (int i = 0; i < k; ++i) { r = r / ten; r.setResultPrecision(wpc); } return r; };
    Float diff = b1 - b2; if (diff < Float(0)) diff = -diff;
    if (diff < tenPow(digits + 2)) { Float r = b2; r.setResultPrecision(digits); return r; }
    // Disagreement (essentially never happens given the margin): recompute with a higher-precision construction and return the best value
    Float b4 = computeBernsteinRaw(2 * digits + 80, digits + 110);
    Float r = b4; r.setResultPrecision(digits); return r;
}

// 76 digits. The first 76 digits where the first 50 digits match OEIS A073001 + three independent constructions (m140/wp100, m160/wp120, m200/wp160)
// agreed to 78 digits (safety margin of 2 digits). Requests of ≤76 digits return this immediately.
inline const char* kBernsteinLiteral =
    "0.2801694990238691330364364912306720000424821398123613931242539678392299247257";

} // namespace bernstein_detail

DEFINE_CACHED_CONSTANT(bernstein,
    if (precision <= 76) {
        // Common range (≤76 digits, including the default 53 digits): return the verified literal (76 digits) immediately.
        cached = Float(bernstein_detail::kBernsteinLiteral);
        cached.setResultPrecision(wp);
    } else {
        // High-precision request (>76 digits): arbitrary-precision computation via Chebyshev-basis Remez (true all-point exchange) + Neville extrapolation.
        //   ★Verified to agree to the returned trailing digit via self-cross-checking of two independent constructions (computeBernstein).
        //   The old implementation (Phase A single-point exchange, O(npts⁴)/m) took ~2.4h for 70 digits, but ① removing Phase A → true
        //   all-point exchange ② making extremum refinement analytic e' root-finding (findExtremum) (|e| bisection's O(wp) iterations → ~log(wp))
        //   ③ tail-window extrapolation ④ proper wp gave ~39x speedup (single construction 70 digits ~4 min, ~2× with cross-check).
        //   The old "pad the literal to wp digits" was silent-wrong, so this implementation is the correct one.
        cached = bernstein_detail::computeBernstein(wp);
    }
)

// --- Gauss-Kuzmin-Wirsing λ ---
// λ ≈ 0.3036630028987326585974481219... is the constant governing the convergence rate of continued fractions
// to the Gauss-Kuzmin distribution; it is the absolute value |λ₁| of the second eigenvalue of the GKW transfer operator
//     (Lf)(x) = Σ_{k≥1} 1/(k+x)² · f(1/(k+x)),   x∈[0,1]
// (λ₀=1 is the invariant density 1/((1+x)ln2)).
// There is no analytic closed form, but it is not "incomputable": in the monomial basis the k-sum closes exactly
// with the Hurwitz zeta (Σ_{k≥1}(k+x)^{-(m+2)} = ζ(m+2,1+x)), and collocation at Chebyshev-Lobatto points reduces it to the
// generalized eigenproblem
//     Z c = λ V c,   V_{im}=x_i^m,  Z_{im}=ζ(m+2,1+x_i).
// It converges hypergeometrically (~0.7 digits/degree) in the degree N.
//
// The old implementation returned a fixed literal that was wrong from the 35th digit on (discovered 2026-06-02).
// The literal below (80 digits) is the first 80 digits of the value confirmed to agree to 84 digits by independently computing
// N=112 and N=128 with this operator method (also matching the published digits of OEIS A038517). For higher precision
// requests, gkw_detail::computeGaussKuzmin computes it at arbitrary precision.
namespace gkw_detail {

inline Float gkw_fabs(const Float& x) { return (x < Float(0)) ? -x : x; }

// Partial-pivot LU factorization (dense, Float-only. Implemented locally to avoid
// pulling linalg into the foundation TU).
struct GkwLU { std::vector<std::vector<Float>> a; std::vector<int> piv; int n; };

inline GkwLU gkw_luFactor(std::vector<std::vector<Float>> a, int wp) {
    int n = static_cast<int>(a.size());
    std::vector<int> piv(n);
    for (int i = 0; i < n; ++i) piv[i] = i;
    for (int k = 0; k < n; ++k) {
        int p = k; Float best = gkw_fabs(a[k][k]);
        for (int i = k + 1; i < n; ++i) { Float v = gkw_fabs(a[i][k]); if (best < v) { best = v; p = i; } }
        if (p != k) { std::swap(a[p], a[k]); std::swap(piv[p], piv[k]); }
        const Float akk = a[k][k];
        for (int i = k + 1; i < n; ++i) {
            Float f = a[i][k] / akk; f.setResultPrecision(wp); a[i][k] = f;
            for (int j = k + 1; j < n; ++j) { Float t = a[i][j] - f * a[k][j]; t.setResultPrecision(wp); a[i][j] = t; }
        }
    }
    return GkwLU{ std::move(a), std::move(piv), n };
}

inline std::vector<Float> gkw_luSolve(const GkwLU& lu, const std::vector<Float>& b, int wp) {
    int n = lu.n; std::vector<Float> x(n);
    for (int i = 0; i < n; ++i) x[i] = b[lu.piv[i]];
    for (int i = 0; i < n; ++i) { Float s = x[i]; for (int j = 0; j < i; ++j) s = s - lu.a[i][j] * x[j]; s.setResultPrecision(wp); x[i] = s; }
    for (int i = n - 1; i >= 0; --i) { Float s = x[i]; for (int j = i + 1; j < n; ++j) s = s - lu.a[i][j] * x[j]; s = s / lu.a[i][i]; s.setResultPrecision(wp); x[i] = s; }
    return x;
}

// For a fixed a, compute the Hurwitz zeta ζ(s,a) in bulk for integers s=2,3,...,sMax.
// This batches the Euler-Maclaurin of the existing hurwitzZeta (FloatMath.cpp) along the s direction:
//   - The direct sum Σ_{k=0}^{N-1}(a+k)^{-s} covers all s with just one reciprocal 1/b per base b=a+k and
//     successive multiplications (b^{-2}, b^{-3}, …) → pow is completely eliminated.
//   - The shift N, the Bernoulli sequence bern[], and the factorials fact[] are s-independent, so they are computed once and shared.
//   - The integral term/midpoint/Bernoulli correction are built across s by successive multiplication.
// This compresses the (N+1)² independent hurwitzZeta calls of the GKW collocation (each doing N pows) into
// one batch per point (N reciprocals + cheap multiplications).
// Used only for GKW (a∈[1,2], integer s ≥2). Values are returned rounded to wp digits (the public API's
// finalizeResult is unnecessary since the GKW-side linear algebra processes at wp).
inline std::vector<Float> gkw_hurwitzBatch(const Float& a_in, int sMax, int wp) {
    const int wp_bits = Float::precisionToBits(wp);
    Float a = a_in; a.truncateToApprox(wp); a.setEffectiveBits(wp_bits);

    const int target_aN = static_cast<int>(wp * 0.4) + 5;     // a+N ≳ 0.4·wp (same as the existing one)
    const int N = std::max(1, target_aN - static_cast<int>(a.toDouble()));
    const int nS = sMax - 1;                                  // s=2..sMax → index 0..nS-1
    std::vector<Float> sum(nS, Float(0));                     // sum[t] = ζ(t+2, a)

    // (1) Direct sum: for each base b=a+k, successively multiply b^{-2}, b^{-3}, … (no pow).
    //     Since a≥1, b≥1, all multipliers are ≤1, so it does not grow and is numerically stable.
    for (int k = 0; k < N; ++k) {
        Float b = a + Float(k); b.truncateToApprox(wp);
        Float binv = Float(1) / b; binv.truncateToApprox(wp);
        Float p = binv * binv; p.truncateToApprox(wp);        // b^{-2} (for s=2)
        for (int t = 0; t < nS; ++t) {
            sum[t] = sum[t] + p; sum[t].truncateToApprox(wp);
            p = p * binv; p.truncateToApprox(wp);             // b^{-(s+1)}
        }
    }

    Float aN = a + Float(N); aN.truncateToApprox(wp);
    Float aN_inv = Float(1) / aN; aN_inv.truncateToApprox(wp);

    // (2) Integral term (a+N)^{1-s}/(s-1) + (3) midpoint ½(a+N)^{-s}. Cross s by successive multiplication.
    Float aN_ms  = aN_inv * aN_inv; aN_ms.truncateToApprox(wp);   // (a+N)^{-s},   aN^{-2} for s=2
    Float aN_ms1 = aN_inv;                                        // (a+N)^{-(s-1)}, aN^{-1} for s=2
    for (int t = 0; t < nS; ++t) {
        const int s = t + 2;
        Float integ = aN_ms1 / Float(s - 1); integ.truncateToApprox(wp);
        Float mid = ldexp(aN_ms, -1);                            // ½ (a+N)^{-s}
        sum[t] = sum[t] + integ + mid; sum[t].truncateToApprox(wp);
        aN_ms  = aN_ms  * aN_inv; aN_ms.truncateToApprox(wp);
        aN_ms1 = aN_ms1 * aN_inv; aN_ms1.truncateToApprox(wp);
    }

    // (4) Bernoulli correction. M, bern[], fact[] are s-independent and shared (same truncation rule as the existing one).
    constexpr double PI_VAL = 3.141592653589793238462643383279502884;
    const int M = static_cast<int>(PI_VAL * static_cast<double>(target_aN)) + 10;
    auto bern = computeBernoulliNumbers(M, wp);                  // bern[k] = B_{2k}
    std::vector<Float> fact(M);                                  // fact[j] = (2(j+1))!
    for (int j = 0; j < M; ++j) {
        Float f(1);
        for (int i = 1; i <= 2 * (j + 1); ++i) f = f * i;
        f.truncateToApprox(wp); fact[j] = f;
    }

    Float aN_pow_start = aN_inv * aN_inv * aN_inv;               // (a+N)^{-(s+1)}, aN^{-3} for s=2
    aN_pow_start.truncateToApprox(wp);
    for (int t = 0; t < nS; ++t) {
        const Float s = Float(t + 2);
        Float rising = s;                                       // s, s(s+1)(s+2), …
        Float aN_pow = aN_pow_start;                            // (a+N)^{-(s+1)}
        int64_t prev_ct = (std::numeric_limits<int64_t>::max)();
        for (int j = 0; j < M; ++j) {
            Float correction = bern[j + 1] / fact[j] * rising * aN_pow;
            correction.truncateToApprox(wp);
            int64_t ct = correction.isZero()
                ? (std::numeric_limits<int64_t>::min)()
                : correction.exponent() + static_cast<int64_t>(correction.mantissa().bitLength());
            if (j >= 3 && ct > prev_ct) break;                 // terminate on divergence of the asymptotic series (passed the smallest term)
            sum[t] = sum[t] + correction; sum[t].truncateToApprox(wp);
            prev_ct = ct;
            if (j >= 3 && !correction.isZero()) {
                auto st = sum[t].exponent() + static_cast<int64_t>(sum[t].mantissa().bitLength());
                if (st - ct > wp_bits + 5) break;              // convergence test
            }
            rising = rising * (s + Float(2 * j + 1)) * (s + Float(2 * (j + 1)));
            rising.truncateToApprox(wp);
            aN_pow = aN_pow * aN_inv * aN_inv; aN_pow.truncateToApprox(wp);
        }
        aN_pow_start = aN_pow_start * aN_inv; aN_pow_start.truncateToApprox(wp);  // to the next s
    }
    return sum;
}

// Compute the GKW constant to the requested digit count digits (arbitrary precision).
inline Float computeGaussKuzmin(int digits) {
    const int N   = static_cast<int>(digits / 0.70) + 14;   // ~0.7 digits/degree + margin
    const int n   = N + 1;
    const int wp  = digits + N / 2 + 35;                      // digit margin for the ill-conditioning of the monomial basis
    const Float pi = Float::pi(wp);

    std::vector<Float> x(n);                                  // Chebyshev-Lobatto points ∈[0,1]
    for (int i = 0; i < n; ++i) {
        Float ang = pi * Float(i) / Float(N);
        Float xi = (Float(1) - cos(ang, wp)) / Float(2); xi.setResultPrecision(wp); x[i] = xi;
    }
    std::vector<std::vector<Float>> V(n, std::vector<Float>(n)), Z(n, std::vector<Float>(n));
    for (int i = 0; i < n; ++i) {
        const Float a = Float(1) + x[i];
        // Compute Z[i][m] = ζ(m+2, a) for all m together in one batch (pow eliminated for fixed a).
        // Old: calling independent hurwitzZeta for each m, (N+1)² times → compressed to one batch per point.
        std::vector<Float> Zrow = gkw_hurwitzBatch(a, n + 1, wp);  // ζ(2,a)…ζ(n+1,a)
        Float xp = Float(1);
        for (int m = 0; m < n; ++m) {
            V[i][m] = (m == 0) ? Float(1) : xp;
            Z[i][m] = Zrow[m];
            xp = xp * x[i]; xp.setResultPrecision(wp);
        }
    }
    // Generalized shift-inverse iteration: σ=-0.30 is closest to λ₁≈-0.3036 and far from the other eigenvalues,
    // so it robustly converges to λ₁ at ~1.85 digits/iteration.
    Float sigma = Float("-0.30"); sigma.setResultPrecision(wp);
    std::vector<std::vector<Float>> A(n, std::vector<Float>(n));
    for (int i = 0; i < n; ++i) for (int j = 0; j < n; ++j) { Float t = Z[i][j] - sigma * V[i][j]; t.setResultPrecision(wp); A[i][j] = t; }
    GkwLU lu = gkw_luFactor(std::move(A), wp);

    auto normalize = [&](std::vector<Float>& v) {
        Float s = Float(0); for (auto& e : v) s = s + e * e; s = sqrt(s, wp);
        for (auto& e : v) { e = e / s; e.setResultPrecision(wp); }
    };
    auto matvec = [&](const std::vector<std::vector<Float>>& M, const std::vector<Float>& v) {
        std::vector<Float> r(n, Float(0));
        for (int i = 0; i < n; ++i) { Float s = Float(0); for (int j = 0; j < n; ++j) s = s + M[i][j] * v[j]; s.setResultPrecision(wp); r[i] = s; }
        return r;
    };
    std::vector<Float> c(n, Float(1)); normalize(c);
    const int iters = static_cast<int>(wp / 1.6) + 18;
    for (int it = 0; it < iters; ++it) {
        std::vector<Float> y = gkw_luSolve(lu, matvec(V, c), wp);
        normalize(y); c = std::move(y);
    }
    std::vector<Float> Zc = matvec(Z, c), Vc = matvec(V, c);
    Float num = Float(0), den = Float(0);
    for (int i = 0; i < n; ++i) { num = num + c[i] * Zc[i]; den = den + c[i] * Vc[i]; }
    Float lam = num / den; lam.setResultPrecision(wp);
    return gkw_fabs(lam);                                     // GKW constant = |λ₁|
}

// 80 digits (the first 80 digits confirmed to agree to 84 digits by the operator method N=112/N=128).
inline const char* kGkwLiteral80 =
    "0.30366300289873265859744812190155623311087735225365789518824548146722699529424691";

} // namespace gkw_detail

DEFINE_CACHED_CONSTANT(gauss_kuzmin,
    if (precision <= 64) {
        // Common range (≤64 digits, including the default 53 digits): return the verified literal immediately.
        cached = Float(gkw_detail::kGkwLiteral80);
        cached.setResultPrecision(wp);
    } else {
        // High-precision request: compute the second eigenvalue of the GKW operator at arbitrary precision.
        cached = gkw_detail::computeGaussKuzmin(wp);
    }
)

// --- Feigenbaum δ ---
// δ ≈ 4.6692016091...
DEFINE_CACHED_CONSTANT(feigenbaum_delta,
    cached = Float("4.669201609102990671853203820466201617258185577475768632745651");
    cached.setPrecision(wp);
)

// --- Feigenbaum α ---
// α ≈ 2.5029078750...
DEFINE_CACHED_CONSTANT(feigenbaum_alpha,
    cached = Float("2.502907875095892822283902873218215786381271376727149977336192");
    cached.setPrecision(wp);
)

// --- Erdős-Borwein E ---
// E = Σ_{n=1}^{∞} 1/(2^n−1) ≈ 1.6066951524...
DEFINE_CACHED_CONSTANT(erdos_borwein,
    int N = wp * 4 + 100;  // sufficient since 2^N >> 10^wp
    Float sum(0);
    Float pow2(1);
    pow2.setPrecision(wp);
    for (int n = 1; n <= N; n++) {
        pow2 = ldexp(pow2, 1);
        Float term = Float(1) / (pow2 - Float(1));
        if (term.isZero()) break;
        sum += term;
    }
    cached = sum;
)

// --- Laplace limit λ* ---
// the real root of x·e^√(1+x²) = 1 + √(1+x²) ≈ 0.6627434193...
DEFINE_CACHED_CONSTANT(laplace_limit,
    Float x(0.6627434193491815);
    x.setPrecision(wp);
    for (int iter = 0; iter < 100; iter++) {
        Float sq = sqrt(Float(1) + x * x, wp);
        Float esq = exp(sq, wp);
        Float f = x * esq - Float(1) - sq;
        Float fprime = esq * (Float(1) + x * x / sq) - x / sq;
        Float dx = f / fprime;
        x -= dx;
        if (abs(dx).toDouble() == 0.0 && iter > 10) break;
    }
    cached = x;
)

// --- Ramanujan-Soldner μ ---
// the positive root of li(μ) = 0 ≈ 1.4513692348...
DEFINE_CACHED_CONSTANT(soldner,
    Float x(1.4513692348833810);
    x.setPrecision(wp);
    for (int iter = 0; iter < 100; iter++) {
        // li(x) = Ei(ln x) ≈ ∫₀^x dt/ln(t)
        // Newton: x ← x − li(x) · ln(x)  (li'(x) = 1/ln(x))
        Float lnx = log(x, wp);
        // Compute li(x): Ramanujan series
        Float li_val = Float::euler(wp) + log(abs(lnx), wp);
        Float lnx_pow(1);
        for (int k = 1; k <= wp + 20; k++) {
            lnx_pow = divScalarF(lnx_pow * lnx, static_cast<uint64_t>(k));
            Float term = divScalarF(lnx_pow, static_cast<uint64_t>(k));
            li_val += term;
            if (abs(term).toDouble() == 0.0 && k > 20) break;
        }
        Float dx = li_val * lnx;
        x -= dx;
        if (abs(dx).toDouble() == 0.0 && iter > 10) break;
    }
    cached = x;
)

// --- Backhouse B ---
// B ≈ 1.4560749485826897...
DEFINE_CACHED_CONSTANT(backhouse,
    cached = Float("1.456074948582689671399595351116543266074274800178");
    cached.setPrecision(wp);
)

// --- Porter C ---
// C = (6·ln2/π²)·(3·ln2 + 4·γ − 24·π²·ζ'(2) + 2) − 1/2 ≈ 1.4677...
// Simplified version: use the known value
DEFINE_CACHED_CONSTANT(porter,
    cached = Float("1.467078079433975472897798117041568832832489495783");
    cached.setPrecision(wp);
)

// --- Lieb square ice (8√3/9) ---
DEFINE_CACHED_CONSTANT(lieb_square_ice,
    cached = divScalarF(mulScalarF(Float::sqrt3(wp), uint64_t(8)), uint64_t(9));
)

// --- Niven C ---
// C = 1 + Σ_{k=2}^{∞} (1 − 1/ζ(k)) ≈ 1.7052111401...
// Niven C = 1 + Σ_{k=2}^∞ (1 − 1/ζ(k)). 1−1/ζ(k) ~ ζ(k)−1 ~ 2^{-k} converges geometrically.
// BUGFIX (2026-05-31): the old implementation had a slow direct sum for the inner ζ(k) (ζ(2) gave ~2 digits) + a fixed outer k≤60,
// plateauing at ~3 digits (detected by a self-consistency audit). Use the full-precision zeta(Float(k)) and iterate to convergence.
DEFINE_CACHED_CONSTANT(niven,
    Float sum(1);
    const int wp_bits = Float::precisionToBits(wp);
    for (int k = 2; k <= wp_bits + 10; k++) {
        Float zk = zeta(Float(k), wp);
        Float contribution = Float(1) - Float(1) / zk;
        contribution.truncateToApprox(wp);
        sum += contribution;
        if (k >= 4 && !contribution.isZero()) {
            int64_t cb = contribution.exponent()
                       + static_cast<int64_t>(contribution.mantissa().bitLength());
            if (cb < -(static_cast<int64_t>(wp_bits) + 2)) break;  // converged below 2^{-wp_bits}
        }
    }
    cached = sum;
)

// --- Reciprocal Fibonacci ψ ---
// ψ = Σ_{k=1}^{∞} 1/F(k) ≈ 3.3598856662...
DEFINE_CACHED_CONSTANT(reciprocal_fibonacci,
    Float sum(0);
    Float fa(1); Float fb(1);  // F(1)=1, F(2)=1
    fa.setPrecision(wp);
    fb.setPrecision(wp);
    sum += Float(1) / fa;  // 1/F(1)
    sum += Float(1) / fb;  // 1/F(2)
    for (int k = 3; k <= wp * 5 + 100; k++) {
        Float fc = fa + fb;
        Float term = Float(1) / fc;
        if (term.isZero()) break;
        sum += term;
        fa = fb;
        fb = fc;
    }
    cached = sum;
)

// --- Sierpiński K ---
// K = π · ln 2 ≈ ... no, that's different
// Sierpiński's constant K ≈ 2.5849817595... = π(ln2 + ... )
// K = π·(2·ln2 + 3·ln(π) + 2·γ − 24·ln(Γ(1/4)))  ... complex
// Use: the known value
DEFINE_CACHED_CONSTANT(sierpinski,
    cached = Float("2.584981759579253217065893587383171160462389109975");
    cached.setPrecision(wp);
)

// --- Mills θ ---
// θ ≈ 1.3063778838... (under the Riemann hypothesis)
DEFINE_CACHED_CONSTANT(mills,
    cached = Float("1.306377883863080690468614492602605712916784585157");
    cached.setPrecision(wp);
)

// --- Dottie number d ---
// the fixed point of cos(d) = d ≈ 0.7390851332...
DEFINE_CACHED_CONSTANT(dottie,
    Float x(0.7390851332151607);
    x.setPrecision(wp);
    for (int iter = 0; iter < 200; iter++) {
        Float cx = cos(x, wp);
        Float f = cx - x;
        Float fprime = -sin(x, wp) - Float(1);
        Float dx = f / fprime;
        x -= dx;
        if (abs(dx).toDouble() == 0.0 && iter > 10) break;
    }
    cached = x;
)

// --- Golomb-Dickman λ ---
// λ ≈ 0.6243299885...
DEFINE_CACHED_CONSTANT(golomb_dickman,
    cached = Float("0.6243299885435508709929363831008372441796426201805");
    cached.setPrecision(wp);
)

// --- Smallest Salem number (Lehmer) τ ---
// Lehmer's Mahler measure ≈ 1.1762808182...
// root: x^10 + x^9 − x^7 − x^6 − x^5 − x^4 − x^3 + x + 1 = 0
DEFINE_CACHED_CONSTANT(salem,
    // BUGFIX (2026-05-31): seed's setPrecision plateaued at ~24 digits
    // → setResultPrecision(wp) + residual convergence loop (self-consistency audit).
    Float x(1.1762808182599175);
    x.setResultPrecision(wp);
    const int wp_bits = Float::precisionToBits(wp);
    for (int it = 0; it < 200; it++) {
        // Lehmer polynomial: x^10+x^9-x^7-x^6-x^5-x^4-x^3+x+1
        Float x2 = x*x; Float x3 = x2*x; Float x4 = x3*x; Float x5 = x4*x;
        Float x6 = x5*x; Float x7 = x6*x; Float x9 = x7*x2; Float x10 = x9*x;
        Float f = x10 + x9 - x7 - x6 - x5 - x4 - x3 + x + Float(1);
        Float fp = Float(10)*x9 + Float(9)*x7*x - Float(7)*x6 - Float(6)*x5
                 - Float(5)*x4 - Float(4)*x3 - Float(3)*x2 + Float(1);
        Float corr = f / fp;
        x -= corr;
        if (corr.isZero()) break;
        int64_t cb = corr.exponent() + static_cast<int64_t>(corr.mantissa().bitLength());
        if (cb < -(static_cast<int64_t>(wp_bits) + 4)) break;
    }
    cached = x;
)

// --- Cahen C ---
// C = Σ_{k=0}^{∞} (−1)^k / (s_k−1) ≈ 0.6434105462...
// s_k is the Sylvester sequence: s_0=2, s_{k+1} = s_k² − s_k + 1
DEFINE_CACHED_CONSTANT(cahen,
    Float sum(0);
    Float s(2);
    s.setPrecision(wp);
    for (int k = 0; k < 30; k++) {  // the Sylvester sequence grows super-exponentially
        Float sign = (k % 2 == 0) ? Float(1) : Float(-1);
        Float term = sign / (s - Float(1));
        if (term.isZero()) break;
        sum += term;
        s = s * s - s + Float(1);
    }
    cached = sum;
)

// --- Lévy β ---
// e^(π²/(12·ln2)) ≈ 3.2758229187...
DEFINE_CACHED_CONSTANT(levy,
    Float pi_val = Float::pi(wp);
    Float ln2_val = Float::log2(wp);
    Float exponent = pi_val * pi_val / (Float(12) * ln2_val);
    cached = exp(exponent, wp);
)

// --- Copeland-Erdős C_CE ---
// 0.23571113171923293137... (a decimal formed by concatenating primes)
DEFINE_CACHED_CONSTANT(copeland_erdos,
    // Concatenate primes as strings and convert to a decimal
    int limit = std::max(wp * 10, 5000);
    std::vector<bool> sieve(limit + 1, true);
    sieve[0] = sieve[1] = false;
    for (int i = 2; i * i <= limit; i++)
        if (sieve[i]) for (int j = i * i; j <= limit; j += i) sieve[j] = false;
    std::string digits = "0.";
    for (int p = 2; p <= limit && (int)digits.size() < wp + 10; p++)
        if (sieve[p]) digits += std::to_string(p);
    digits = digits.substr(0, wp + 5);
    cached = Float(digits);
    cached.setPrecision(wp);
)

// --- π²/6 = ζ(2) ---
DEFINE_CACHED_CONSTANT(pi_squared_over_6,
    Float pi_val = Float::pi(wp);
    cached = pi_val * pi_val / Float(6);
)

// --- π²/12 ---
DEFINE_CACHED_CONSTANT(pi_squared_over_12,
    Float pi_val = Float::pi(wp);
    cached = pi_val * pi_val / Float(12);
)

// --- Champernowne constant C_10 ---
// 0.123456789101112131415... (a decimal formed by base-10 concatenating the positive integers). Exactly computable to arbitrary precision.
DEFINE_CACHED_CONSTANT(champernowne,
    std::string digits = "0.";
    for (int n = 1; static_cast<int>(digits.size()) < wp + 10; ++n)
        digits += std::to_string(n);
    digits = digits.substr(0, wp + 5);
    cached = Float(digits);
    cached.setPrecision(wp);
)

// --- Liouville constant ---
// Σ_{k=1}^∞ 10^{-k!} = 0.110001000000000000000001000... (the k!-th decimal place is 1, the rest are 0).
// The first concrete example of a transcendental number (Liouville 1844). Exactly computable to arbitrary precision.
DEFINE_CACHED_CONSTANT(liouville,
    std::string digits(static_cast<std::size_t>(wp + 5), '0');
    long long fact = 1;
    for (int k = 1; k <= 20; ++k) {       // 20! is just below the long long limit; k! grows super-exponentially
        fact *= k;
        if (fact > static_cast<long long>(wp + 4)) break;
        digits[static_cast<std::size_t>(fact - 1)] = '1';
    }
    cached = Float("0." + digits);
    cached.setPrecision(wp);
)

// --- Alladi-Grinstead constant ---
// exp(c-1), c=Σ_{k≥2}(1/k)ln(k/(k-1)) family. Appears in the asymptotics of the product representation of n! (OEIS A085291).
// Limited-precision literal (same kind as mills/golomb_dickman etc.).
DEFINE_CACHED_CONSTANT(alladi_grinstead,
    cached = Float("0.80939402054063913071793188059409131721595399242500030424");
    cached.setPrecision(wp);
)

// --- Hafner-Sarnak-McCurley constant ---
// The limiting probability that the determinants of two large integer square matrices are coprime (OEIS A085849).
// = the Euler product Π_p (1 - (1 - Π_{k≥1}(1-p^{-k}))²). Limited-precision literal.
DEFINE_CACHED_CONSTANT(hafner_sarnak_mccurley,
    cached = Float("0.35323637185499598454351655043268201128016477856669044642");
    cached.setPrecision(wp);
)

// --- Lengyel constant L ---
// A constant L ≈ 1.0986858... appearing in the recurrence of set partitions (Bell number family) (OEIS A086053).
// Limited-precision literal.
DEFINE_CACHED_CONSTANT(lengyel,
    cached = Float("1.0986858055251870130177463257213318079312220710644268407");
    cached.setPrecision(wp);
)

// --- Hardy-Littlewood prime quadruplet constant ---
// The Euler product constant appearing in the density conjecture for prime quadruplets (p, p+2, p+6, p+8) (OEIS A061642).
// ≈ 4.1511808632... Limited-precision literal.
DEFINE_CACHED_CONSTANT(prime_quadruplet,
    cached = Float("4.1511808632374157571652855619595375157994100193339630320");
    cached.setPrecision(wp);
)

#undef DEFINE_CACHED_CONSTANT

//======================================================================
// Internal implementation helper functions
//======================================================================

Float Float::addUnsigned(const Float& rhs) const & {
    // Precondition: operator+/- has handled NaN/Inf/Zero. Both operands are finite and non-zero.

    // Align the exponents
    int64_t exp_diff = exponent_ - rhs.exponent_;

    // The early-return decision must compare by MSB distance (magnitude_gap).
    // exp_diff = lhs.exp - rhs.exp is not the MSB distance of the Floats:
    // the true MSB position is exp + bitLen - 1, and when bitLen differs greatly (sparse mantissa
    // etc.) exp_diff over/underestimates the MSB distance by rhs.bitLen - lhs.bitLen.
    // BUG_FLOAT_SPARSE_MANTISSA_PRECISION_20260509 H4 fix.
    int64_t magnitude_gap = exp_diff
                          + static_cast<int64_t>(mantissa_.bitLength())
                          - static_cast<int64_t>(rhs.mantissa_.bitLength());

    // Exponent-difference threshold: if large enough, the smaller operand does not affect the rounded result.
    // Use requested_bits_ directly to avoid the round trip through precision()/precisionToBits().
    int64_t exp_threshold = std::max(static_cast<int64_t>(1000),
        static_cast<int64_t>(
            std::max(requested_bits_ < INT_MAX ? requested_bits_ : 0,
                     rhs.requested_bits_ < INT_MAX ? rhs.requested_bits_ : 0)
        ) + 64);

    if (exp_diff == 0) {
        // If the exponents are equal, add the mantissas
        const uint64_t* a = mantissa_.data();
        const uint64_t* b = rhs.mantissa_.data();
        size_t an = mantissa_.size(), bn = rhs.mantissa_.size();
        // mpn::add assumes an >= bn
        if (an < bn) { std::swap(a, b); std::swap(an, bn); }

        if (an < mpn::KARATSUBA_THRESHOLD) {
            // Direct mpn addition with a stack buffer
            uint64_t rbuf[mpn::KARATSUBA_THRESHOLD + 1];
            uint64_t carry = mpn::add(rbuf, a, an, b, bn);
            size_t rn = an;
            if (carry) { rbuf[rn] = carry; rn++; }
            // Skip LSB zero words and adjust the exponent
            size_t lsb = 0;
            while (lsb < rn && rbuf[lsb] == 0) ++lsb;
            size_t ns = rn - lsb;
            // Construct the Float directly (avoiding Int::fromRawWordsPreNormalized + move)
            Float result;
            result.mantissa_.m_words.resize_uninitialized(ns);
            std::memcpy(result.mantissa_.m_words.data(), rbuf + lsb, ns * sizeof(uint64_t));
            result.mantissa_.m_sign = 1;
            result.mantissa_.m_state = NumericState::Normal;
            result.exponent_ = exponent_ + static_cast<int64_t>(lsb) * 64;
            result.is_negative_ = false;
            return result;
        }
        // Large size: legacy path
        Int result_mantissa = mantissa_ + rhs.mantissa_;
        return Float(std::move(result_mantissa), exponent_, false);
    }

    // exp_diff != 0: shift + add
    // lhs = the side with the larger exponent, rhs_side = the side with the smaller exponent
    const uint64_t* lhs_data;
    size_t lhs_n;
    const uint64_t* rhs_data;
    size_t rhs_n;
    int64_t abs_diff;
    int64_t base_exp;

    if (exp_diff > 0) {
        if (magnitude_gap > exp_threshold) return *this;
        lhs_data = mantissa_.data();
        lhs_n = mantissa_.size();
        rhs_data = rhs.mantissa_.data();
        rhs_n = rhs.mantissa_.size();
        abs_diff = exp_diff;
        base_exp = rhs.exponent_;
    } else {
        if (-magnitude_gap > exp_threshold) return rhs;
        lhs_data = rhs.mantissa_.data();
        lhs_n = rhs.mantissa_.size();
        rhs_data = mantissa_.data();
        rhs_n = mantissa_.size();
        abs_diff = -exp_diff;
        base_exp = exponent_;
    }

    size_t word_shift = static_cast<size_t>(abs_diff) / 64;
    unsigned bit_shift = static_cast<unsigned>(abs_diff % 64);

    // Stack buffer size limit: fall back to the legacy path if exceeded
    constexpr size_t BUF_LIMIT = mpn::KARATSUBA_THRESHOLD * 2;
    size_t aligned_max = word_shift + lhs_n + 1; // +1 for possible lshift carry
    if (aligned_max <= BUF_LIMIT && rhs_n <= BUF_LIMIT) {
        // Shift lhs and place it into the stack buffer
        uint64_t aligned[BUF_LIMIT + 1];
        size_t aligned_n;
        // The low word_shift words are zero
        std::memset(aligned, 0, word_shift * sizeof(uint64_t));
        if (bit_shift == 0) {
            std::memcpy(aligned + word_shift, lhs_data, lhs_n * sizeof(uint64_t));
            aligned_n = word_shift + lhs_n;
        } else {
            uint64_t carry = mpn::lshift(aligned + word_shift, lhs_data, lhs_n, bit_shift);
            aligned_n = word_shift + lhs_n;
            if (carry) { aligned[aligned_n] = carry; aligned_n++; }
        }

        // Add aligned + rhs_data
        uint64_t rbuf[BUF_LIMIT + 2];
        const uint64_t* big;
        size_t big_n;
        const uint64_t* small;
        size_t small_n;
        if (aligned_n >= rhs_n) {
            big = aligned; big_n = aligned_n;
            small = rhs_data; small_n = rhs_n;
        } else {
            big = rhs_data; big_n = rhs_n;
            small = aligned; small_n = aligned_n;
        }
        uint64_t c = mpn::add(rbuf, big, big_n, small, small_n);
        size_t rn = big_n;
        if (c) { rbuf[rn] = c; rn++; }
        // Skip LSB zero words and adjust the exponent
        size_t lsb = 0;
        while (lsb < rn && rbuf[lsb] == 0) ++lsb;
        size_t ns = rn - lsb;
        Float result;
        result.mantissa_.m_words.resize_uninitialized(ns);
        std::memcpy(result.mantissa_.m_words.data(), rbuf + lsb, ns * sizeof(uint64_t));
        result.mantissa_.m_sign = 1;
        result.mantissa_.m_state = NumericState::Normal;
        result.exponent_ = base_exp + static_cast<int64_t>(lsb) * 64;
        result.is_negative_ = false;
        return result;
    }

    // Large size: legacy path (via Int)
    if (exp_diff > 0) {
        Int padded_mantissa = mantissa_ << static_cast<int>(exp_diff);
        Int result_mantissa = padded_mantissa + rhs.mantissa_;
        return Float(std::move(result_mantissa), rhs.exponent_, false);
    } else {
        Int padded_rhs = rhs.mantissa_ << static_cast<int>(-exp_diff);
        Int result_mantissa = mantissa_ + padded_rhs;
        return Float(std::move(result_mantissa), exponent_, false);
    }
}

Float Float::addUnsigned(const Float& rhs) && {
    // rvalue version: reuse mantissa_'s buffer in the large-size path.
    // The small-size (stack buffer) path delegates to the const version (no effect without heap allocation).

    int64_t exp_diff = exponent_ - rhs.exponent_;
    // BUG_FLOAT_SPARSE_MANTISSA_PRECISION_20260509 H4 fix:
    // The early return is decided by MSB distance (magnitude_gap) (same as the const& version).
    int64_t magnitude_gap = exp_diff
                          + static_cast<int64_t>(mantissa_.bitLength())
                          - static_cast<int64_t>(rhs.mantissa_.bitLength());

    int64_t exp_threshold = std::max(static_cast<int64_t>(1000),
        static_cast<int64_t>(
            std::max(requested_bits_ < INT_MAX ? requested_bits_ : 0,
                     rhs.requested_bits_ < INT_MAX ? rhs.requested_bits_ : 0)
        ) + 64);

    if (exp_diff == 0) {
        size_t an = mantissa_.size(), bn = rhs.mantissa_.size();
        if (std::max(an, bn) < mpn::KARATSUBA_THRESHOLD) {
            return static_cast<const Float&>(*this).addUnsigned(rhs);
        }
        // Large size: reuse the buffer via Int's rvalue operator+
        Int result_mantissa = std::move(mantissa_) + rhs.mantissa_;
        return Float(std::move(result_mantissa), exponent_, false);
    }

    if (exp_diff > 0) {
        if (magnitude_gap > exp_threshold) return std::move(*this);
    } else {
        if (-magnitude_gap > exp_threshold) return rhs;
    }

    // exp_diff != 0: decide whether it fits in the stack buffer
    size_t lhs_n = (exp_diff > 0) ? mantissa_.size() : rhs.mantissa_.size();
    size_t other_n = (exp_diff > 0) ? rhs.mantissa_.size() : mantissa_.size();
    size_t word_shift = static_cast<size_t>(std::abs(exp_diff)) / 64;
    size_t aligned_max = word_shift + lhs_n + 1;

    constexpr size_t BUF_LIMIT = mpn::KARATSUBA_THRESHOLD * 2;
    if (aligned_max <= BUF_LIMIT && other_n <= BUF_LIMIT) {
        return static_cast<const Float&>(*this).addUnsigned(rhs);
    }

    // Large size: exploit move
    if (exp_diff > 0) {
        mantissa_ <<= static_cast<int>(exp_diff);
        Int result_mantissa = std::move(mantissa_) + rhs.mantissa_;
        return Float(std::move(result_mantissa), rhs.exponent_, false);
    } else {
        Int padded_rhs = rhs.mantissa_ << static_cast<int>(-exp_diff);
        Int result_mantissa = std::move(mantissa_) + std::move(padded_rhs);
        return Float(std::move(result_mantissa), exponent_, false);
    }
}

Float Float::subtractUnsigned(const Float& rhs) const & {
    // Precondition: operator+/- has handled NaN/Inf/Zero. Both operands are finite and non-zero.

    // Align the exponents
    int64_t exp_diff = exponent_ - rhs.exponent_;
    // BUG_FLOAT_SPARSE_MANTISSA_PRECISION_20260509 H4 fix: the early return is decided by MSB distance.
    int64_t magnitude_gap = exp_diff
                          + static_cast<int64_t>(mantissa_.bitLength())
                          - static_cast<int64_t>(rhs.mantissa_.bitLength());

    // Exponent-difference threshold (same as addUnsigned)
    int64_t exp_threshold = std::max(static_cast<int64_t>(1000),
        static_cast<int64_t>(
            std::max(requested_bits_ < INT_MAX ? requested_bits_ : 0,
                     rhs.requested_bits_ < INT_MAX ? rhs.requested_bits_ : 0)
        ) + 64);

    if (exp_diff == 0) {
        // If the exponents are equal: compare with mpn::cmp and subtract with mpn::sub
        const uint64_t* a = mantissa_.data();
        const uint64_t* b = rhs.mantissa_.data();
        size_t an = mantissa_.size(), bn = rhs.mantissa_.size();

        int cmp_result = mpn::cmp(a, an, b, bn);
        if (cmp_result == 0) return zero();

        bool result_negative;
        if (cmp_result < 0) {
            std::swap(a, b); std::swap(an, bn);
            result_negative = true;
        } else {
            result_negative = false;
        }

        if (an < mpn::KARATSUBA_THRESHOLD) {
            uint64_t rbuf[mpn::KARATSUBA_THRESHOLD];
            mpn::sub(rbuf, a, an, b, bn);
            size_t rn = mpn::normalized_size(rbuf, an);
            if (rn == 0) return zero();
            // Skip LSB zero words and adjust the exponent
            size_t lsb = 0;
            while (lsb < rn && rbuf[lsb] == 0) ++lsb;
            if (lsb >= rn) return zero();
            size_t ns = rn - lsb;
            Float result;
            result.mantissa_.m_words.resize_uninitialized(ns);
            std::memcpy(result.mantissa_.m_words.data(), rbuf + lsb, ns * sizeof(uint64_t));
            result.mantissa_.m_sign = 1;
            result.mantissa_.m_state = NumericState::Normal;
            result.exponent_ = exponent_ + static_cast<int64_t>(lsb) * 64;
            result.is_negative_ = result_negative;
            return result;
        }
        // Large size: legacy path
        Int result_mantissa = (cmp_result > 0)
            ? (mantissa_ - rhs.mantissa_)
            : (rhs.mantissa_ - mantissa_);
        return Float(std::move(result_mantissa), exponent_, result_negative);
    }

    // exp_diff != 0: shift + subtract
    // lhs = shift the side with the larger exponent; the result's base_exp is the smaller side's exponent
    // subtractUnsigned is this - rhs (unsigned mantissa subtraction), so
    // the result may become negative
    if (exp_diff > 0) {
        if (magnitude_gap > exp_threshold) {
            // Early return: |lhs| dominates, result ≈ |lhs|
            // subtractUnsigned contract: is_negative_ is a flag indicating (|lhs| < |rhs|)
            // (interpreted by the operator+ caller). false since |lhs| dominates.
            // Accompanying fix for BUG_FLOAT_SPARSE_MANTISSA_PRECISION_20260509 H4 fix.
            Float result = *this;
            result.is_negative_ = false;
            return result;
        }
    } else {
        if (-magnitude_gap > exp_threshold) {
            // |rhs| dominates, result ≈ -|rhs| (in subtractUnsigned semantics)
            Float result = rhs;
            result.is_negative_ = true;
            return result;
        }
    }

    // lhs_side = the side with the larger exponent (the side to shift)
    const uint64_t* lhs_data;
    size_t lhs_n;
    const uint64_t* rhs_data;
    size_t rhs_n;
    int64_t abs_diff;
    int64_t base_exp;
    // lhs_is_this: whether the shifted lhs derives from this's mantissa
    bool lhs_is_this;

    if (exp_diff > 0) {
        lhs_data = mantissa_.data();
        lhs_n = mantissa_.size();
        rhs_data = rhs.mantissa_.data();
        rhs_n = rhs.mantissa_.size();
        abs_diff = exp_diff;
        base_exp = rhs.exponent_;
        lhs_is_this = true;
    } else {
        lhs_data = rhs.mantissa_.data();
        lhs_n = rhs.mantissa_.size();
        rhs_data = mantissa_.data();
        rhs_n = mantissa_.size();
        abs_diff = -exp_diff;
        base_exp = exponent_;
        lhs_is_this = false;
    }

    size_t word_shift = static_cast<size_t>(abs_diff) / 64;
    unsigned bit_shift = static_cast<unsigned>(abs_diff % 64);

    constexpr size_t BUF_LIMIT = mpn::KARATSUBA_THRESHOLD * 2;
    size_t aligned_max = word_shift + lhs_n + 1;
    if (aligned_max <= BUF_LIMIT && rhs_n <= BUF_LIMIT) {
        // Shift lhs and place it into the stack buffer
        uint64_t aligned[BUF_LIMIT + 1];
        size_t aligned_n;
        std::memset(aligned, 0, word_shift * sizeof(uint64_t));
        if (bit_shift == 0) {
            std::memcpy(aligned + word_shift, lhs_data, lhs_n * sizeof(uint64_t));
            aligned_n = word_shift + lhs_n;
        } else {
            uint64_t carry = mpn::lshift(aligned + word_shift, lhs_data, lhs_n, bit_shift);
            aligned_n = word_shift + lhs_n;
            if (carry) { aligned[aligned_n] = carry; aligned_n++; }
        }

        // Compare aligned (shifted lhs) with rhs_data and subtract
        int cmp = mpn::cmp(aligned, aligned_n, rhs_data, rhs_n);
        bool result_negative;
        const uint64_t* big;
        size_t big_n;
        const uint64_t* small;
        size_t small_n;

        if (cmp == 0) return zero();
        if (cmp > 0) {
            // aligned >= rhs_data: positive if the this side is larger, negative if the rhs side
            big = aligned; big_n = aligned_n;
            small = rhs_data; small_n = rhs_n;
            result_negative = !lhs_is_this; // positive if lhs is this, negative if rhs
        } else {
            big = rhs_data; big_n = rhs_n;
            small = aligned; small_n = aligned_n;
            result_negative = lhs_is_this; // negative if lhs is this, positive if rhs
        }

        uint64_t rbuf[BUF_LIMIT + 1];
        mpn::sub(rbuf, big, big_n, small, small_n);
        size_t rn = mpn::normalized_size(rbuf, big_n);
        if (rn == 0) return zero();
        // Skip LSB zero words and adjust the exponent
        size_t lsb = 0;
        while (lsb < rn && rbuf[lsb] == 0) ++lsb;
        if (lsb >= rn) return zero();
        size_t ns = rn - lsb;
        Float result;
        result.mantissa_.m_words.resize_uninitialized(ns);
        std::memcpy(result.mantissa_.m_words.data(), rbuf + lsb, ns * sizeof(uint64_t));
        result.mantissa_.m_sign = 1;
        result.mantissa_.m_state = NumericState::Normal;
        result.exponent_ = base_exp + static_cast<int64_t>(lsb) * 64;
        result.is_negative_ = result_negative;
        return result;
    }

    // Large size: legacy path (via Int)
    if (exp_diff > 0) {
        Int padded_mantissa = mantissa_ << static_cast<int>(exp_diff);
        if (padded_mantissa >= rhs.mantissa_) {
            Int result_mantissa = padded_mantissa - rhs.mantissa_;
            return Float(std::move(result_mantissa), rhs.exponent_, false);
        } else {
            Int result_mantissa = rhs.mantissa_ - padded_mantissa;
            return Float(std::move(result_mantissa), rhs.exponent_, true);
        }
    } else {
        int neg_exp_diff = static_cast<int>(-exp_diff);
        Int padded_rhs = rhs.mantissa_ << neg_exp_diff;
        if (mantissa_ >= padded_rhs) {
            Int result_mantissa = mantissa_ - padded_rhs;
            return Float(std::move(result_mantissa), exponent_, false);
        } else {
            Int result_mantissa = padded_rhs - mantissa_;
            return Float(std::move(result_mantissa), exponent_, true);
        }
    }
}

Float Float::subtractUnsigned(const Float& rhs) && {
    // rvalue version: reuse mantissa_'s buffer in the large-size path.

    int64_t exp_diff = exponent_ - rhs.exponent_;
    // BUG_FLOAT_SPARSE_MANTISSA_PRECISION_20260509 H4 fix: the early return is decided by MSB distance.
    int64_t magnitude_gap = exp_diff
                          + static_cast<int64_t>(mantissa_.bitLength())
                          - static_cast<int64_t>(rhs.mantissa_.bitLength());

    int64_t exp_threshold = std::max(static_cast<int64_t>(1000),
        static_cast<int64_t>(
            std::max(requested_bits_ < INT_MAX ? requested_bits_ : 0,
                     rhs.requested_bits_ < INT_MAX ? rhs.requested_bits_ : 0)
        ) + 64);

    if (exp_diff == 0) {
        size_t an = mantissa_.size(), bn = rhs.mantissa_.size();
        if (std::max(an, bn) < mpn::KARATSUBA_THRESHOLD) {
            return static_cast<const Float&>(*this).subtractUnsigned(rhs);
        }
        // Large size: reuse the buffer via Int's rvalue operator-
        int cmp_result = mpn::cmp(mantissa_.data(), an, rhs.mantissa_.data(), bn);
        if (cmp_result == 0) return zero();
        if (cmp_result > 0) {
            Int result_mantissa = std::move(mantissa_) - rhs.mantissa_;
            return Float(std::move(result_mantissa), exponent_, false);
        } else {
            Int result_mantissa = rhs.mantissa_ - std::move(mantissa_);
            return Float(std::move(result_mantissa), exponent_, true);
        }
    }

    if (exp_diff > 0) {
        if (magnitude_gap > exp_threshold) {
            // Same as the const version: is_negative_ = false to match the subtractUnsigned contract
            is_negative_ = false;
            return std::move(*this);
        }
    } else {
        if (-magnitude_gap > exp_threshold) {
            Float result = rhs;
            result.is_negative_ = true;
            return result;
        }
    }

    // exp_diff != 0: decide whether it fits in the stack buffer
    size_t lhs_n = (exp_diff > 0) ? mantissa_.size() : rhs.mantissa_.size();
    size_t other_n = (exp_diff > 0) ? rhs.mantissa_.size() : mantissa_.size();
    size_t word_shift = static_cast<size_t>(std::abs(exp_diff)) / 64;
    size_t aligned_max = word_shift + lhs_n + 1;

    constexpr size_t BUF_LIMIT = mpn::KARATSUBA_THRESHOLD * 2;
    if (aligned_max <= BUF_LIMIT && other_n <= BUF_LIMIT) {
        return static_cast<const Float&>(*this).subtractUnsigned(rhs);
    }

    // Large size: exploit move
    if (exp_diff > 0) {
        mantissa_ <<= static_cast<int>(exp_diff);
        if (mantissa_ >= rhs.mantissa_) {
            Int result_mantissa = std::move(mantissa_) - rhs.mantissa_;
            return Float(std::move(result_mantissa), rhs.exponent_, false);
        } else {
            Int result_mantissa = rhs.mantissa_ - std::move(mantissa_);
            return Float(std::move(result_mantissa), rhs.exponent_, true);
        }
    } else {
        Int padded_rhs = rhs.mantissa_ << static_cast<int>(-exp_diff);
        if (mantissa_ >= padded_rhs) {
            Int result_mantissa = std::move(mantissa_) - std::move(padded_rhs);
            return Float(std::move(result_mantissa), exponent_, false);
        } else {
            Int result_mantissa = std::move(padded_rhs) - std::move(mantissa_);
            return Float(std::move(result_mantissa), exponent_, true);
        }
    }
}

// ── in-place addUnsigned/subtractUnsigned ──
// Called from operator+= / -=. Overwrites this->mantissa_'s buffer directly.
// Does not change is_negative_. effective_bits_ / requested_bits_ are set by the caller.

void Float::addUnsignedInPlace(const Float& rhs) {
    int64_t exp_diff = exponent_ - rhs.exponent_;
    // BUG_FLOAT_SPARSE_MANTISSA_PRECISION_20260509 H4 fix: the early return is decided by MSB distance.
    int64_t magnitude_gap = exp_diff
                          + static_cast<int64_t>(mantissa_.bitLength())
                          - static_cast<int64_t>(rhs.mantissa_.bitLength());

    int64_t exp_threshold = std::max(static_cast<int64_t>(1000),
        static_cast<int64_t>(
            std::max(requested_bits_ < INT_MAX ? requested_bits_ : 0,
                     rhs.requested_bits_ < INT_MAX ? rhs.requested_bits_ : 0)
        ) + 64);

    if (exp_diff == 0) {
        // Equal exponents: add the mantissas directly
        const uint64_t* a = mantissa_.data();
        const uint64_t* b = rhs.mantissa_.data();
        size_t an = mantissa_.size(), bn = rhs.mantissa_.size();
        if (an < bn) { std::swap(a, b); std::swap(an, bn); }

        if (an < mpn::KARATSUBA_THRESHOLD) {
            uint64_t rbuf[mpn::KARATSUBA_THRESHOLD + 1];
            uint64_t carry = mpn::add(rbuf, a, an, b, bn);
            size_t rn = an;
            if (carry) { rbuf[rn] = carry; rn++; }
            size_t lsb = 0;
            while (lsb < rn && rbuf[lsb] == 0) ++lsb;
            size_t ns = rn - lsb;
            mantissa_.m_words.resize_uninitialized(ns);
            std::memcpy(mantissa_.m_words.data(), rbuf + lsb, ns * sizeof(uint64_t));
            mantissa_.m_sign = 1;
            mantissa_.m_state = NumericState::Normal;
            exponent_ += static_cast<int64_t>(lsb) * 64;
            return;
        }
        // Large size: via Int
        mantissa_ = std::move(mantissa_) + rhs.mantissa_;
        return;
    }

    // exp_diff != 0
    if (exp_diff > 0) {
        if (magnitude_gap > exp_threshold) return; // ignore rhs
    } else {
        if (-magnitude_gap > exp_threshold) {
            // ignore this, adopt rhs
            mantissa_ = rhs.mantissa_;
            exponent_ = rhs.exponent_;
            return;
        }
    }

    const uint64_t* hi_data;
    size_t hi_n;
    const uint64_t* lo_data;
    size_t lo_n;
    int64_t abs_diff;
    int64_t base_exp;

    if (exp_diff > 0) {
        hi_data = mantissa_.data(); hi_n = mantissa_.size();
        lo_data = rhs.mantissa_.data(); lo_n = rhs.mantissa_.size();
        abs_diff = exp_diff;
        base_exp = rhs.exponent_;
    } else {
        hi_data = rhs.mantissa_.data(); hi_n = rhs.mantissa_.size();
        lo_data = mantissa_.data(); lo_n = mantissa_.size();
        abs_diff = -exp_diff;
        base_exp = exponent_;
    }

    size_t word_shift = static_cast<size_t>(abs_diff) / 64;
    unsigned bit_shift = static_cast<unsigned>(abs_diff % 64);

    constexpr size_t BUF_LIMIT = mpn::KARATSUBA_THRESHOLD * 2;
    size_t aligned_max = word_shift + hi_n + 1;
    if (aligned_max <= BUF_LIMIT && lo_n <= BUF_LIMIT) {
        uint64_t aligned[BUF_LIMIT + 1];
        size_t aligned_n;
        std::memset(aligned, 0, word_shift * sizeof(uint64_t));
        if (bit_shift == 0) {
            std::memcpy(aligned + word_shift, hi_data, hi_n * sizeof(uint64_t));
            aligned_n = word_shift + hi_n;
        } else {
            uint64_t carry = mpn::lshift(aligned + word_shift, hi_data, hi_n, bit_shift);
            aligned_n = word_shift + hi_n;
            if (carry) { aligned[aligned_n] = carry; aligned_n++; }
        }

        uint64_t rbuf[BUF_LIMIT + 2];
        const uint64_t* big;
        size_t big_n;
        const uint64_t* small;
        size_t small_n;
        if (aligned_n >= lo_n) {
            big = aligned; big_n = aligned_n;
            small = lo_data; small_n = lo_n;
        } else {
            big = lo_data; big_n = lo_n;
            small = aligned; small_n = aligned_n;
        }
        uint64_t c = mpn::add(rbuf, big, big_n, small, small_n);
        size_t rn = big_n;
        if (c) { rbuf[rn] = c; rn++; }
        size_t lsb = 0;
        while (lsb < rn && rbuf[lsb] == 0) ++lsb;
        size_t ns = rn - lsb;
        mantissa_.m_words.resize_uninitialized(ns);
        std::memcpy(mantissa_.m_words.data(), rbuf + lsb, ns * sizeof(uint64_t));
        mantissa_.m_sign = 1;
        mantissa_.m_state = NumericState::Normal;
        exponent_ = base_exp + static_cast<int64_t>(lsb) * 64;
        return;
    }

    // Large size: via Int
    if (exp_diff > 0) {
        mantissa_ <<= static_cast<int>(exp_diff);
        mantissa_ = std::move(mantissa_) + rhs.mantissa_;
        exponent_ = rhs.exponent_;
    } else {
        Int padded_rhs = rhs.mantissa_ << static_cast<int>(-exp_diff);
        mantissa_ = std::move(mantissa_) + std::move(padded_rhs);
    }
}

void Float::subtractUnsignedInPlace(const Float& rhs) {
    // Same semantics as subtractUnsigned:
    // compute |this| - |rhs|, and set is_negative_ = true if |this| < |rhs|

    int64_t exp_diff = exponent_ - rhs.exponent_;
    // BUG_FLOAT_SPARSE_MANTISSA_PRECISION_20260509 H4 fix: the early return is decided by MSB distance.
    int64_t magnitude_gap = exp_diff
                          + static_cast<int64_t>(mantissa_.bitLength())
                          - static_cast<int64_t>(rhs.mantissa_.bitLength());

    int64_t exp_threshold = std::max(static_cast<int64_t>(1000),
        static_cast<int64_t>(
            std::max(requested_bits_ < INT_MAX ? requested_bits_ : 0,
                     rhs.requested_bits_ < INT_MAX ? rhs.requested_bits_ : 0)
        ) + 64);

    if (exp_diff == 0) {
        const uint64_t* a = mantissa_.data();
        const uint64_t* b = rhs.mantissa_.data();
        size_t an = mantissa_.size(), bn = rhs.mantissa_.size();

        int cmp_result = mpn::cmp(a, an, b, bn);
        if (cmp_result == 0) {
            mantissa_.m_words.resize_uninitialized(0);
            mantissa_.m_sign = 0;
            mantissa_.m_state = NumericState::Normal;
            is_negative_ = false;
            return;
        }

        bool result_negative;
        if (cmp_result < 0) {
            std::swap(a, b); std::swap(an, bn);
            result_negative = true;
        } else {
            result_negative = false;
        }

        if (an < mpn::KARATSUBA_THRESHOLD) {
            uint64_t rbuf[mpn::KARATSUBA_THRESHOLD];
            mpn::sub(rbuf, a, an, b, bn);
            size_t rn = mpn::normalized_size(rbuf, an);
            if (rn == 0) {
                mantissa_.m_words.resize_uninitialized(0);
                mantissa_.m_sign = 0;
                mantissa_.m_state = NumericState::Normal;
                is_negative_ = false;
                return;
            }
            size_t lsb = 0;
            while (lsb < rn && rbuf[lsb] == 0) ++lsb;
            if (lsb >= rn) {
                mantissa_.m_words.resize_uninitialized(0);
                mantissa_.m_sign = 0;
                mantissa_.m_state = NumericState::Normal;
                is_negative_ = false;
                return;
            }
            size_t ns = rn - lsb;
            mantissa_.m_words.resize_uninitialized(ns);
            std::memcpy(mantissa_.m_words.data(), rbuf + lsb, ns * sizeof(uint64_t));
            mantissa_.m_sign = 1;
            mantissa_.m_state = NumericState::Normal;
            exponent_ += static_cast<int64_t>(lsb) * 64;
            is_negative_ = result_negative;
            return;
        }
        // Large size
        if (cmp_result > 0) {
            mantissa_ = std::move(mantissa_) - rhs.mantissa_;
        } else {
            mantissa_ = rhs.mantissa_ - std::move(mantissa_);
        }
        is_negative_ = result_negative;
        return;
    }

    // exp_diff != 0
    if (exp_diff > 0) {
        if (magnitude_gap > exp_threshold) {
            // |lhs| dominates: result ≈ |this|. Per the subtractUnsigned contract (is_negative_ is a flip flag),
            // is_negative_ = false (no flip).
            is_negative_ = false;
            return;
        }
    } else {
        if (-magnitude_gap > exp_threshold) {
            // ignore this, return -rhs
            mantissa_ = rhs.mantissa_;
            exponent_ = rhs.exponent_;
            is_negative_ = true;
            return;
        }
    }

    const uint64_t* lhs_data;
    size_t lhs_n;
    const uint64_t* rhs_data;
    size_t rhs_n;
    int64_t abs_diff;
    int64_t base_exp;
    bool lhs_is_this;

    if (exp_diff > 0) {
        lhs_data = mantissa_.data(); lhs_n = mantissa_.size();
        rhs_data = rhs.mantissa_.data(); rhs_n = rhs.mantissa_.size();
        abs_diff = exp_diff;
        base_exp = rhs.exponent_;
        lhs_is_this = true;
    } else {
        lhs_data = rhs.mantissa_.data(); lhs_n = rhs.mantissa_.size();
        rhs_data = mantissa_.data(); rhs_n = mantissa_.size();
        abs_diff = -exp_diff;
        base_exp = exponent_;
        lhs_is_this = false;
    }

    size_t word_shift = static_cast<size_t>(abs_diff) / 64;
    unsigned bit_shift = static_cast<unsigned>(abs_diff % 64);

    constexpr size_t BUF_LIMIT = mpn::KARATSUBA_THRESHOLD * 2;
    size_t aligned_max = word_shift + lhs_n + 1;
    if (aligned_max <= BUF_LIMIT && rhs_n <= BUF_LIMIT) {
        uint64_t aligned[BUF_LIMIT + 1];
        size_t aligned_n;
        std::memset(aligned, 0, word_shift * sizeof(uint64_t));
        if (bit_shift == 0) {
            std::memcpy(aligned + word_shift, lhs_data, lhs_n * sizeof(uint64_t));
            aligned_n = word_shift + lhs_n;
        } else {
            uint64_t carry = mpn::lshift(aligned + word_shift, lhs_data, lhs_n, bit_shift);
            aligned_n = word_shift + lhs_n;
            if (carry) { aligned[aligned_n] = carry; aligned_n++; }
        }

        int cmp = mpn::cmp(aligned, aligned_n, rhs_data, rhs_n);
        if (cmp == 0) {
            mantissa_.m_words.resize_uninitialized(0);
            mantissa_.m_sign = 0;
            mantissa_.m_state = NumericState::Normal;
            is_negative_ = false;
            exponent_ = 0;
            return;
        }

        bool result_negative;
        const uint64_t* big;
        size_t big_n;
        const uint64_t* small;
        size_t small_n;

        if (cmp > 0) {
            big = aligned; big_n = aligned_n;
            small = rhs_data; small_n = rhs_n;
            result_negative = !lhs_is_this;
        } else {
            big = rhs_data; big_n = rhs_n;
            small = aligned; small_n = aligned_n;
            result_negative = lhs_is_this;
        }

        uint64_t rbuf[BUF_LIMIT + 1];
        mpn::sub(rbuf, big, big_n, small, small_n);
        size_t rn = mpn::normalized_size(rbuf, big_n);
        if (rn == 0) {
            mantissa_.m_words.resize_uninitialized(0);
            mantissa_.m_sign = 0;
            mantissa_.m_state = NumericState::Normal;
            is_negative_ = false;
            exponent_ = 0;
            return;
        }
        size_t lsb = 0;
        while (lsb < rn && rbuf[lsb] == 0) ++lsb;
        if (lsb >= rn) {
            mantissa_.m_words.resize_uninitialized(0);
            mantissa_.m_sign = 0;
            mantissa_.m_state = NumericState::Normal;
            is_negative_ = false;
            exponent_ = 0;
            return;
        }
        size_t ns = rn - lsb;
        mantissa_.m_words.resize_uninitialized(ns);
        std::memcpy(mantissa_.m_words.data(), rbuf + lsb, ns * sizeof(uint64_t));
        mantissa_.m_sign = 1;
        mantissa_.m_state = NumericState::Normal;
        exponent_ = base_exp + static_cast<int64_t>(lsb) * 64;
        is_negative_ = result_negative;
        return;
    }

    // Large size: via Int
    if (exp_diff > 0) {
        mantissa_ <<= static_cast<int>(exp_diff);
        if (mantissa_ >= rhs.mantissa_) {
            mantissa_ = std::move(mantissa_) - rhs.mantissa_;
            is_negative_ = false;
        } else {
            mantissa_ = rhs.mantissa_ - std::move(mantissa_);
            is_negative_ = true;
        }
        exponent_ = rhs.exponent_;
    } else {
        Int padded_rhs = rhs.mantissa_ << static_cast<int>(-exp_diff);
        if (mantissa_ >= padded_rhs) {
            mantissa_ = std::move(mantissa_) - std::move(padded_rhs);
            is_negative_ = false;
        } else {
            mantissa_ = std::move(padded_rhs) - std::move(mantissa_);
            is_negative_ = true;
        }
    }
}

int Float::compareUnsigned(const Float& rhs) const {
    // Compare special values
    if (isNaN() || rhs.isNaN()) return 0;  // NaN is incomparable
    if (isInfinity() && rhs.isInfinity()) return 0;  // equal infinities are equal
    if (isInfinity()) return 1;  // infinity is greater than every other value
    if (rhs.isInfinity()) return -1;  // every other value is less than infinity
    if (isZero() && rhs.isZero()) return 0;  // both zero means equal
    if (isZero()) return -1;  // 0 < positive value
    if (rhs.isZero()) return 1;  // positive value > 0

    // Value magnitude = 2^(bitLength + exponent - 1) ~ 2^(bitLength + exponent)
    // First compare by order of magnitude
    int64_t mag_this = static_cast<int64_t>(mantissa_.bitLength()) + exponent_;
    int64_t mag_rhs = static_cast<int64_t>(rhs.mantissa_.bitLength()) + rhs.exponent_;

    if (mag_this > mag_rhs) return 1;
    if (mag_this < mag_rhs) return -1;

    // Same order of magnitude: align the exponents and compare the mantissas
    int64_t exp_diff = exponent_ - rhs.exponent_;
    if (exp_diff > 0) {
        // this's exponent is larger (mantissa smaller): left-shift this to align
        Int shifted = mantissa_ << static_cast<int>(exp_diff);
        if (shifted > rhs.mantissa_) return 1;
        if (shifted < rhs.mantissa_) return -1;
        return 0;
    } else if (exp_diff < 0) {
        // rhs's exponent is larger (mantissa smaller): left-shift rhs to align
        Int shifted = rhs.mantissa_ << static_cast<int>(-exp_diff);
        if (mantissa_ > shifted) return 1;
        if (mantissa_ < shifted) return -1;
        return 0;
    } else {
        // If the exponents are equal, compare the mantissas directly
        if (mantissa_ > rhs.mantissa_) return 1;
        if (mantissa_ < rhs.mantissa_) return -1;
        return 0;
    }
}

//======================================================================
// numeric_traits<Float> implementation
//======================================================================

Float numeric_traits<Float>::abs(const Float& value) {
    if (value.isNaN()) return Float::nan();
    if (value.isInfinity()) return Float::positiveInfinity();
    
    Float result = value;
    result.is_negative_ = false;
    return result;
}

Float numeric_traits<Float>::norm(const Float& value) {
    if (value.isNaN()) return Float::nan();
    if (value.isInfinity()) return Float::positiveInfinity();
    
    // Compute |x|^2
    Float abs_value = abs(value);
    return abs_value * abs_value;
}

//======================================================================
// Operator implementations
//======================================================================

// Merge of requested_bits_ treating INT_MAX (exact) as neutral
// MIN_PROPAGATION: align to the lower one (save computation)
// MAX_PROPAGATION: keep the higher one (precision-safe)
static int mergeRequested(int req_a, int req_b) {
    if constexpr (PRECISION_POLICY == PrecisionPolicy::MIN_PROPAGATION) {
        if (req_a >= INT_MAX) return req_b;
        if (req_b >= INT_MAX) return req_a;
        return std::min(req_a, req_b);
    } else {
        if (req_a >= INT_MAX && req_b >= INT_MAX) return INT_MAX;
        if (req_a >= INT_MAX) return req_b;
        if (req_b >= INT_MAX) return req_a;
        return std::max(req_a, req_b);
    }
}

// Merge of effective_bits_ treating INT_MAX (exact) as neutral
// MIN_PROPAGATION: align to the lower one (information-theoretically accurate, used for pre-truncation)
// MAX_PROPAGATION: keep the higher one (no pre-truncation, for benchmark comparison)
static int mergeEffective(int eff_a, int eff_b) {
    if constexpr (PRECISION_POLICY == PrecisionPolicy::MIN_PROPAGATION) {
        return std::min(eff_a, eff_b);  // min(INT_MAX, x) = x (neutral)
    } else {
        if (eff_a >= INT_MAX && eff_b >= INT_MAX) return INT_MAX;
        if (eff_a >= INT_MAX) return eff_b;
        if (eff_b >= INT_MAX) return eff_a;
        return std::max(eff_a, eff_b);
    }
}

// Consolidate the exact÷exact precision resolution of division (operator/ and FloatOps::div) in one place.
//  - determine both_exact (both eff/req are INT_MAX)
//  - the working-precision fallback for exact inputs = contextBits() (the PrecisionScope if present, otherwise
//    defaultPrecision). Guarantees the same poison countermeasure on both division paths.
//  - also fires the poison audit hook (auditContextFreeFallback) on both paths without omission.
// The caller receives both_exact/eff_calc/req_calc/compute_prec in its own style
// (operator/ assigns to eff/req to reproduce the overwrite, FloatOps::div uses eff_calc/req_calc directly).
struct DivPrecResolve { bool both_exact; int eff_calc; int req_calc; int compute_prec; };
static DivPrecResolve resolveDivPrecision(int eff, int req) {
    DivPrecResolve p;
    p.both_exact = (eff >= INT_MAX);
    if (p.both_exact) Float::auditContextFreeFallback();  // poison audit (counts only when no scope is set)
    p.eff_calc = (eff >= INT_MAX) ? Float::contextBits() : eff;
    p.req_calc = (req >= INT_MAX) ? Float::contextBits() : req;
    p.compute_prec = p.both_exact ? std::max(p.eff_calc, p.req_calc) : (p.eff_calc + 32);
    return p;
}

Float operator+(const Float& lhs, const Float& rhs) {
    // Handle special values
    if (lhs.isNaN() || rhs.isNaN()) [[unlikely]] return Float::nan();
    if (lhs.isInfinity() && rhs.isInfinity()) [[unlikely]] {
        if (lhs.isNegative() == rhs.isNegative()) {
            return lhs.isNegative() ? Float::negativeInfinity() : Float::positiveInfinity();
        }
        return Float::nan();
    }
    if (lhs.isInfinity()) [[unlikely]] return lhs;
    if (rhs.isInfinity()) [[unlikely]] return rhs;

    // If one of them is zero
    if (lhs.isZero()) [[unlikely]] return rhs;
    if (rhs.isZero()) [[unlikely]] return lhs;

    // Prepare to propagate the precision fields
    int eff_a = lhs.effective_bits_, eff_b = rhs.effective_bits_;
    int req = mergeRequested(lhs.requested_bits_, rhs.requested_bits_);
    int eff_min = mergeEffective(eff_a, eff_b);

    // ── P1-1: small-precision fast path (≤2 limbs, |exp_diff| < 128) ──
    // Avoid the function call and threshold computation of addUnsigned/subtractUnsigned
    {
        size_t an = lhs.mantissa_.size(), bn = rhs.mantissa_.size();
        if (an <= 2 && bn <= 2) {
            int64_t exp_diff = lhs.exponent_ - rhs.exponent_;
            if (static_cast<uint64_t>(exp_diff + 128) <= 256u) {  // |exp_diff| <= 128
                bool same_sign = (lhs.isNegative() == rhs.isNegative());

                // Left-shift the one with the larger exponent to align
                const uint64_t* hi_data;  // the side with the larger exponent
                size_t hi_n;
                const uint64_t* lo_data;  // the side with the smaller exponent
                size_t lo_n;
                int64_t abs_diff;
                int64_t base_exp;
                bool hi_is_lhs;

                if (exp_diff >= 0) {
                    hi_data = lhs.mantissa_.data(); hi_n = an;
                    lo_data = rhs.mantissa_.data(); lo_n = bn;
                    abs_diff = exp_diff;
                    base_exp = rhs.exponent_;
                    hi_is_lhs = true;
                } else {
                    hi_data = rhs.mantissa_.data(); hi_n = bn;
                    lo_data = lhs.mantissa_.data(); lo_n = an;
                    abs_diff = -exp_diff;
                    base_exp = lhs.exponent_;
                    hi_is_lhs = false;
                }

                // Left-shift hi → aligned[0..4] (at most 128 + 128 = 256 bits = 4 limbs + carry)
                uint64_t aligned[5] = {0};
                size_t aligned_n;
                size_t word_shift = static_cast<size_t>(abs_diff) / 64;
                unsigned bit_shift = static_cast<unsigned>(abs_diff) & 63;

                if (bit_shift == 0) {
                    for (size_t i = 0; i < hi_n; i++) aligned[i + word_shift] = hi_data[i];
                    aligned_n = hi_n + word_shift;
                } else {
                    for (size_t i = 0; i < hi_n; i++) {
                        aligned[i + word_shift] |= hi_data[i] << bit_shift;
                        aligned[i + word_shift + 1] = hi_data[i] >> (64 - bit_shift);
                    }
                    aligned_n = hi_n + word_shift + (aligned[hi_n + word_shift] != 0 ? 1 : 0);
                }

                uint64_t rbuf[6] = {0};
                size_t rn;
                bool result_neg;

                if (same_sign) {
                    // Mantissa addition
                    size_t max_n = std::max(aligned_n, lo_n);
                    unsigned char carry = 0;
                    for (size_t i = 0; i < max_n; i++) {
                        uint64_t av = (i < aligned_n) ? aligned[i] : 0;
                        uint64_t bv = (i < lo_n) ? lo_data[i] : 0;
                        carry = _addcarry_u64(carry, av, bv, &rbuf[i]);
                    }
                    rn = max_n;
                    if (carry) { rbuf[rn] = 1; rn++; }
                    result_neg = lhs.isNegative();

                    // Remove trailing zero words + construct the Float
                    size_t lsb = 0;
                    while (lsb < rn && rbuf[lsb] == 0) ++lsb;
                    size_t ns = rn - lsb;
                    return Float::fromRawLimbs(rbuf + lsb, ns,
                        base_exp + static_cast<int64_t>(lsb) * 64,
                        result_neg, eff_min, req);
                } else {
                    // Mantissa subtraction (opposite signs)
                    size_t max_n = std::max(aligned_n, lo_n);
                    // Magnitude comparison
                    int cmp = 0;
                    for (size_t i = max_n; i-- > 0; ) {
                        uint64_t av = (i < aligned_n) ? aligned[i] : 0;
                        uint64_t bv = (i < lo_n) ? lo_data[i] : 0;
                        if (av != bv) { cmp = (av > bv) ? 1 : -1; break; }
                    }
                    if (cmp == 0) {
                        Float z = Float::zero();
                        if (eff_min >= INT_MAX) {
                            z.effective_bits_ = INT_MAX;
                            z.requested_bits_ = INT_MAX;
                        } else {
                            z.effective_bits_ = 0;
                            z.requested_bits_ = req;
                        }
                        return z;
                    }

                    const uint64_t* big;
                    size_t big_n;
                    const uint64_t* small_p;
                    size_t small_n;
                    bool lhs_larger;
                    if (cmp > 0) {
                        big = aligned; big_n = aligned_n;
                        small_p = lo_data; small_n = lo_n;
                        lhs_larger = hi_is_lhs;
                    } else {
                        big = lo_data; big_n = lo_n;
                        small_p = aligned; small_n = aligned_n;
                        lhs_larger = !hi_is_lhs;
                    }

                    unsigned char borrow = 0;
                    for (size_t i = 0; i < big_n; i++) {
                        uint64_t sv = (i < small_n) ? small_p[i] : 0;
                        borrow = _subborrow_u64(borrow, big[i], sv, &rbuf[i]);
                    }
                    rn = big_n;
                    while (rn > 0 && rbuf[rn - 1] == 0) --rn;

                    if (rn == 0) {
                        Float z = Float::zero();
                        z.effective_bits_ = (eff_min >= INT_MAX) ? INT_MAX : 0;
                        z.requested_bits_ = (eff_min >= INT_MAX) ? INT_MAX : req;
                        return z;
                    }

                    result_neg = lhs_larger ? lhs.isNegative() : rhs.isNegative();

                    size_t lsb = 0;
                    while (lsb < rn && rbuf[lsb] == 0) ++lsb;
                    size_t ns = rn - lsb;

                    // Cancellation computation
                    int64_t mag_lhs = static_cast<int64_t>(lhs.mantissa_.bitLength()) + lhs.exponent_;
                    int64_t mag_rhs = static_cast<int64_t>(rhs.mantissa_.bitLength()) + rhs.exponent_;
                    int64_t max_mag = std::max(mag_lhs, mag_rhs);
                    int64_t result_exp = base_exp + static_cast<int64_t>(lsb) * 64;
                    // Compute result's bitLength (before fromRawLimbs)
                    int result_bl = static_cast<int>((ns - 1) * 64 +
                        (64 - std::countl_zero(rbuf[lsb + ns - 1])));
                    int64_t result_mag = static_cast<int64_t>(result_bl) + result_exp;
                    int lost_bits = static_cast<int>(std::max(int64_t(0), max_mag - result_mag));

                    int eff_result;
                    if (eff_min >= INT_MAX) {
                        eff_result = INT_MAX;
                    } else {
                        eff_result = std::max(1, eff_min - lost_bits);
                    }
                    return Float::fromRawLimbs(rbuf + lsb, ns,
                        result_exp, result_neg, eff_result, req);
                }
            }
        }
    }

    if (lhs.isNegative() == rhs.isNegative()) {
        // Same sign → addition (no cancellation)
        Float result = lhs.addUnsigned(rhs);
        result.is_negative_ = lhs.isNegative();
        result.effective_bits_ = eff_min;
        result.requested_bits_ = req;
        return result;
    }
    else {
        // Opposite signs → subtraction of magnitudes (with cancellation)
        // Use subtractUnsigned's internal comparison result instead of calling compareUnsigned
        int64_t mag_lhs = static_cast<int64_t>(lhs.mantissa_.bitLength()) + lhs.exponent_;
        int64_t mag_rhs = static_cast<int64_t>(rhs.mantissa_.bitLength()) + rhs.exponent_;
        int64_t max_mag = std::max(mag_lhs, mag_rhs);

        Float result = lhs.subtractUnsigned(rhs);

        if (result.isZero()) {
            Float z = Float::zero();
            if (eff_min >= INT_MAX) {
                z.effective_bits_ = INT_MAX;
                z.requested_bits_ = INT_MAX;
            } else {
                z.effective_bits_ = 0;
                z.requested_bits_ = req;
            }
            return z;
        }

        // subtractUnsigned: is_negative_=true means |lhs| < |rhs|
        result.is_negative_ = result.is_negative_ ? rhs.isNegative() : lhs.isNegative();

        // Reduction of effective bits due to cancellation
        int64_t result_mag = static_cast<int64_t>(result.mantissa_.bitLength()) + result.exponent_;
        int lost_bits = static_cast<int>(std::max(int64_t(0), max_mag - result_mag));

        if (eff_min >= INT_MAX) {
            result.effective_bits_ = INT_MAX;
        } else {
            result.effective_bits_ = std::max(1, eff_min - lost_bits);
        }
        result.requested_bits_ = req;
        return result;
    }
}

Float operator+(Float&& lhs, const Float& rhs) {
    // Self-operation safety: a += a → operator+(std::move(a), a) can make &lhs == &rhs
    if (&lhs == &rhs) return operator+(static_cast<const Float&>(lhs), rhs);

    // Within SBO range, delegate to the const& version (no rvalue-optimization benefit, avoids dispatch cost)
    constexpr size_t SBO_CAP = 9;
    if (lhs.mantissa_.size() <= SBO_CAP && rhs.mantissa_.size() <= SBO_CAP) {
        return operator+(static_cast<const Float&>(lhs), rhs);
    }

    // Handle special values
    if (lhs.isNaN() || rhs.isNaN()) [[unlikely]] return Float::nan();
    if (lhs.isInfinity() && rhs.isInfinity()) [[unlikely]] {
        if (lhs.isNegative() == rhs.isNegative()) {
            return lhs.isNegative() ? Float::negativeInfinity() : Float::positiveInfinity();
        }
        return Float::nan();
    }
    if (lhs.isInfinity()) [[unlikely]] return std::move(lhs);
    if (rhs.isInfinity()) [[unlikely]] return rhs;

    if (lhs.isZero()) [[unlikely]] return rhs;
    if (rhs.isZero()) [[unlikely]] return std::move(lhs);

    // Prepare to propagate the precision fields
    int eff_a = lhs.effective_bits_, eff_b = rhs.effective_bits_;
    int req = mergeRequested(lhs.requested_bits_, rhs.requested_bits_);
    int eff_min = mergeEffective(eff_a, eff_b);

    bool lhs_neg = lhs.isNegative();

    if (lhs_neg == rhs.isNegative()) {
        Float result = std::move(lhs).addUnsigned(rhs);
        result.is_negative_ = lhs_neg;
        result.effective_bits_ = eff_min;
        result.requested_bits_ = req;
        return result;
    }
    else {
        // Opposite signs: use subtractUnsigned's internal comparison instead of calling compareUnsigned
        int64_t mag_lhs = static_cast<int64_t>(lhs.mantissa_.bitLength()) + lhs.exponent_;
        int64_t mag_rhs = static_cast<int64_t>(rhs.mantissa_.bitLength()) + rhs.exponent_;
        int64_t max_mag = std::max(mag_lhs, mag_rhs);

        Float result = std::move(lhs).subtractUnsigned(rhs);

        if (result.isZero()) {
            Float z = Float::zero();
            if (eff_min >= INT_MAX) {
                z.effective_bits_ = INT_MAX;
                z.requested_bits_ = INT_MAX;
            } else {
                z.effective_bits_ = 0;
                z.requested_bits_ = req;
            }
            return z;
        }

        result.is_negative_ = result.is_negative_ ? rhs.isNegative() : lhs_neg;

        int64_t result_mag = static_cast<int64_t>(result.mantissa_.bitLength()) + result.exponent_;
        int lost_bits = static_cast<int>(std::max(int64_t(0), max_mag - result_mag));

        if (eff_min >= INT_MAX) {
            result.effective_bits_ = INT_MAX;
        } else {
            result.effective_bits_ = std::max(1, eff_min - lost_bits);
        }
        result.requested_bits_ = req;
        return result;
    }
}

Float operator+(const Float& lhs, Float&& rhs) {
    // Addition is commutative: exploit rhs's rvalue
    if (&lhs == &rhs) return operator+(static_cast<const Float&>(lhs), rhs);
    return operator+(std::move(rhs), lhs);
}

Float operator+(Float&& lhs, Float&& rhs) {
    // Reuse the larger buffer
    if (lhs.mantissa_.size() >= rhs.mantissa_.size()) {
        return operator+(std::move(lhs), static_cast<const Float&>(rhs));
    }
    return operator+(std::move(rhs), static_cast<const Float&>(lhs));
}

Float operator-(const Float& lhs, const Float& rhs) {
    // Handle special values
    if (lhs.isNaN() || rhs.isNaN()) [[unlikely]] return Float::nan();
    if (lhs.isInfinity() && rhs.isInfinity()) [[unlikely]] {
        if (lhs.isNegative() != rhs.isNegative()) {
            return lhs.isNegative() ? Float::negativeInfinity() : Float::positiveInfinity();
        }
        return Float::nan();
    }
    if (lhs.isInfinity()) [[unlikely]] return lhs;
    if (rhs.isInfinity()) [[unlikely]] return rhs.isNegative() ? Float::positiveInfinity() : Float::negativeInfinity();

    // If one of them is zero
    if (lhs.isZero()) [[unlikely]] {
        Float result = rhs;
        result.is_negative_ = !result.is_negative_;
        return result;
    }
    if (rhs.isZero()) [[unlikely]] return lhs;

    // Prepare to propagate the precision fields
    int req = mergeRequested(lhs.requested_bits_, rhs.requested_bits_);
    int eff_min = mergeEffective(lhs.effective_bits_, rhs.effective_bits_);

    if (lhs.isNegative() != rhs.isNegative()) {
        // Opposite signs: (+a)-(-b)=a+b, (-a)-(+b)=-(a+b) → mantissa addition (no cancellation)
        Float result = lhs.addUnsigned(rhs);
        result.is_negative_ = lhs.isNegative();
        result.effective_bits_ = eff_min;
        result.requested_bits_ = req;
        return result;
    }
    else {
        // Same sign: (+a)-(+b)=a-b, (-a)-(-b)=b-a → mantissa subtraction (with cancellation)
        int64_t mag_lhs = static_cast<int64_t>(lhs.mantissa_.bitLength()) + lhs.exponent_;
        int64_t mag_rhs = static_cast<int64_t>(rhs.mantissa_.bitLength()) + rhs.exponent_;
        int64_t max_mag = std::max(mag_lhs, mag_rhs);

        Float result = lhs.subtractUnsigned(rhs);

        if (result.isZero()) {
            Float z = Float::zero();
            if (eff_min >= INT_MAX) {
                z.effective_bits_ = INT_MAX;
                z.requested_bits_ = INT_MAX;
            } else {
                z.effective_bits_ = 0;
                z.requested_bits_ = req;
            }
            return z;
        }

        // subtractUnsigned: is_negative_=true means |lhs| < |rhs|
        // |lhs|>=|rhs|: sign=lhs.neg, |lhs|<|rhs|: sign=!lhs.neg
        result.is_negative_ = result.is_negative_ ? !lhs.isNegative() : lhs.isNegative();

        // Reduction of effective bits due to cancellation
        int64_t result_mag = static_cast<int64_t>(result.mantissa_.bitLength()) + result.exponent_;
        int lost_bits = static_cast<int>(std::max(int64_t(0), max_mag - result_mag));

        if (eff_min >= INT_MAX) {
            result.effective_bits_ = INT_MAX;
        } else {
            result.effective_bits_ = std::max(1, eff_min - lost_bits);
        }
        result.requested_bits_ = req;
        return result;
    }
}

Float operator-(Float&& lhs, const Float& rhs) {
    if (&lhs == &rhs) return operator-(static_cast<const Float&>(lhs), rhs);

    // Within SBO range, delegate to the const& version (no rvalue-optimization benefit)
    constexpr size_t SBO_CAP = 9;
    if (lhs.mantissa_.size() <= SBO_CAP && rhs.mantissa_.size() <= SBO_CAP) {
        return operator-(static_cast<const Float&>(lhs), rhs);
    }

    // Large-size path: handle special values
    if (lhs.isNaN() || rhs.isNaN()) [[unlikely]] return Float::nan();
    if (lhs.isInfinity() && rhs.isInfinity()) [[unlikely]] {
        if (lhs.isNegative() != rhs.isNegative()) {
            return lhs.isNegative() ? Float::negativeInfinity() : Float::positiveInfinity();
        }
        return Float::nan();
    }
    if (lhs.isInfinity()) [[unlikely]] return std::move(lhs);
    if (rhs.isInfinity()) [[unlikely]] return rhs.isNegative() ? Float::positiveInfinity() : Float::negativeInfinity();
    if (lhs.isZero()) [[unlikely]] { Float r = rhs; r.is_negative_ = !r.is_negative_; return r; }
    if (rhs.isZero()) [[unlikely]] return std::move(lhs);

    int req = mergeRequested(lhs.requested_bits_, rhs.requested_bits_);
    int eff_min = mergeEffective(lhs.effective_bits_, rhs.effective_bits_);
    bool lhs_neg = lhs.isNegative();

    if (lhs_neg != rhs.isNegative()) {
        // Opposite signs: mantissa addition (no cancellation)
        Float result = std::move(lhs).addUnsigned(rhs);
        result.is_negative_ = lhs_neg;
        result.effective_bits_ = eff_min;
        result.requested_bits_ = req;
        return result;
    } else {
        // Same sign: mantissa subtraction (with cancellation)
        int64_t mag_lhs = static_cast<int64_t>(lhs.mantissa_.bitLength()) + lhs.exponent_;
        int64_t mag_rhs = static_cast<int64_t>(rhs.mantissa_.bitLength()) + rhs.exponent_;
        int64_t max_mag = std::max(mag_lhs, mag_rhs);

        Float result = std::move(lhs).subtractUnsigned(rhs);

        if (result.isZero()) {
            Float z = Float::zero();
            if (eff_min >= INT_MAX) { z.effective_bits_ = INT_MAX; z.requested_bits_ = INT_MAX; }
            else { z.effective_bits_ = 0; z.requested_bits_ = req; }
            return z;
        }

        result.is_negative_ = result.is_negative_ ? !lhs_neg : lhs_neg;

        int64_t result_mag = static_cast<int64_t>(result.mantissa_.bitLength()) + result.exponent_;
        int lost_bits = static_cast<int>(std::max(int64_t(0), max_mag - result_mag));
        if (eff_min >= INT_MAX) { result.effective_bits_ = INT_MAX; }
        else { result.effective_bits_ = std::max(1, eff_min - lost_bits); }
        result.requested_bits_ = req;
        return result;
    }
}

Float operator-(const Float& lhs, Float&& rhs) {
    // Move rhs, flip its sign, and add
    rhs.is_negative_ = !rhs.is_negative_;
    return lhs + std::move(rhs);
}

Float operator-(Float&& lhs, Float&& rhs) {
    // Flip rhs's sign and add (both rvalue)
    rhs.is_negative_ = !rhs.is_negative_;
    return std::move(lhs) + std::move(rhs);
}

Float operator+(const Float& value) {
    return value;
}

Float operator-(const Float& value) {
    if (value.isNaN()) return Float::nan();
    if (value.isZero()) return value;  // preserve the precision fields
    if (value.isInfinity()) {
        return value.isNegative() ? Float::positiveInfinity() : Float::negativeInfinity();
    }

    Float result = value;
    result.is_negative_ = !result.is_negative_;
    return result;
}

Float operator*(const Float& lhs, const Float& rhs) {
    // Handle special values
    if (lhs.isNaN() || rhs.isNaN()) [[unlikely]] return Float::nan();

    // Handle infinity
    if (lhs.isInfinity() || rhs.isInfinity()) [[unlikely]] {
        // zero × infinity = NaN
        if ((lhs.isZero() && rhs.isInfinity()) || (lhs.isInfinity() && rhs.isZero())) {
            return Float::nan();
        }

        // Determine the sign
        bool negative = lhs.isNegative() != rhs.isNegative();
        return negative ? Float::negativeInfinity() : Float::positiveInfinity();
    }

    // Handle zero (propagate the precision fields)
    if (lhs.isZero() || rhs.isZero()) {
        Float z;
        z.effective_bits_ = mergeEffective(lhs.effective_bits_, rhs.effective_bits_);
        z.requested_bits_ = mergeRequested(lhs.requested_bits_, rhs.requested_bits_);
        return z;
    }

    // Propagate the precision fields
    int eff = mergeEffective(lhs.effective_bits_, rhs.effective_bits_);
    int req = mergeRequested(lhs.requested_bits_, rhs.requested_bits_);

    // ── P1-1: small-precision fast path (≤2 limbs) ──
    // Avoid Int construction and normalize; assemble the Float directly
    {
        size_t a_sz = lhs.mantissa_.size(), b_sz = rhs.mantissa_.size();
        if (a_sz <= 2 && b_sz <= 2) {
            uint64_t rbuf[4];
            mpn::mul_basecase(rbuf, lhs.mantissa_.data(), a_sz,
                              rhs.mantissa_.data(), b_sz);
            size_t rn = a_sz + b_sz;
            if (rbuf[rn - 1] == 0) --rn;
            // Remove trailing zero words (equivalent to normalize)
            size_t lsb = 0;
            while (lsb < rn && rbuf[lsb] == 0) ++lsb;
            size_t ns = rn - lsb;

            Float result = Float::fromRawLimbs(rbuf + lsb, ns,
                lhs.exponent_ + rhs.exponent_ + static_cast<int64_t>(lsb) * 64,
                lhs.isNegative() != rhs.isNegative(), eff, req);
            // Precision adjustment (unnecessary in the exact case)
            if (eff < INT_MAX && req < INT_MAX) {
                if constexpr (PRECISION_POLICY == PrecisionPolicy::MIN_PROPAGATION) {
                    result.setPrecision(Float::bitsToPrecision(std::min(eff, req)));
                } else {
                    result.setPrecision(Float::bitsToPrecision(req));
                }
            }
            return result;
        }
    }

    // ── FLOAT-PERF-6: direct mpn multiplication fast path ──
    // Step 2: avoid copies via pointer reference
    const Int* lhs_p = &lhs.mantissa_;
    const Int* rhs_p = &rhs.mantissa_;
    Int lhs_trunc, rhs_trunc;  // used only when truncating
    int64_t lhs_exp = lhs.exponent_;
    int64_t rhs_exp = rhs.exponent_;

    if (eff < INT_MAX) {
        int target = (req < INT_MAX) ? std::min(eff, req) : eff;
        int compute_bits = target + 32;  // absorb truncation error with guard bits

        // Word-wise truncation: no bit shift, keeps up to 63 bits of excess
        int lhs_bl = static_cast<int>(lhs_p->bitLength());
        if (lhs_bl > compute_bits) {
            int words_to_drop = (lhs_bl - compute_bits) / 64;
            if (words_to_drop > 0) {
                int shift = words_to_drop * 64;
                lhs_trunc = *lhs_p;
                lhs_trunc >>= shift;
                lhs_exp += shift;
                lhs_p = &lhs_trunc;
            }
        }
        int rhs_bl = static_cast<int>(rhs_p->bitLength());
        if (rhs_bl > compute_bits) {
            int words_to_drop = (rhs_bl - compute_bits) / 64;
            if (words_to_drop > 0) {
                int shift = words_to_drop * 64;
                rhs_trunc = *rhs_p;
                rhs_trunc >>= shift;
                rhs_exp += shift;
                rhs_p = &rhs_trunc;
            }
        }
    }

    const uint64_t* a_data = lhs_p->data();
    const uint64_t* b_data = rhs_p->data();
    size_t an = lhs_p->size();
    size_t bn = rhs_p->size();
    size_t total = an + bn;

    // Required result word count = ceil(target / 64) + 2 (guard)
    size_t needed = total;
    if (eff < INT_MAX) {
        int target = (req < INT_MAX) ? std::min(eff, req) : eff;
        needed = static_cast<size_t>((target + 63) / 64) + 2;
        if (needed > total) needed = total;
    }

    Int result_mantissa;
    int64_t result_exponent;

    // Step 3: direct mpn multiplication (avoid the dispatch overhead of Int operator*)

    // Fast path for 1×1, 2×1: completely avoid the stack buffer + fromRawWords
    // Note: 2×2 is not included since the ASM basecase is faster than the intrinsic
    if (bn == 1 && an <= 2) [[likely]] {
        uint64_t rbuf[4];
        mpn::mul_basecase(rbuf, a_data, an, b_data, bn);
        size_t rn = total;
        if (rbuf[rn - 1] == 0) --rn;
        result_mantissa = Int::fromRawWordsPreNormalized(
            std::span<const uint64_t>(rbuf, rn), 1);
        result_exponent = lhs_exp + rhs_exp;
    } else if (an < mpn::KARATSUBA_THRESHOLD && bn < mpn::KARATSUBA_THRESHOLD) [[likely]] {
        uint64_t rbuf[mpn::KARATSUBA_THRESHOLD * 2];  // stack buffer
        mpn::mul_basecase(rbuf, a_data, an, b_data, bn);

        if (needed < total) {
            // Short multiplication: skip the low words
            size_t skip = total - needed;
            size_t rn = mpn::normalized_size(rbuf + skip, needed);
            result_mantissa = Int::fromRawWords(
                std::span<const uint64_t>(rbuf + skip, rn), 1);
            result_exponent = lhs_exp + rhs_exp + static_cast<int64_t>(skip) * 64;
        } else {
            size_t rn = total;
            if (rbuf[rn - 1] == 0) --rn;
            result_mantissa = Int::fromRawWords(
                std::span<const uint64_t>(rbuf, rn), 1);
            result_exponent = lhs_exp + rhs_exp;
        }
    } else if (needed < total && std::min(an, bn) < mpn::KARATSUBA_THRESHOLD) {
        result_mantissa = IntMultiplication::multiplyHigh(*lhs_p, *rhs_p, needed);
        result_exponent = lhs_exp + rhs_exp + static_cast<int64_t>(total - needed) * 64;
    } else {
        result_mantissa = (*lhs_p) * (*rhs_p);
        result_exponent = lhs_exp + rhs_exp;
    }

    // Step 4: optimize Float construction (normalize only once via the move constructor)
    bool result_negative = lhs.isNegative() != rhs.isNegative();
    Float result(std::move(result_mantissa), result_exponent, result_negative);
    result.effective_bits_ = eff;
    result.requested_bits_ = req;

    // Adjust the precision (unnecessary for exact values)
    if (eff < INT_MAX && req < INT_MAX) {
        if constexpr (PRECISION_POLICY == PrecisionPolicy::MIN_PROPAGATION) {
            result.setPrecision(Float::bitsToPrecision(std::min(eff, req)));
        } else {
            result.setPrecision(Float::bitsToPrecision(req));
        }
    }

    return result;
}

Float operator*(Float&& lhs, const Float& rhs) {
    if (&lhs == &rhs) return operator*(static_cast<const Float&>(lhs), rhs);

    // Within SBO range, delegate to the const& version
    constexpr size_t SBO_CAP = 9;
    if (lhs.mantissa_.size() <= SBO_CAP && rhs.mantissa_.size() <= SBO_CAP) {
        return operator*(static_cast<const Float&>(lhs), rhs);
    }

    // Handle special values
    if (lhs.isNaN() || rhs.isNaN()) [[unlikely]] return Float::nan();
    if (lhs.isInfinity() || rhs.isInfinity()) [[unlikely]] {
        if ((lhs.isZero() && rhs.isInfinity()) || (lhs.isInfinity() && rhs.isZero())) {
            return Float::nan();
        }
        bool negative = lhs.isNegative() != rhs.isNegative();
        return negative ? Float::negativeInfinity() : Float::positiveInfinity();
    }
    if (lhs.isZero() || rhs.isZero()) {
        Float z;
        z.effective_bits_ = mergeEffective(lhs.effective_bits_, rhs.effective_bits_);
        z.requested_bits_ = mergeRequested(lhs.requested_bits_, rhs.requested_bits_);
        return z;
    }

    int eff = mergeEffective(lhs.effective_bits_, rhs.effective_bits_);
    int req = mergeRequested(lhs.requested_bits_, rhs.requested_bits_);

    // Move lhs's mantissa and operate directly (avoid copies when truncating)
    Int lhs_mantissa = std::move(lhs.mantissa_);
    int64_t lhs_exp = lhs.exponent_;

    const Int* rhs_p = &rhs.mantissa_;
    Int rhs_trunc;
    int64_t rhs_exp = rhs.exponent_;

    if (eff < INT_MAX) {
        int target = (req < INT_MAX) ? std::min(eff, req) : eff;
        int compute_bits = target + 32;

        // lhs: in-place truncation (no copy)
        int lhs_bl = static_cast<int>(lhs_mantissa.bitLength());
        if (lhs_bl > compute_bits) {
            int words_to_drop = (lhs_bl - compute_bits) / 64;
            if (words_to_drop > 0) {
                int shift = words_to_drop * 64;
                lhs_mantissa >>= shift;
                lhs_exp += shift;
            }
        }
        // rhs: const&, so copy only when needed
        int rhs_bl = static_cast<int>(rhs_p->bitLength());
        if (rhs_bl > compute_bits) {
            int words_to_drop = (rhs_bl - compute_bits) / 64;
            if (words_to_drop > 0) {
                int shift = words_to_drop * 64;
                rhs_trunc = *rhs_p;
                rhs_trunc >>= shift;
                rhs_exp += shift;
                rhs_p = &rhs_trunc;
            }
        }
    }

    const uint64_t* a_data = lhs_mantissa.data();
    const uint64_t* b_data = rhs_p->data();
    size_t an = lhs_mantissa.size();
    size_t bn = rhs_p->size();
    size_t total = an + bn;

    size_t needed = total;
    if (eff < INT_MAX) {
        int target = (req < INT_MAX) ? std::min(eff, req) : eff;
        needed = static_cast<size_t>((target + 63) / 64) + 2;
        if (needed > total) needed = total;
    }

    Int result_mantissa;
    int64_t result_exponent;

    if (an < mpn::KARATSUBA_THRESHOLD && bn < mpn::KARATSUBA_THRESHOLD) [[likely]] {
        uint64_t rbuf[mpn::KARATSUBA_THRESHOLD * 2];
        mpn::mul_basecase(rbuf, a_data, an, b_data, bn);

        if (needed < total) {
            size_t skip = total - needed;
            size_t rn = mpn::normalized_size(rbuf + skip, needed);
            result_mantissa = Int::fromRawWords(
                std::span<const uint64_t>(rbuf + skip, rn), 1);
            result_exponent = lhs_exp + rhs_exp + static_cast<int64_t>(skip) * 64;
        } else {
            size_t rn = total;
            if (rbuf[rn - 1] == 0) --rn;
            result_mantissa = Int::fromRawWords(
                std::span<const uint64_t>(rbuf, rn), 1);
            result_exponent = lhs_exp + rhs_exp;
        }
    } else if (needed < total && std::min(an, bn) < mpn::KARATSUBA_THRESHOLD) {
        result_mantissa = IntMultiplication::multiplyHigh(lhs_mantissa, *rhs_p, needed);
        result_exponent = lhs_exp + rhs_exp + static_cast<int64_t>(total - needed) * 64;
    } else {
        result_mantissa = lhs_mantissa * (*rhs_p);
        result_exponent = lhs_exp + rhs_exp;
    }

    bool result_negative = lhs.isNegative() != rhs.isNegative();
    Float result(std::move(result_mantissa), result_exponent, result_negative);
    result.effective_bits_ = eff;
    result.requested_bits_ = req;

    if (eff < INT_MAX && req < INT_MAX) {
        if constexpr (PRECISION_POLICY == PrecisionPolicy::MIN_PROPAGATION) {
            result.setPrecision(Float::bitsToPrecision(std::min(eff, req)));
        } else {
            result.setPrecision(Float::bitsToPrecision(req));
        }
    }

    return result;
}

Float operator*(const Float& lhs, Float&& rhs) {
    // Multiplication is commutative: exploit rhs's rvalue
    return operator*(std::move(rhs), lhs);
}

Float operator*(Float&& lhs, Float&& rhs) {
    return operator*(std::move(lhs), static_cast<const Float&>(rhs));
}

Float operator/(const Float& lhs, const Float& rhs) {
    // Handle special values
    if (lhs.isNaN() || rhs.isNaN()) [[unlikely]] return Float::nan();
    if (lhs.isInfinity() && rhs.isInfinity()) [[unlikely]] return Float::nan();
    if (lhs.isInfinity() && !rhs.isZero()) [[unlikely]] return lhs.isNegative() != rhs.isNegative() ? Float::negativeInfinity() : Float::positiveInfinity();
    if (rhs.isInfinity()) [[unlikely]] return Float::zero();
    if (rhs.isZero()) [[unlikely]] {
        if (lhs.isZero()) return Float::nan();
        return lhs.isNegative() != rhs.isNegative() ? Float::negativeInfinity() : Float::positiveInfinity();
    }
    if (lhs.isZero()) [[unlikely]] {
        // 0 ÷ x = 0 (preserve the precision fields)
        Float z;
        z.effective_bits_ = lhs.effective_bits_;
        z.requested_bits_ = mergeRequested(lhs.requested_bits_, rhs.requested_bits_);
        return z;
    }

    // Propagate the precision fields
    int eff = mergeEffective(lhs.effective_bits_, rhs.effective_bits_);
    int req = mergeRequested(lhs.requested_bits_, rhs.requested_bits_);

    // Share the exact÷exact precision resolution with FloatOps::div (resolveDivPrecision).
    // Replace INT_MAX eff/req with contextBits() (reflecting PrecisionScope), and fire the poison audit too.
    auto _dp = resolveDivPrecision(eff, req);
    bool both_exact = _dp.both_exact;
    eff = _dp.eff_calc;   // reproduce old behavior: contextBits when INT_MAX, unchanged otherwise
    req = _dp.req_calc;
    int compute_prec = _dp.compute_prec;

    // ── P1-1: small-precision fast path (≤2 limbs, 1-limb divisor) ──
    // Avoid all Int copies, vector allocation, and normalize
    if (lhs.mantissa_.size() <= 2 && rhs.mantissa_.size() == 1) {
        int scaling = compute_prec + 10 + 64;
        size_t lhs_n = lhs.mantissa_.size();
        size_t word_shift = static_cast<size_t>(scaling) / 64;
        unsigned bit_shift = static_cast<unsigned>(scaling) % 64;
        constexpr size_t MAX_BUF = 12;  // supports up to ~500 bits of precision

        // Fast path only when it fits in the stack buffer
        if (lhs_n + word_shift + 2 <= MAX_BUF) {
        int64_t result_exponent = lhs.exponent_ - rhs.exponent_ - scaling;

        // Scale the dividend (left-shift in the stack buffer)
        const uint64_t* lhs_data = lhs.mantissa_.data();
        uint64_t sbuf[MAX_BUF] = {0};
        // The low word_shift words are zero (already zero-initialized)
        for (size_t i = 0; i < lhs_n; i++) sbuf[i + word_shift] = lhs_data[i];
        size_t sn = lhs_n + word_shift;
        if (bit_shift > 0) {
            uint64_t carry = mpn::lshift(sbuf + word_shift, sbuf + word_shift, lhs_n, bit_shift);
            if (carry) { sbuf[sn] = carry; sn++; }
        }

        // divmod_1 on stack
        uint64_t qbuf[MAX_BUF];
        uint64_t div_val = rhs.mantissa_.data()[0];
        uint64_t remainder_val = mpn::divmod_1(qbuf, sbuf, sn, div_val);

        // Remove high zero words
        size_t qn = sn;
        while (qn > 0 && qbuf[qn - 1] == 0) --qn;
        if (qn == 0) return Float::zero();

        // Sticky bit
        bool exact_division = (remainder_val == 0);
        if (!exact_division) qbuf[0] |= 1;

        // Remove trailing zero words
        size_t lsb = 0;
        while (lsb < qn && qbuf[lsb] == 0) ++lsb;
        size_t ns = qn - lsb;
        result_exponent += static_cast<int64_t>(lsb) * 64;

        int eff_result = (both_exact && exact_division) ? INT_MAX : (both_exact ? compute_prec : eff);
        int req_result = (both_exact && exact_division) ? INT_MAX : req;
        Float result = Float::fromRawLimbs(qbuf + lsb, ns, result_exponent,
            lhs.isNegative() != rhs.isNegative(), eff_result, req_result);
        // Precision adjustment (unnecessary for an exact-division result)
        if (!(both_exact && exact_division)) {
            if constexpr (PRECISION_POLICY == PrecisionPolicy::MIN_PROPAGATION) {
                result.setPrecision(Float::bitsToPrecision(std::min(eff_result, req_result)));
            } else {
                result.setPrecision(Float::bitsToPrecision(std::max(eff_result, req_result)));
            }
        }
        return result;
        } // buffer size check
    } // 1-limb divisor fast path

    // Adjust the mantissas for division
    Int scaled_mantissa = lhs.mantissa_;
    Int divisor = rhs.mantissa_;
    int64_t result_exponent = lhs.exponent_ - rhs.exponent_;

    // Non-exact: pre-truncation based on the effective bit count
    // Since the result has only eff bits of precision, the extra input bits are unnecessary.
    // This reduces the input size of the multiply/divide algorithm and greatly reduces computation.
    if (!both_exact) {
        // Word-wise truncation: no bit shift, keeps up to 63 bits of excess
        int div_bl = static_cast<int>(divisor.bitLength());
        if (div_bl > compute_prec) {
            int words_to_drop = (div_bl - compute_prec) / 64;
            if (words_to_drop > 0) {
                int shift = words_to_drop * 64;
                divisor >>= shift;
                result_exponent -= shift;
            }
        }

        int lhs_bl = static_cast<int>(scaled_mantissa.bitLength());
        if (lhs_bl > compute_prec) {
            int words_to_drop = (lhs_bl - compute_prec) / 64;
            if (words_to_drop > 0) {
                int shift = words_to_drop * 64;
                scaled_mantissa >>= shift;
                result_exponent += shift;
            }
        }
    }

    // If the divisor is 1 limb: fast division with divmod_1 (avoiding BZ/Newton)
    if (divisor.size() == 1) {
        int scaling = compute_prec + 10 + 64;
        scaled_mantissa <<= scaling;
        result_exponent -= scaling;

        uint64_t div_val = divisor.data()[0];
        const uint64_t* src = scaled_mantissa.data();
        size_t n = scaled_mantissa.size();
        std::vector<uint64_t> q(n);
        uint64_t remainder_val = mpn::divmod_1(q.data(), src, n, div_val);
        // normalize: remove high zero words
        while (!q.empty() && q.back() == 0) q.pop_back();
        // Sticky bit: remainder != 0 → set LSB to 1 to make the rounding decision accurate
        bool exact_division = (remainder_val == 0);
        if (!exact_division && !q.empty()) {
            q[0] |= 1;
        }
        Int quotient = Int::fromRawWords(q, 1);

        bool result_negative = lhs.isNegative() != rhs.isNegative();
        Float result(std::move(quotient), result_exponent, result_negative);

        if (both_exact && exact_division) {
            result.effective_bits_ = INT_MAX;
            result.requested_bits_ = INT_MAX;
            result.normalize();
        } else {
            // both_exact and not exactly divisible: since the inputs are exact, the result precision
            // is limited by compute_prec (eff has already been reduced to defaultPrecision)
            result.effective_bits_ = both_exact ? compute_prec : eff;
            result.requested_bits_ = req;
            result.normalize();
            if constexpr (PRECISION_POLICY == PrecisionPolicy::MIN_PROPAGATION) {
                result.setPrecision(Float::bitsToPrecision(std::min(eff, req)));
            } else {
                result.setPrecision(Float::bitsToPrecision(std::max(eff, req)));
            }
        }
        return result;
    }

    // Newton reciprocal iteration: perform large-size division in O(M(n)) (BZ is O(M(n) log n))
    // Do the Newton iteration of 1/rhs at the Float level and obtain the quotient as lhs * (1/rhs).
    // x_{k+1} = x_k * (2 - b * x_k)  (quadratic convergence: precision doubles each step)
    // Total cost: ~3M(n) (geometric series + final multiplication)
    constexpr size_t NEWTON_DIV_THRESHOLD = 64;  // words (~4096 bits ≈ 1230 digits)

    size_t div_n = divisor.size();
    if (div_n >= NEWTON_DIV_THRESHOLD) {
        // target_bits: precision needed for the quotient + guard
        int target_bits = compute_prec + 64;
        int target_prec = Float::bitsToPrecision(target_bits);

        // Limit the precision of rhs as a positive working copy
        Float b_work = rhs.isNegative() ? -rhs : rhs;
        b_work.effective_bits_ = target_bits;
        b_work.requested_bits_ = target_bits;
        b_work.setPrecision(target_prec);

        // Initial approximation: obtain a ~53-bit approximation of 1/b as a double from the high bits of the mantissa
        // toDouble() overflows for large values, so temporarily adjust the exponent
        int64_t b_bl = static_cast<int64_t>(b_work.mantissa_.bitLength());
        int64_t exp_adj = b_work.exponent_ + b_bl - 1;  // b = b_norm * 2^exp_adj, b_norm ∈ [1,2)
        int64_t save_exp = b_work.exponent_;
        b_work.exponent_ = -(b_bl - 1);  // b_work.value ∈ [1.0, 2.0)
        double b_d = b_work.toDouble();
        b_work.exponent_ = save_exp;      // restore

        Float x(1.0 / b_d);              // x ≈ 1/b_norm ∈ (0.5, 1.0]
        x.exponent_ -= exp_adj;           // adjust x to 1/b_original
        x.effective_bits_ = 64;
        x.requested_bits_ = 64;

        // Newton iteration: x_{k+1} = x_k + x_k * (1 - b * x_k)
        // YC-1c: residual form — instead of correction = 2 - bx ≈ 1 (full n limbs),
        //   use residual = 1 - bx ≈ 2^{-correct_bits} (about n/2 limbs), and
        //   shrink x * residual to M(n, n/2) (~25% saved per iteration vs M(n,n))
        int correct_bits = 48;

        while (correct_bits < target_bits) {
            // YC-1b: if the next iteration would reach target_bits, go to the fused path
            if (2 * correct_bits >= target_bits) {
                break;
            }

            int step_bits = std::min(2 * correct_bits + 32, target_bits + 64);
            int step_prec = Float::bitsToPrecision(step_bits);

            // Limit the working precision
            Float b_step = b_work;
            b_step.setPrecision(step_prec);
            b_step.effective_bits_ = step_bits;

            x.setPrecision(step_prec);
            x.effective_bits_ = step_bits;

            // bx = b * x ≈ 1 — M(n, n) full multiplication
            Float bx = b_step * x;
            bx.setPrecision(step_prec);

            // residual = 1 - bx ≈ 2^{-correct_bits} — cancellation shrinks the mantissa to ~correct_bits/64 limbs
            Float one_s(1);
            one_s.effective_bits_ = step_bits;
            one_s.requested_bits_ = step_bits;
            Float residual = one_s - bx;

            // x = x + x * residual — M(n, n/2): the NTT length is short since residual's mantissa is small
            x = x + x * residual;
            x.setPrecision(step_prec);
            // The Newton iteration doubles precision (quadratic convergence). step_bits is the working
            // precision, not the achieved precision. Achieved precision = min(2 * old_correct_bits, step_bits).
            correct_bits = std::min(2 * correct_bits, step_bits);
        }

        // YC-1b: fuse the final Newton iteration with the dividend multiplication
        // x is a correct_bits approximation of 1/b (r_n), 2*correct_bits >= target_bits
        // result = a*r_n * (2 - b*r_n) = a*r_n + a*r_n*(1 - b*r_n)
        // All 3 multiplications are M(P, correct_bits), avoiding a final M(P,P)
        Float a_work = lhs.isNegative() ? -lhs : lhs;
        a_work.effective_bits_ = target_bits;
        a_work.requested_bits_ = target_bits;
        a_work.setPrecision(target_prec);

        int half_bits = correct_bits + 32;
        int half_prec = Float::bitsToPrecision(half_bits);
        x.setPrecision(half_prec);
        x.effective_bits_ = half_bits;

        // YC-12: run q0 = a*x and bx = b*x in parallel (mutually independent)
        Float b_full = b_work;
        b_full.setPrecision(target_prec);
        b_full.effective_bits_ = target_bits;

        Float q0, bx;
        std::thread div_thread([&]() {
            q0 = a_work * x;
            q0.setPrecision(target_prec);
            q0.effective_bits_ = target_bits;
        });
        bx = b_full * x;
        bx.setPrecision(target_prec);
        div_thread.join();

        // residual = 1 - bx ≈ 2^{-correct_bits}
        Float one(1);
        one.effective_bits_ = target_bits;
        one.requested_bits_ = target_bits;
        Float residual = one - bx;
        residual.setPrecision(half_prec);
        residual.effective_bits_ = half_bits;

        // result = q0 + q0 * residual — the correction is M(P, P/2)
        Float result = q0 + q0 * residual;

        if (lhs.isNegative() != rhs.isNegative()) {
            result = -result;
        }

        // Sticky bit: always inexact since it is a Newton approximation
        if (!result.mantissa_.isZero()) {
            result.mantissa_ |= Int(1);
        }

        result.effective_bits_ = both_exact ? compute_prec : eff;
        result.requested_bits_ = req;
        if constexpr (PRECISION_POLICY == PrecisionPolicy::MIN_PROPAGATION) {
            result.setPrecision(Float::bitsToPrecision(std::min(eff, req)));
        } else {
            result.setPrecision(Float::bitsToPrecision(std::max(eff, req)));
        }
        return result;
    }

    // --- Generic path: BZ division (small to medium size) ---

    // Scaling: shift so the quotient yields compute_prec bits
    // quotient_bits = (lhs_bl + scaling) - divisor_bl ≈ compute_prec + guard
    // Old scheme: scaling = compute_prec + 10 + divisor.size()*64 → 2x excessive padding
    int div_bl = static_cast<int>(divisor.bitLength());
    int lhs_bl = static_cast<int>(scaled_mantissa.bitLength());
    int scaling = compute_prec + 74 + std::max(0, div_bl - lhs_bl);
    scaled_mantissa <<= scaling;
    result_exponent -= scaling;

    // Perform the division
    Int remainder;
    Int quotient = IntOps::divmod(scaled_mantissa, divisor, remainder);

    // If exactly divisible: the result is exact
    bool exact_division = remainder.isZero();

    // Sticky bit: if remainder != 0, the true quotient is larger than quotient.
    // Set the LSB to 1 to make round()'s midpoint decision accurate.
    if (!exact_division) {
        quotient |= Int(1);
    }

    // Check for the anomaly of the quotient being 0 (this case is a bug)
    if (quotient.isZero() && !scaled_mantissa.isZero()) {
        quotient = Int(1);
    }

    // Determine the sign
    bool result_negative = lhs.isNegative() != rhs.isNegative();

    // Create the result and normalize
    Float result(quotient, result_exponent, result_negative);

    if (both_exact && exact_division) {
        // exact / exact divided evenly → the result is also exact
        result.effective_bits_ = INT_MAX;
        result.requested_bits_ = INT_MAX;
        result.normalize();
    } else {
        // both_exact and not exactly divisible: since the inputs are exact, the result precision
        // is limited by compute_prec (eff has already been reduced to defaultPrecision)
        result.effective_bits_ = both_exact ? compute_prec : eff;
        result.requested_bits_ = req;
        result.normalize();
        if constexpr (PRECISION_POLICY == PrecisionPolicy::MIN_PROPAGATION) {
            result.setPrecision(Float::bitsToPrecision(std::min(eff, req)));
        } else {
            result.setPrecision(Float::bitsToPrecision(std::max(eff, req)));
        }
    }

    return result;
}

Float operator/(Float&& lhs, const Float& rhs) {
    if (&lhs == &rhs) return operator/(static_cast<const Float&>(lhs), rhs);

    // Within SBO range or large sizes targeted by Newton, delegate to the const& version
    constexpr size_t SBO_CAP = 9;
    constexpr size_t NEWTON_DIV_THRESHOLD_RVAL = 64;
    if (lhs.mantissa_.size() <= SBO_CAP && rhs.mantissa_.size() <= SBO_CAP) {
        return operator/(static_cast<const Float&>(lhs), rhs);
    }
    if (rhs.mantissa_.size() >= NEWTON_DIV_THRESHOLD_RVAL) {
        return operator/(static_cast<const Float&>(lhs), rhs);
    }

    // Handle special values
    if (lhs.isNaN() || rhs.isNaN()) [[unlikely]] return Float::nan();
    if (lhs.isInfinity() && rhs.isInfinity()) [[unlikely]] return Float::nan();
    if (lhs.isInfinity() && !rhs.isZero()) [[unlikely]] return lhs.isNegative() != rhs.isNegative() ? Float::negativeInfinity() : Float::positiveInfinity();
    if (rhs.isInfinity()) [[unlikely]] return Float::zero();
    if (rhs.isZero()) [[unlikely]] {
        if (lhs.isZero()) return Float::nan();
        return lhs.isNegative() != rhs.isNegative() ? Float::negativeInfinity() : Float::positiveInfinity();
    }
    if (lhs.isZero()) [[unlikely]] {
        Float z;
        z.effective_bits_ = lhs.effective_bits_;
        z.requested_bits_ = mergeRequested(lhs.requested_bits_, rhs.requested_bits_);
        return z;
    }

    int eff = mergeEffective(lhs.effective_bits_, rhs.effective_bits_);
    int req = mergeRequested(lhs.requested_bits_, rhs.requested_bits_);

    bool both_exact = (eff >= INT_MAX);
    if (eff >= INT_MAX) {
        eff = Float::precisionToBits(Float::defaultPrecision());
    }
    if (req >= INT_MAX) {
        req = Float::precisionToBits(Float::defaultPrecision());
    }

    int compute_prec = both_exact ? std::max(eff, req) : (eff + 32);

    // Move lhs's mantissa (avoid a copy)
    Int scaled_mantissa = std::move(lhs.mantissa_);
    Int divisor = rhs.mantissa_;
    int64_t result_exponent = lhs.exponent_ - rhs.exponent_;

    if (!both_exact) {
        int div_bl = static_cast<int>(divisor.bitLength());
        if (div_bl > compute_prec) {
            int words_to_drop = (div_bl - compute_prec) / 64;
            if (words_to_drop > 0) {
                int shift = words_to_drop * 64;
                divisor >>= shift;
                result_exponent -= shift;
            }
        }

        int lhs_bl = static_cast<int>(scaled_mantissa.bitLength());
        if (lhs_bl > compute_prec) {
            int words_to_drop = (lhs_bl - compute_prec) / 64;
            if (words_to_drop > 0) {
                int shift = words_to_drop * 64;
                scaled_mantissa >>= shift;
                result_exponent += shift;
            }
        }
    }

    // Scaling: shift so the quotient yields compute_prec bits
    int div_bl = static_cast<int>(divisor.bitLength());
    int lhs_bl_rval = static_cast<int>(scaled_mantissa.bitLength());
    int scaling = compute_prec + 74 + std::max(0, div_bl - lhs_bl_rval);
    scaled_mantissa <<= scaling;
    result_exponent -= scaling;

    Int remainder;
    Int quotient = IntOps::divmod(scaled_mantissa, divisor, remainder);

    bool exact_division = remainder.isZero();
    if (!exact_division) {
        quotient |= Int(1);
    }

    if (quotient.isZero() && !scaled_mantissa.isZero()) {
        quotient = Int(1);
    }

    bool result_negative = lhs.isNegative() != rhs.isNegative();
    Float result(quotient, result_exponent, result_negative);

    if (both_exact && exact_division) {
        result.effective_bits_ = INT_MAX;
        result.requested_bits_ = INT_MAX;
        result.normalize();
    } else {
        result.effective_bits_ = eff;
        result.requested_bits_ = req;
        result.normalize();
        if constexpr (PRECISION_POLICY == PrecisionPolicy::MIN_PROPAGATION) {
            result.setPrecision(Float::bitsToPrecision(std::min(eff, req)));
        } else {
            result.setPrecision(Float::bitsToPrecision(std::max(eff, req)));
        }
    }

    return result;
}

// Single-word multiplication: O(n) single-limb multiplication
Float mulScalarF(const Float& lhs, uint64_t rhs) {
    if (lhs.isNaN()) return Float::nan();
    if (lhs.isZero() || rhs == 0) return Float();
    if (rhs == 1) return lhs;
    if (lhs.isInfinity()) return lhs;

    // Power of 2 → ldexp (O(1) exponent operation, avoiding mpn::mul_1)
    if ((rhs & (rhs - 1)) == 0) {
        unsigned long shift;
        _BitScanForward64(&shift, rhs);
        return ldexp(lhs, static_cast<int>(shift));
    }

    const uint64_t* src = lhs.mantissa_.data();
    size_t n = lhs.mantissa_.size();

    // Stack buffer: avoid heap allocation for small sizes (≤2048 bit)
    constexpr size_t STACK_LIMIT = 33;  // 32 words + 1 carry
    uint64_t stack_buf[STACK_LIMIT];
    std::vector<uint64_t> heap_buf;
    uint64_t* p;
    if (n + 1 <= STACK_LIMIT) {
        p = stack_buf;
    } else {
        heap_buf.resize(n + 1);
        p = heap_buf.data();
    }

    std::memcpy(p, src, n * sizeof(uint64_t));
    uint64_t carry = mpn::mul_1(p, p, n, rhs);
    if (carry) { p[n] = carry; n++; }

    Int product = Int::fromRawWordsPreNormalized(std::span<const uint64_t>(p, n), 1);
    Float result(std::move(product), lhs.exponent_, lhs.isNegative());
    result.effective_bits_ = lhs.effective_bits_;
    result.requested_bits_ = lhs.requested_bits_;
    result.normalize();
    return result;
}

Float mulScalarF(Float&& lhs, uint64_t rhs) {
    // Special-value check (delegate NaN/Inf/Zero to the const& version)
    if (lhs.isNaN() || lhs.isInfinity()) return mulScalarF(static_cast<const Float&>(lhs), rhs);
    if (lhs.isZero() || rhs == 0) return Float();
    if (rhs == 1) return std::move(lhs);

    // Power of 2 → ldexp (O(1) exponent operation)
    if ((rhs & (rhs - 1)) == 0) {
        unsigned long shift;
        _BitScanForward64(&shift, rhs);
        return ldexp(std::move(lhs), static_cast<int>(shift));
    }

    // Extract the mantissa, process it, and put the result back into a new Float
    // (Int::m_words is private, so go through the public API)
    Int mantissa = std::move(lhs.mantissa_);
    const uint64_t* src = mantissa.data();
    size_t n = mantissa.size();

    constexpr size_t STACK_LIMIT = 33;
    uint64_t stack_buf[STACK_LIMIT];
    std::vector<uint64_t> heap_buf;
    uint64_t* p;
    if (n + 1 <= STACK_LIMIT) {
        p = stack_buf;
    } else {
        heap_buf.resize(n + 1);
        p = heap_buf.data();
    }

    std::memcpy(p, src, n * sizeof(uint64_t));
    uint64_t carry = mpn::mul_1(p, p, n, rhs);
    if (carry) { p[n] = carry; n++; }

    Int product = Int::fromRawWordsPreNormalized(std::span<const uint64_t>(p, n), 1);
    Float result(std::move(product), lhs.exponent_, lhs.isNegative());
    result.effective_bits_ = lhs.effective_bits_;
    result.requested_bits_ = lhs.requested_bits_;
    result.normalize();
    return result;
}

Float mulScalarF(const Float& lhs, int64_t rhs) {
    if (rhs >= 0) return mulScalarF(lhs, static_cast<uint64_t>(rhs));
    Float result = mulScalarF(lhs, static_cast<uint64_t>(-(rhs + 1)) + 1u);
    if (!result.isZero() && !result.isNaN()) result.is_negative_ = !result.is_negative_;
    return result;
}

Float mulScalarF(Float&& lhs, int64_t rhs) {
    if (rhs >= 0) return mulScalarF(std::move(lhs), static_cast<uint64_t>(rhs));
    Float result = mulScalarF(std::move(lhs), static_cast<uint64_t>(-(rhs + 1)) + 1u);
    if (!result.isZero() && !result.isNaN()) result.is_negative_ = !result.is_negative_;
    return result;
}

// Single-word division: O(n) single-limb division
// Used in the Taylor-series recurrence term = term * x / n when n is a small integer
Float divScalarF(const Float& lhs, uint64_t rhs) {
    if (lhs.isNaN()) return Float::nan();
    if (rhs == 0) {
        if (lhs.isZero()) return Float::nan();
        return lhs.isNegative() ? Float::negativeInfinity() : Float::positiveInfinity();
    }
    if (lhs.isInfinity()) return lhs;
    if (lhs.isZero()) {
        Float z;
        z.effective_bits_ = lhs.effective_bits_;
        z.requested_bits_ = lhs.requested_bits_;
        return z;
    }
    if (rhs == 1) return lhs;

    // Power of 2 → ldexp (O(1) exponent operation, avoiding mpn::divmod_1)
    if ((rhs & (rhs - 1)) == 0) {
        unsigned long shift;
        _BitScanForward64(&shift, rhs);
        return ldexp(lhs, -static_cast<int>(shift));
    }

    // General path → delegate to the verified Float/Float division.
    // BUGFIX (2026-05-30): the old implementation did divmod_1 with the mantissa at its input width and
    // broke down for dividends with a small mantissa (exact small integers/dyadic etc.) by *failing to extend the precision*
    // (e.g. Float(7)/12 → 1, Float(1)/12 → 1). Constants such as zeta7/glaisher/bernstein and
    // recurrences using Float/int produced wrong values. Float/Float scales the dividend up to compute_prec,
    // so it is correct, and it also has a single-word-divisor fast path, so it keeps the speed.
    return lhs / Float(rhs);
}

Float divScalarF(Float&& lhs, uint64_t rhs) {
    // Special-value check (delegate NaN/Inf/Zero/division-by-zero to the const& version)
    if (lhs.isNaN() || lhs.isInfinity() || lhs.isZero() || rhs == 0)
        return divScalarF(static_cast<const Float&>(lhs), rhs);
    if (rhs == 1) return std::move(lhs);

    // Power of 2 → ldexp (O(1) exponent operation)
    if ((rhs & (rhs - 1)) == 0) {
        unsigned long shift;
        _BitScanForward64(&shift, rhs);
        return ldexp(std::move(lhs), -static_cast<int>(shift));
    }

    // General path → delegate to Float/Float (same reason as the const& version. Avoids the divmod_1
    // truncation bug where precision is not extended for a dividend with a small mantissa).
    return std::move(lhs) / Float(rhs);
}

Float divScalarF(const Float& lhs, int64_t rhs) {
    if (rhs == 0) {
        if (lhs.isZero()) return Float::nan();
        return lhs.isNegative() ? Float::negativeInfinity() : Float::positiveInfinity();
    }
    uint64_t abs_rhs = static_cast<uint64_t>(rhs < 0 ? -(rhs + 1) + 1 : rhs);
    Float result = divScalarF(lhs, abs_rhs);
    if (rhs < 0 && !result.isZero() && !result.isNaN() && !result.isInfinity())
        result.is_negative_ = !result.is_negative_;
    return result;
}

Float divScalarF(Float&& lhs, int64_t rhs) {
    if (rhs == 0) {
        if (lhs.isZero()) return Float::nan();
        return lhs.isNegative() ? Float::negativeInfinity() : Float::positiveInfinity();
    }
    uint64_t abs_rhs = static_cast<uint64_t>(rhs < 0 ? -(rhs + 1) + 1 : rhs);
    Float result = divScalarF(std::move(lhs), abs_rhs);
    if (rhs < 0 && !result.isZero() && !result.isNaN() && !result.isInfinity())
        result.is_negative_ = !result.is_negative_;
    return result;
}

std::partial_ordering operator<=>(const Float& lhs, const Float& rhs) {
    // NaN is incomparable
    if (lhs.isNaN() || rhs.isNaN()) return std::partial_ordering::unordered;

    // Both zero (sign ignored)
    if (lhs.isZero() && rhs.isZero()) return std::partial_ordering::equivalent;

    // Comparison by sign
    if (lhs.isNegative() && !rhs.isNegative()) return std::partial_ordering::less;
    if (!lhs.isNegative() && rhs.isNegative()) return std::partial_ordering::greater;

    // Same sign
    bool is_negative = lhs.isNegative();

    // Handle infinity
    if (lhs.isInfinity()) {
        if (rhs.isInfinity()) return std::partial_ordering::equivalent;
        return is_negative ? std::partial_ordering::less : std::partial_ordering::greater;
    }
    if (rhs.isInfinity()) {
        return is_negative ? std::partial_ordering::greater : std::partial_ordering::less;
    }

    // Normal comparison (reverse the direction according to the sign)
    int cmp = lhs.compareUnsigned(rhs);
    if (cmp == 0) return std::partial_ordering::equivalent;
    if (is_negative) cmp = -cmp;
    return (cmp < 0) ? std::partial_ordering::less : std::partial_ordering::greater;
}

bool operator==(const Float& lhs, const Float& rhs) {
    if (lhs.isNaN() || rhs.isNaN()) return false;
    return (lhs <=> rhs) == std::partial_ordering::equivalent;
}

//======================================================================
// Stream input/output
//======================================================================

std::ostream& operator<<(std::ostream& os, const Float& value) {
    return os << value.toString();
}

std::istream& operator>>(std::istream& is, Float& value) {
    std::string str;
    is >> str;
    if (!is.fail()) {
        value = Float(str);
    }
    return is;
}

//======================================================================
// Math functions
//======================================================================

Float abs(const Float& value) {
    return numeric_traits<Float>::abs(value);
}

Float abs(Float&& value) {
    if (value.isNaN()) return Float::nan();
    if (value.isInfinity()) return Float::positiveInfinity();
    value.is_negative_ = false;
    return std::move(value);
}

// sqrt(const Float&) is provided by the inline overload in Float.hpp
// → delegates to sqrt(x, Float::defaultPrecision())

//======================================================================
// isInteger — determine whether the value is an integer
//======================================================================

bool Float::isInteger() const {
    if (is_nan_ || is_infinity_) return false;
    if (mantissa_.isZero()) return true;   // 0 is an integer
    if (exponent_ >= 0) return true;       // mantissa * 2^(positive exponent) is always an integer

    // exponent_ < 0: an integer if the mantissa has at least |exponent_| trailing zero bits
    int64_t trailing = static_cast<int64_t>(mantissa_.countTrailingZeros());
    return trailing >= -exponent_;
}

//======================================================================
// fitsInt / fitsInt64 / fitsDouble — fit-to-type checks
//======================================================================

bool Float::fitsInt() const {
    if (is_nan_ || is_infinity_) return false;
    if (mantissa_.isZero()) return true;

    // Compute the integer part and range-check
    try {
        Int intVal = toInt();
        if (intVal > Int(INT32_MAX) || intVal < Int(INT32_MIN)) return false;
        return true;
    } catch (...) {
        return false;
    }
}

bool Float::fitsInt64() const {
    if (is_nan_ || is_infinity_) return false;
    if (mantissa_.isZero()) return true;

    // Compute the integer part and range-check
    try {
        Int intVal = toInt();
        if (intVal > Int(INT64_MAX) || intVal < Int(INT64_MIN)) return false;
        return true;
    } catch (...) {
        return false;
    }
}

bool Float::fitsDouble() const {
    if (is_nan_ || is_infinity_) return true;  // double also has NaN/∞
    if (mantissa_.isZero()) return true;

    // Check the magnitude: whether |value| <= DBL_MAX
    // value = mantissa * 2^exponent
    // bit length of |value| ≈ mantissa.bitLength() + exponent
    int64_t value_bits = static_cast<int64_t>(mantissa_.bitLength()) + exponent_;
    // double's maximum exponent is 1023 (below 2^1024)
    if (value_bits > 1024) return false;
    // Very small values: the smallest subnormal is 2^(-1074)
    if (value_bits < -1074) return false;
    return true;
}

//======================================================================
// swap — ADL-compatible swap
//======================================================================

void swap(Float& a, Float& b) noexcept {
    using std::swap;
    swap(a.mantissa_, b.mantissa_);
    swap(a.exponent_, b.exponent_);
    swap(a.is_negative_, b.is_negative_);
    swap(a.is_infinity_, b.is_infinity_);
    swap(a.is_nan_, b.is_nan_);
    swap(a.effective_bits_, b.effective_bits_);
    swap(a.requested_bits_, b.requested_bits_);
}

//======================================================================
// fmin / fmax / fdim — IEEE 754 compliant
//======================================================================

Float fmin(const Float& a, const Float& b) {
    // Skip NaN (return the other one). If both are NaN, return NaN
    if (a.isNaN()) return b;
    if (b.isNaN()) return a;
    return (a <= b) ? a : b;
}

Float fmin(Float&& a, Float&& b) {
    if (a.isNaN()) return std::move(b);
    if (b.isNaN()) return std::move(a);
    return (a <= b) ? std::move(a) : std::move(b);
}

Float fmax(const Float& a, const Float& b) {
    if (a.isNaN()) return b;
    if (b.isNaN()) return a;
    return (a >= b) ? a : b;
}

Float fmax(Float&& a, Float&& b) {
    if (a.isNaN()) return std::move(b);
    if (b.isNaN()) return std::move(a);
    return (a >= b) ? std::move(a) : std::move(b);
}

Float fdim(const Float& a, const Float& b) {
    if (a.isNaN() || b.isNaN()) return Float::nan();
    if (a > b) return a - b;
    return Float(0);
}

Float fdim(Float&& a, Float&& b) {
    if (a.isNaN() || b.isNaN()) return Float::nan();
    if (a > b) return std::move(a) - std::move(b);
    return Float(0);
}

//======================================================================
// copySign / signBit
//======================================================================

Float copySign(const Float& x, const Float& y) {
    if (x.isNaN()) {
        Float result = Float::nan();
        result.is_negative_ = y.is_negative_;
        return result;
    }
    Float result = x;
    result.is_negative_ = y.is_negative_;
    return result;
}

Float copySign(Float&& x, const Float& y) {
    if (x.isNaN()) {
        Float result = Float::nan();
        result.is_negative_ = y.is_negative_;
        return result;
    }
    x.is_negative_ = y.is_negative_;
    return std::move(x);
}

bool signBit(const Float& x) {
    return x.isNegative();
}

//======================================================================
// ldexp / frexp — IEEE 754 scaling functions
//======================================================================

Float ldexp(const Float& value, int exp) {
    if (value.isNaN()) return Float::nan();
    if (value.isInfinity()) return value;
    if (value.isZero()) return value;
    Float result = value;
    result.exponent_ += exp;
    result.checkExponentBounds();
    return result;
}

Float ldexp(Float&& value, int exp) {
    if (value.isNaN()) return Float::nan();
    if (value.isInfinity()) return std::move(value);
    if (value.isZero()) return std::move(value);
    value.exponent_ += exp;
    value.checkExponentBounds();
    return std::move(value);
}

Float frexp(const Float& value, int* exp) {
    if (value.isNaN()) { *exp = 0; return Float::nan(); }
    if (value.isInfinity()) { *exp = 0; return value; }
    if (value.isZero()) { *exp = 0; return value; }

    // value = mantissa * 2^exponent, mantissa is a positive integer
    // frexp: value = m * 2^e, |m| in [0.5, 1.0)
    // If the mantissa's bit length is b: mantissa = m_int, m = m_int / 2^b
    // value = (m_int / 2^b) * 2^(exponent + b)
    // → m = mantissa * 2^(-b), e = exponent + b
    int64_t b = static_cast<int64_t>(value.mantissa_.bitLength());
    int64_t total_exp = value.exponent_ + b;
    if (total_exp > static_cast<int64_t>(std::numeric_limits<int>::max())) {
        *exp = std::numeric_limits<int>::max();
    } else if (total_exp < static_cast<int64_t>(std::numeric_limits<int>::min())) {
        *exp = std::numeric_limits<int>::min();
    } else {
        *exp = static_cast<int>(total_exp);
    }

    // Result: mantissa * 2^(-b) — same sign as the original value
    // Copy the precision fields directly from the original value (since setPrecision does not change eff)
    Float result(value.mantissa_, -b, value.is_negative_);
    result.effective_bits_ = value.effective_bits_;
    result.requested_bits_ = value.requested_bits_;
    return result;
}

//======================================================================
// modf — decomposition into integer and fractional parts
//======================================================================

Float modf(const Float& value, Float* iptr) {
    if (value.isNaN()) {
        *iptr = Float::nan();
        return Float::nan();
    }
    if (value.isInfinity()) {
        *iptr = value;
        Float zero(0);
        if (value.isNegative()) zero = -zero;
        return zero;
    }
    if (value.isZero()) {
        Float zero(0);
        if (value.isNegative()) zero = -zero;
        *iptr = zero;
        return zero;
    }

    // Integer part = trunc(value)
    Float integer_part = trunc(value);
    *iptr = integer_part;

    // Fractional part = value - trunc(value), same sign as the original value
    if (value.isInteger()) {
        // The fractional part returns signed zero (IEEE 754 compliant)
        Float zero(0);
        if (value.isNegative()) return -zero;
        return zero;
    }

    return value - integer_part;
}

//======================================================================
// floor / ceil / trunc / round / frac — rounding/integerization functions
//======================================================================

Float trunc(const Float& x) {
    if (x.isNaN()) return Float::nan();
    if (x.isInfinity()) return x;
    if (x.isZero()) return Float(0);
    if (x.isInteger()) return x;
    return Float(x.toInt());
}

Float trunc(Float&& x) {
    if (x.isNaN()) return Float::nan();
    if (x.isInfinity()) return std::move(x);
    if (x.isZero()) return Float(0);
    if (x.isInteger()) return std::move(x);
    return Float(x.toInt());
}

Float floor(const Float& x) {
    if (x.isNaN()) return Float::nan();
    if (x.isInfinity()) return x;
    if (x.isZero()) return Float(0);
    if (x.isInteger()) return x;

    Float t = trunc(x);
    // floor: a negative non-integer is 1 less than trunc
    if (x.isNegative()) {
        return t - Float(1);
    }
    return t;
}

Float floor(Float&& x) {
    if (x.isNaN()) return Float::nan();
    if (x.isInfinity()) return std::move(x);
    if (x.isZero()) return Float(0);
    if (x.isInteger()) return std::move(x);

    bool was_negative = x.isNegative();
    Float t = trunc(std::move(x));
    if (was_negative) {
        return t - Float(1);
    }
    return t;
}

Float ceil(const Float& x) {
    if (x.isNaN()) return Float::nan();
    if (x.isInfinity()) return x;
    if (x.isZero()) return Float(0);
    if (x.isInteger()) return x;

    Float t = trunc(x);
    // ceil: a positive non-integer is 1 greater than trunc
    if (!x.isNegative()) {
        return t + Float(1);
    }
    return t;
}

Float ceil(Float&& x) {
    if (x.isNaN()) return Float::nan();
    if (x.isInfinity()) return std::move(x);
    if (x.isZero()) return Float(0);
    if (x.isInteger()) return std::move(x);

    bool was_negative = x.isNegative();
    Float t = trunc(std::move(x));
    if (!was_negative) {
        return t + Float(1);
    }
    return t;
}

Float round(const Float& x) {
    if (x.isNaN()) return Float::nan();
    if (x.isInfinity()) return x;
    if (x.isZero()) return Float(0);
    if (x.isInteger()) return x;

    // half-away-from-zero: take floor(|x| + 0.5) and attach the original sign
    Float half(Int(1), -1, false);  // 0.5 = 1 * 2^(-1)
    if (x.isNegative()) {
        return -floor(-x + half);
    }
    return floor(x + half);
}

Float round(Float&& x) {
    if (x.isNaN()) return Float::nan();
    if (x.isInfinity()) return std::move(x);
    if (x.isZero()) return Float(0);
    if (x.isInteger()) return std::move(x);

    Float half(Int(1), -1, false);  // 0.5 = 1 * 2^(-1)
    if (x.isNegative()) {
        return -floor(-std::move(x) + half);
    }
    return floor(std::move(x) + half);
}

// roundEven: round to nearest even (banker's rounding / IEEE 754 roundTiesToEven)
// On a tie (fractional part exactly 0.5), round toward the even side
static Float roundEvenImpl(const Float& x) {
    // t = trunc(x), frac_abs = |x - t| (= |fractional part|)
    Float t = trunc(x);
    Float f = x - t;                     // signed fractional part
    Float fa = abs(f);

    Float half(Int(1), -1, false);       // 0.5

    auto cmp = fa <=> half;
    if (cmp < 0) {
        // |frac| < 0.5 → toward trunc
        return t;
    }
    if (cmp > 0) {
        // |frac| > 0.5 → toward the far side
        if (x.isNegative()) return t - Float(1);
        return t + Float(1);
    }
    // |frac| == 0.5 (tie) → toward the even side
    Int ti = t.toInt();
    if (ti.isEven()) return t;           // already even
    if (x.isNegative()) return t - Float(1);
    return t + Float(1);
}

Float roundEven(const Float& x) {
    if (x.isNaN()) return Float::nan();
    if (x.isInfinity()) return x;
    if (x.isZero()) return Float(0);
    if (x.isInteger()) return x;
    return roundEvenImpl(x);
}

Float roundEven(Float&& x) {
    if (x.isNaN()) return Float::nan();
    if (x.isInfinity()) return std::move(x);
    if (x.isZero()) return Float(0);
    if (x.isInteger()) return std::move(x);
    return roundEvenImpl(x);
}

Float frac(const Float& x) {
    if (x.isNaN()) return Float::nan();
    if (x.isInfinity()) return Float::nan();  // frac(∞) is undefined → NaN
    if (x.isZero()) return Float(0);
    if (x.isInteger()) return Float(0);

    // frac(x) = x - floor(x) — always in the range [0, 1)
    return x - floor(x);
}

Float frac(Float&& x) {
    if (x.isNaN()) return Float::nan();
    if (x.isInfinity()) return Float::nan();
    if (x.isZero()) return Float(0);
    if (x.isInteger()) return Float(0);

    // frac(x) = x - floor(x)
    Float fl = floor(x);
    return std::move(x) - fl;
}

//======================================================================
// lerp / midpoint — C++20 compatible
//======================================================================

Float lerp(const Float& a, const Float& b, const Float& t, int precision) {
    // lerp(a, b, t) = a + t * (b - a)
    if (a.isNaN() || b.isNaN() || t.isNaN()) return Float::nan();
    return a + t * (b - a);
}

Float midpoint(const Float& a, const Float& b) {
    if (a.isNaN() || b.isNaN()) return Float::nan();
    if (a.isInfinity() || b.isInfinity()) return Float::nan();
    return ldexp(a + b, -1);  // (a + b) / 2
}

//======================================================================
// modf — integer-part + fractional-part separation
//======================================================================

Float modf(const Float& x, Float& iptr) {
    if (x.isNaN()) { iptr = Float::nan(); return Float::nan(); }
    if (x.isInfinity()) { iptr = x; return Float(0); }
    if (x.isZero()) { iptr = Float(0); return Float(0); }
    iptr = trunc(x);
    return x - iptr;
}

//======================================================================
// ilogb / logb — obtain the exponent
//======================================================================

int64_t ilogb(const Float& x) {
    if (x.isNaN() || x.isZero() || x.isInfinity()) return INT64_MIN;
    // MSB position = exponent + bitLength - 1 (0-indexed)
    return x.exponent() + static_cast<int64_t>(x.mantissa().bitLength()) - 1;
}

Float logb(const Float& x) {
    int64_t e = ilogb(x);
    if (e == INT64_MIN) return Float::nan();
    return Float(e);
}

//======================================================================
// scalbn — x * 2^n (alias for ldexp)
//======================================================================

Float scalbn(const Float& x, int n) {
    return ldexp(x, n);
}

Float scalbn(Float&& x, int n) {
    return ldexp(std::move(x), n);
}

//======================================================================
// nearbyint / rint — aliases for round
//======================================================================

Float nearbyint(const Float& x) { return round(x); }
Float nearbyint(Float&& x) { return round(std::move(x)); }
Float rint(const Float& x) { return round(x); }
Float rint(Float&& x) { return round(std::move(x)); }

//======================================================================
// FloatOps: 3-argument operations (equivalent to GMP mpf_add/sub/mul/div)
// Reuse result's buffer and avoid Float construction, normalize, and move
//======================================================================

// ── FloatOps helper macros ──
// Set all of result's fields directly (avoid normalize)
#define FLOATOPS_SET_FIELDS(result, neg, exp, e, r) \
    do { \
        (result).is_negative_ = (neg); \
        (result).is_infinity_ = false; \
        (result).is_nan_ = false; \
        (result).exponent_ = (exp); \
        (result).effective_bits_ = (e); \
        (result).requested_bits_ = (r); \
    } while(0)

// Remove mantissa LSB/MSB zero words + adjust the exponent
#define FLOATOPS_TRIM(result) \
    do { \
        auto& w__ = (result).mantissa_.m_words; \
        size_t lsb__ = 0; \
        while (lsb__ < w__.size() && w__[lsb__] == 0) ++lsb__; \
        if (lsb__ > 0) { \
            size_t ns__ = w__.size() - lsb__; \
            std::memmove(w__.data(), w__.data() + lsb__, ns__ * sizeof(uint64_t)); \
            w__.resize_uninitialized(ns__); \
            (result).exponent_ += static_cast<int64_t>(lsb__) * 64; \
        } \
        while (w__.size() > 0 && w__[w__.size() - 1] == 0) \
            w__.resize_uninitialized(w__.size() - 1); \
        (result).mantissa_.m_sign = w__.empty() ? 0 : 1; \
        (result).mantissa_.m_state = NumericState::Normal; \
    } while(0)

// Precision adjustment (setPrecision + round)
#define FLOATOPS_APPLY_PRECISION(result, e, r) \
    do { \
        if ((e) < INT_MAX && (r) < INT_MAX) { \
            int t__; \
            if constexpr (PRECISION_POLICY == PrecisionPolicy::MIN_PROPAGATION) { \
                t__ = std::min((e), (r)); \
            } else { \
                t__ = (r); \
            } \
            (result).setPrecision(Float::bitsToPrecision(t__)); \
        } \
    } while(0)

void FloatOps::mul(const Float& lhs, const Float& rhs, Float& result) {
    // Special values: delegate to the operator
    if (lhs.isNaN() || rhs.isNaN() || lhs.isInfinity() || rhs.isInfinity() ||
        lhs.isZero() || rhs.isZero()) [[unlikely]] {
        result = lhs * rhs;
        return;
    }

    int eff = mergeEffective(lhs.effective_bits_, rhs.effective_bits_);
    int req = mergeRequested(lhs.requested_bits_, rhs.requested_bits_);

    // ── P1-1: small-precision fast path (≤2 limbs) ──
    {
        size_t a_sz = lhs.mantissa_.size(), b_sz = rhs.mantissa_.size();
        if (a_sz <= 2 && b_sz <= 2) {
            uint64_t rbuf[4];
            mpn::mul_basecase(rbuf, lhs.mantissa_.data(), a_sz,
                              rhs.mantissa_.data(), b_sz);
            size_t rn = a_sz + b_sz;
            if (rbuf[rn - 1] == 0) --rn;
            size_t lsb = 0;
            while (lsb < rn && rbuf[lsb] == 0) ++lsb;
            size_t ns = rn - lsb;

            result.mantissa_.m_words.resize_uninitialized(ns);
            std::memcpy(result.mantissa_.m_words.data(), rbuf + lsb, ns * sizeof(uint64_t));
            result.mantissa_.m_sign = 1;
            result.mantissa_.m_state = NumericState::Normal;
            FLOATOPS_SET_FIELDS(result, lhs.is_negative_ != rhs.is_negative_,
                                lhs.exponent_ + rhs.exponent_ + static_cast<int64_t>(lsb) * 64,
                                eff, req);
            result.checkExponentBounds();
            FLOATOPS_APPLY_PRECISION(result, eff, req);
            return;
        }
    }

    // Truncate the operands (same logic as operator*)
    const Int* lhs_p = &lhs.mantissa_;
    const Int* rhs_p = &rhs.mantissa_;
    Int lhs_trunc, rhs_trunc;
    int64_t lhs_exp = lhs.exponent_;
    int64_t rhs_exp = rhs.exponent_;

    if (eff < INT_MAX) {
        int target = (req < INT_MAX) ? std::min(eff, req) : eff;
        int compute_bits = target + 32;

        int lhs_bl = static_cast<int>(lhs_p->bitLength());
        if (lhs_bl > compute_bits) {
            int words_to_drop = (lhs_bl - compute_bits) / 64;
            if (words_to_drop > 0) {
                lhs_trunc = *lhs_p;
                lhs_trunc >>= words_to_drop * 64;
                lhs_exp += words_to_drop * 64;
                lhs_p = &lhs_trunc;
            }
        }
        int rhs_bl = static_cast<int>(rhs_p->bitLength());
        if (rhs_bl > compute_bits) {
            int words_to_drop = (rhs_bl - compute_bits) / 64;
            if (words_to_drop > 0) {
                rhs_trunc = *rhs_p;
                rhs_trunc >>= words_to_drop * 64;
                rhs_exp += words_to_drop * 64;
                rhs_p = &rhs_trunc;
            }
        }
    }

    const uint64_t* a_data = lhs_p->data();
    const uint64_t* b_data = rhs_p->data();
    size_t an = lhs_p->size();
    size_t bn = rhs_p->size();
    size_t total = an + bn;

    // Multiplication: write directly into result.mantissa_
    if (an < mpn::KARATSUBA_THRESHOLD && bn < mpn::KARATSUBA_THRESHOLD) {
        uint64_t rbuf[mpn::KARATSUBA_THRESHOLD * 2];
        mpn::mul_basecase(rbuf, a_data, an, b_data, bn);
        size_t rn = total;
        if (rbuf[rn - 1] == 0) --rn;
        result.mantissa_.m_words.resize_uninitialized(rn);
        std::memcpy(result.mantissa_.m_words.data(), rbuf, rn * sizeof(uint64_t));
        result.mantissa_.m_sign = (rn > 0) ? 1 : 0;
        result.mantissa_.m_state = NumericState::Normal;
    } else if (&result == &lhs || &result == &rhs) {
        // Aliasing: via an Int temporary
        result.mantissa_ = (*lhs_p) * (*rhs_p);
    } else {
        // Large size: write directly into result.mantissa_ (avoid an Int temporary)
        result.mantissa_.m_words.resize_uninitialized(total);
        uint64_t* rp = result.mantissa_.m_words.data();
        size_t mul_scratch_sz = mpn::multiply_scratch_size(an, bn);
        ScratchScope scope;
        uint64_t* mul_scratch = getThreadArena().alloc_limbs(mul_scratch_sz);
        if (an >= bn)
            mpn::multiply(rp, a_data, an, b_data, bn, mul_scratch);
        else
            mpn::multiply(rp, b_data, bn, a_data, an, mul_scratch);
        size_t rn = mpn::normalized_size(rp, total);
        result.mantissa_.m_words.resize_uninitialized(rn);
        result.mantissa_.m_sign = (rn > 0) ? 1 : 0;
        result.mantissa_.m_state = NumericState::Normal;
    }

    FLOATOPS_SET_FIELDS(result, lhs.is_negative_ != rhs.is_negative_,
                        lhs_exp + rhs_exp, eff, req);
    result.checkExponentBounds();
    FLOATOPS_APPLY_PRECISION(result, eff, req);
}

void FloatOps::sqr(const Float& x, Float& result) {
    // Special values
    if (x.isNaN() || x.isInfinity() || x.isZero()) [[unlikely]] {
        result = sangi::sqr(x, Float::defaultPrecision());
        return;
    }

    int eff = x.effective_bits_;
    int req = x.requested_bits_;

    // Truncate the operand
    const Int* xp = &x.mantissa_;
    Int x_trunc;
    int64_t x_exp = x.exponent_;

    if (eff < INT_MAX) {
        int target = (req < INT_MAX) ? std::min(eff, req) : eff;
        int compute_bits = target + 32;

        int bl = static_cast<int>(xp->bitLength());
        if (bl > compute_bits) {
            int words_to_drop = (bl - compute_bits) / 64;
            if (words_to_drop > 0) {
                x_trunc = *xp;
                x_trunc >>= words_to_drop * 64;
                x_exp += words_to_drop * 64;
                xp = &x_trunc;
            }
        }
    }

    // IntOps::squareUnchecked: NaN/Inf/Zero already rejected at the entrance → mantissa is Normal
    IntOps::squareUnchecked(*xp, result.mantissa_);

    FLOATOPS_SET_FIELDS(result, false, x_exp * 2, eff, req);
    result.checkExponentBounds();
    FLOATOPS_APPLY_PRECISION(result, eff, req);
}

// ★ Precondition (common to the 3-argument FloatOps::add / sub APIs):
//   `&result` must be a different object from `&lhs` and `&rhs` (no aliasing).
//
// Reason (latent bug, 2026-05-08 audit):
//   On the exp_diff != 0 path (line ~5951 / ~6294) the following triple issue chains:
//   (1) capturing `hi_d = lhs.mantissa_.data()` before `result.mantissa_.m_words.resize_uninitialized()`
//       → if result aliases lhs/rhs and resize causes reallocation, hi_d becomes a dangling pointer (UB).
//   (2) `std::memset(rp, 0, word_shift * 8)` destroys the low part of the shared buffer.
//   (3) forward overlap of `mpn::lshift(rp+ws, hi_d, ...)` (addressed in this commit).
//   Currently production `FloatOps::add/sub` calls conventionally use separate buffers, so it does not fire.
//   ※ Adding to itself works via `Float a; a = a + a;` (operator+)
//   (operator+ makes an lvalue copy before calling IntOps, so no aliasing occurs).
void FloatOps::add(const Float& lhs, const Float& rhs, Float& result) {
    // ── P1-1: delegate small precision to the operator+ fast path (lightweight copy with SBO) ──
    if (lhs.mantissa_.size() <= 2 && rhs.mantissa_.size() <= 2) {
        result = lhs + rhs;
        return;
    }

    // Special values: delegate to the operator
    if (lhs.isNaN() || rhs.isNaN() || lhs.isInfinity() || rhs.isInfinity()) [[unlikely]] {
        result = lhs + rhs;
        return;
    }
    if (lhs.isZero()) [[unlikely]] { result = rhs; return; }
    if (rhs.isZero()) [[unlikely]] { result = lhs; return; }

    int eff = mergeEffective(lhs.effective_bits_, rhs.effective_bits_);
    int req = mergeRequested(lhs.requested_bits_, rhs.requested_bits_);

    if (lhs.isNegative() != rhs.isNegative()) {
        // Opposite signs: delegate to the operator (cancellation computation is complex)
        result = lhs + rhs;
        return;
    }

    // Same-sign addition: write the addUnsigned equivalent directly into result
    int64_t exp_diff = lhs.exponent_ - rhs.exponent_;
    bool result_negative = lhs.isNegative();

    // BUG_FLOAT_SPARSE_MANTISSA_PRECISION_20260509 H4 fix: the early return is decided by MSB distance.
    int64_t magnitude_gap = exp_diff
                          + static_cast<int64_t>(lhs.mantissa_.bitLength())
                          - static_cast<int64_t>(rhs.mantissa_.bitLength());

    // If the exponent difference is too large: return the larger one
    int64_t exp_threshold = std::max(static_cast<int64_t>(1000),
        static_cast<int64_t>(
            std::max(lhs.requested_bits_ < INT_MAX ? lhs.requested_bits_ : 0,
                     rhs.requested_bits_ < INT_MAX ? rhs.requested_bits_ : 0)
        ) + 64);
    if (magnitude_gap > exp_threshold) { result = lhs; return; }
    if (-magnitude_gap > exp_threshold) { result = rhs; return; }

    // To cover 1000 digits ≈ 52 limbs, use a larger buffer than addUnsigned
    constexpr size_t BUF_LIMIT = 128;

    if (exp_diff == 0) {
        // Equal exponents: add the mantissas directly
        const uint64_t* a = lhs.mantissa_.data();
        const uint64_t* b = rhs.mantissa_.data();
        size_t an = lhs.mantissa_.size(), bn = rhs.mantissa_.size();
        if (an < bn) { std::swap(a, b); std::swap(an, bn); }

        if (an < BUF_LIMIT) {
            uint64_t rbuf[BUF_LIMIT + 1];
            uint64_t carry = mpn::add(rbuf, a, an, b, bn);
            size_t rn = an;
            if (carry) { rbuf[rn] = carry; rn++; }
            // Skip LSB/MSB zero words before copying
            size_t lsb = 0;
            while (lsb < rn && rbuf[lsb] == 0) ++lsb;
            while (rn > lsb && rbuf[rn - 1] == 0) --rn;
            size_t ns = rn - lsb;
            if (ns > 0) {
                result.mantissa_.m_words.resize_uninitialized(ns);
                std::memcpy(result.mantissa_.m_words.data(), rbuf + lsb, ns * sizeof(uint64_t));
                result.mantissa_.m_sign = 1;
            } else {
                result.mantissa_.m_words.resize_uninitialized(0);
                result.mantissa_.m_sign = 0;
            }
            result.mantissa_.m_state = NumericState::Normal;

            FLOATOPS_SET_FIELDS(result, result_negative,
                                lhs.exponent_ + static_cast<int64_t>(lsb) * 64, eff, req);
            FLOATOPS_APPLY_PRECISION(result, eff, req);
            return;
        }
    } else {
        // exp_diff != 0: write the shift + add directly into result
        const uint64_t* hi_data;  // the side with the larger exponent
        size_t hi_n;
        const uint64_t* lo_data;  // the side with the smaller exponent
        size_t lo_n;
        int64_t abs_diff;
        int64_t base_exp;

        if (exp_diff > 0) {
            hi_data = lhs.mantissa_.data(); hi_n = lhs.mantissa_.size();
            lo_data = rhs.mantissa_.data(); lo_n = rhs.mantissa_.size();
            abs_diff = exp_diff;
            base_exp = rhs.exponent_;
        } else {
            hi_data = rhs.mantissa_.data(); hi_n = rhs.mantissa_.size();
            lo_data = lhs.mantissa_.data(); lo_n = lhs.mantissa_.size();
            abs_diff = -exp_diff;
            base_exp = lhs.exponent_;
        }

        size_t word_shift = static_cast<size_t>(abs_diff) / 64;
        unsigned bit_shift = static_cast<unsigned>(abs_diff % 64);
        size_t aligned_max = word_shift + hi_n + 1;

        if (aligned_max <= BUF_LIMIT && lo_n <= BUF_LIMIT) {
            // Shift hi and place it into the stack buffer
            uint64_t aligned[BUF_LIMIT + 1];
            size_t aligned_n;
            std::memset(aligned, 0, word_shift * sizeof(uint64_t));
            if (bit_shift == 0) {
                std::memcpy(aligned + word_shift, hi_data, hi_n * sizeof(uint64_t));
                aligned_n = word_shift + hi_n;
            } else {
                uint64_t carry = mpn::lshift(aligned + word_shift, hi_data, hi_n, bit_shift);
                aligned_n = word_shift + hi_n;
                if (carry) { aligned[aligned_n] = carry; aligned_n++; }
            }

            // Add aligned + lo (stack buffer)
            uint64_t rbuf[BUF_LIMIT + 2];
            const uint64_t* big;
            size_t big_n;
            const uint64_t* small_p;
            size_t small_n;
            if (aligned_n >= lo_n) {
                big = aligned; big_n = aligned_n;
                small_p = lo_data; small_n = lo_n;
            } else {
                big = lo_data; big_n = lo_n;
                small_p = aligned; small_n = aligned_n;
            }

            uint64_t c = mpn::add(rbuf, big, big_n, small_p, small_n);
            size_t rn = big_n;
            if (c) { rbuf[rn] = c; rn++; }

            // Skip LSB zero words before copying (avoid memmove)
            size_t lsb = 0;
            while (lsb < rn && rbuf[lsb] == 0) ++lsb;
            // Skip MSB zero words too
            while (rn > lsb && rbuf[rn - 1] == 0) --rn;
            size_t ns = rn - lsb;

            if (ns > 0) {
                result.mantissa_.m_words.resize_uninitialized(ns);
                std::memcpy(result.mantissa_.m_words.data(), rbuf + lsb, ns * sizeof(uint64_t));
                result.mantissa_.m_sign = 1;
            } else {
                result.mantissa_.m_words.resize_uninitialized(0);
                result.mantissa_.m_sign = 0;
            }
            result.mantissa_.m_state = NumericState::Normal;

            FLOATOPS_SET_FIELDS(result, result_negative,
                                base_exp + static_cast<int64_t>(lsb) * 64, eff, req);
            FLOATOPS_APPLY_PRECISION(result, eff, req);
            return;
        }
    }

    // Large size: write directly into result.mantissa_
    // In the aliasing case (&result == &lhs or &rhs), go via a temporary
    if (&result == &lhs || &result == &rhs) {
        Float temp = lhs.addUnsigned(rhs);
        temp.is_negative_ = result_negative;
        temp.effective_bits_ = eff;
        temp.requested_bits_ = req;
        result = std::move(temp);
        FLOATOPS_APPLY_PRECISION(result, eff, req);
        return;
    }

    if (exp_diff == 0) {
        // Equal exponents: add the mantissas directly
        const uint64_t* a = lhs.mantissa_.data();
        const uint64_t* b = rhs.mantissa_.data();
        size_t an = lhs.mantissa_.size(), bn = rhs.mantissa_.size();
        if (an < bn) { std::swap(a, b); std::swap(an, bn); }

        result.mantissa_.m_words.resize_uninitialized(an + 1);
        uint64_t* rp = result.mantissa_.m_words.data();
        uint64_t carry = mpn::add(rp, a, an, b, bn);
        size_t rn = an;
        if (carry) { rp[rn] = carry; rn++; }

        // Trim LSB/MSB zeros
        size_t lsb = 0;
        while (lsb < rn && rp[lsb] == 0) ++lsb;
        while (rn > lsb && rp[rn - 1] == 0) --rn;
        size_t ns = rn - lsb;
        if (lsb > 0) std::memmove(rp, rp + lsb, ns * sizeof(uint64_t));
        result.mantissa_.m_words.resize_uninitialized(ns);
        result.mantissa_.m_sign = (ns > 0) ? 1 : 0;
        result.mantissa_.m_state = NumericState::Normal;

        FLOATOPS_SET_FIELDS(result, result_negative,
                            lhs.exponent_ + static_cast<int64_t>(lsb) * 64, eff, req);
        FLOATOPS_APPLY_PRECISION(result, eff, req);
    } else {
        // exp_diff != 0: write the shift + add directly into result
        const uint64_t* hi_d;
        size_t hi_n;
        const uint64_t* lo_d;
        size_t lo_n;
        int64_t abs_diff;
        int64_t base_exp;

        if (exp_diff > 0) {
            hi_d = lhs.mantissa_.data(); hi_n = lhs.mantissa_.size();
            lo_d = rhs.mantissa_.data(); lo_n = rhs.mantissa_.size();
            abs_diff = exp_diff;
            base_exp = rhs.exponent_;
        } else {
            hi_d = rhs.mantissa_.data(); hi_n = rhs.mantissa_.size();
            lo_d = lhs.mantissa_.data(); lo_n = lhs.mantissa_.size();
            abs_diff = -exp_diff;
            base_exp = lhs.exponent_;
        }

        size_t word_shift = static_cast<size_t>(abs_diff) / 64;
        unsigned bit_shift = static_cast<unsigned>(abs_diff % 64);
        size_t shifted_top = word_shift + hi_n + (bit_shift ? 1 : 0);
        size_t total_n = std::max(shifted_top, lo_n) + 1;

        result.mantissa_.m_words.resize_uninitialized(total_n);
        uint64_t* rp = result.mantissa_.m_words.data();

        // Zero only the low word_shift words (avoid zero-filling the whole buffer)
        if (word_shift > 0) std::memset(rp, 0, word_shift * sizeof(uint64_t));

        // Shift hi and place it into rp
        size_t shifted_actual;
        if (bit_shift == 0) {
            std::memcpy(rp + word_shift, hi_d, hi_n * sizeof(uint64_t));
            shifted_actual = word_shift + hi_n;
        } else {
            rp[word_shift + hi_n] = mpn::lshift(rp + word_shift, hi_d, hi_n, bit_shift);
            shifted_actual = word_shift + hi_n + (rp[word_shift + hi_n] ? 1 : 0);
        }
        // Zero the gap above the shifted data (the range add reads)
        for (size_t i = shifted_actual; i < total_n; i++) rp[i] = 0;
        size_t rn = shifted_actual;
        if (rn == 0) rn = 1;

        // Add lo (in-place: rp += lo)
        if (lo_n > 0) {
            uint64_t carry;
            if (rn >= lo_n) {
                carry = mpn::add(rp, rp, rn, lo_d, lo_n);
            } else {
                carry = mpn::add(rp, lo_d, lo_n, rp, rn);
                rn = lo_n;
            }
            if (carry) { rp[rn] = carry; rn++; }
        }

        // Trim LSB/MSB zeros
        size_t lsb = 0;
        while (lsb < rn && rp[lsb] == 0) ++lsb;
        while (rn > lsb && rp[rn - 1] == 0) --rn;
        size_t ns = rn - lsb;
        if (lsb > 0) std::memmove(rp, rp + lsb, ns * sizeof(uint64_t));
        result.mantissa_.m_words.resize_uninitialized(ns);
        result.mantissa_.m_sign = (ns > 0) ? 1 : 0;
        result.mantissa_.m_state = NumericState::Normal;

        FLOATOPS_SET_FIELDS(result, result_negative,
                            base_exp + static_cast<int64_t>(lsb) * 64, eff, req);
        FLOATOPS_APPLY_PRECISION(result, eff, req);
    }
}

void FloatOps::sub(const Float& lhs, const Float& rhs, Float& result) {
    // ── P1-1: delegate small precision to the operator- fast path ──
    if (lhs.mantissa_.size() <= 2 && rhs.mantissa_.size() <= 2) {
        result = lhs - rhs;
        return;
    }

    // Special values: delegate to the operator
    if (lhs.isNaN() || rhs.isNaN() || lhs.isInfinity() || rhs.isInfinity()) [[unlikely]] {
        result = lhs - rhs;
        return;
    }
    if (lhs.isZero()) [[unlikely]] {
        result = rhs;
        result.is_negative_ = !result.is_negative_;
        return;
    }
    if (rhs.isZero()) [[unlikely]] { result = lhs; return; }

    int eff = mergeEffective(lhs.effective_bits_, rhs.effective_bits_);
    int req = mergeRequested(lhs.requested_bits_, rhs.requested_bits_);

    if (lhs.isNegative() != rhs.isNegative()) {
        // Opposite signs: (+a)-(-b)=a+b → mantissa addition (no cancellation)
        // FloatOps::add already optimizes same-sign addition
        // Temporarily call add as same-sign
        FloatOps::add(lhs, rhs, result);
        result.is_negative_ = lhs.isNegative();
        return;
    }

    // Same sign: mantissa subtraction (with cancellation)
    int64_t exp_diff = lhs.exponent_ - rhs.exponent_;
    bool lhs_neg = lhs.isNegative();

    // BUG_FLOAT_SPARSE_MANTISSA_PRECISION_20260509 H4 fix: the early return is decided by MSB distance.
    int64_t magnitude_gap = exp_diff
                          + static_cast<int64_t>(lhs.mantissa_.bitLength())
                          - static_cast<int64_t>(rhs.mantissa_.bitLength());

    int64_t exp_threshold = std::max(static_cast<int64_t>(1000),
        static_cast<int64_t>(
            std::max(lhs.requested_bits_ < INT_MAX ? lhs.requested_bits_ : 0,
                     rhs.requested_bits_ < INT_MAX ? rhs.requested_bits_ : 0)
        ) + 64);
    if (magnitude_gap > exp_threshold) { result = lhs; return; }
    if (-magnitude_gap > exp_threshold) {
        result = rhs;
        result.is_negative_ = !rhs.isNegative();
        return;
    }

    int64_t mag_lhs = static_cast<int64_t>(lhs.mantissa_.bitLength()) + lhs.exponent_;
    int64_t mag_rhs = static_cast<int64_t>(rhs.mantissa_.bitLength()) + rhs.exponent_;
    int64_t max_mag = std::max(mag_lhs, mag_rhs);

    constexpr size_t BUF_LIMIT = 128;

    if (exp_diff == 0) {
        const uint64_t* a = lhs.mantissa_.data();
        const uint64_t* b = rhs.mantissa_.data();
        size_t an = lhs.mantissa_.size(), bn = rhs.mantissa_.size();

        int cmp = mpn::cmp(a, an, b, bn);
        if (cmp == 0) {
            result = Float::zero();
            if (eff >= INT_MAX) { result.effective_bits_ = INT_MAX; result.requested_bits_ = INT_MAX; }
            else { result.effective_bits_ = 0; result.requested_bits_ = req; }
            return;
        }

        bool result_negative;
        if (cmp < 0) {
            std::swap(a, b); std::swap(an, bn);
            result_negative = !lhs_neg;
        } else {
            result_negative = lhs_neg;
        }

        if (an < BUF_LIMIT) {
            uint64_t rbuf[BUF_LIMIT];
            mpn::sub(rbuf, a, an, b, bn);
            size_t rn = mpn::normalized_size(rbuf, an);
            if (rn == 0) {
                result = Float::zero();
                if (eff >= INT_MAX) { result.effective_bits_ = INT_MAX; result.requested_bits_ = INT_MAX; }
                else { result.effective_bits_ = 0; result.requested_bits_ = req; }
                return;
            }
            size_t lsb = 0;
            while (lsb < rn && rbuf[lsb] == 0) ++lsb;
            size_t ns = rn - lsb;
            if (ns > 0) {
                result.mantissa_.m_words.resize_uninitialized(ns);
                std::memcpy(result.mantissa_.m_words.data(), rbuf + lsb, ns * sizeof(uint64_t));
                result.mantissa_.m_sign = 1;
            } else {
                result.mantissa_.m_words.resize_uninitialized(0);
                result.mantissa_.m_sign = 0;
            }
            result.mantissa_.m_state = NumericState::Normal;

            int64_t result_exp = lhs.exponent_ + static_cast<int64_t>(lsb) * 64;
            FLOATOPS_SET_FIELDS(result, result_negative, result_exp, eff, req);

            // Reduction of effective bits due to cancellation
            if (ns > 0 && eff < INT_MAX) {
                int64_t result_mag = static_cast<int64_t>(result.mantissa_.bitLength()) + result_exp;
                int lost_bits = static_cast<int>(std::max(int64_t(0), max_mag - result_mag));
                result.effective_bits_ = std::max(1, eff - lost_bits);
            }
            FLOATOPS_APPLY_PRECISION(result, result.effective_bits_, req);
            return;
        }
    } else {
        // exp_diff != 0: shift + subtract
        const uint64_t* hi_data;
        size_t hi_n;
        const uint64_t* lo_data;
        size_t lo_n;
        int64_t abs_diff;
        int64_t base_exp;
        bool hi_is_lhs;

        if (exp_diff > 0) {
            hi_data = lhs.mantissa_.data(); hi_n = lhs.mantissa_.size();
            lo_data = rhs.mantissa_.data(); lo_n = rhs.mantissa_.size();
            abs_diff = exp_diff;
            base_exp = rhs.exponent_;
            hi_is_lhs = true;
        } else {
            hi_data = rhs.mantissa_.data(); hi_n = rhs.mantissa_.size();
            lo_data = lhs.mantissa_.data(); lo_n = lhs.mantissa_.size();
            abs_diff = -exp_diff;
            base_exp = lhs.exponent_;
            hi_is_lhs = false;
        }

        size_t word_shift = static_cast<size_t>(abs_diff) / 64;
        unsigned bit_shift = static_cast<unsigned>(abs_diff % 64);
        size_t aligned_max = word_shift + hi_n + 1;

        if (aligned_max <= BUF_LIMIT && lo_n <= BUF_LIMIT) {
            uint64_t aligned[BUF_LIMIT + 1];
            size_t aligned_n;
            std::memset(aligned, 0, word_shift * sizeof(uint64_t));
            if (bit_shift == 0) {
                std::memcpy(aligned + word_shift, hi_data, hi_n * sizeof(uint64_t));
                aligned_n = word_shift + hi_n;
            } else {
                uint64_t carry = mpn::lshift(aligned + word_shift, hi_data, hi_n, bit_shift);
                aligned_n = word_shift + hi_n;
                if (carry) { aligned[aligned_n] = carry; aligned_n++; }
            }

            int cmp = mpn::cmp(aligned, aligned_n, lo_data, lo_n);
            if (cmp == 0) {
                result = Float::zero();
                if (eff >= INT_MAX) { result.effective_bits_ = INT_MAX; result.requested_bits_ = INT_MAX; }
                else { result.effective_bits_ = 0; result.requested_bits_ = req; }
                return;
            }

            bool result_negative;
            const uint64_t* big;
            size_t big_n;
            const uint64_t* small_p;
            size_t small_n;

            if (cmp > 0) {
                big = aligned; big_n = aligned_n;
                small_p = lo_data; small_n = lo_n;
                result_negative = hi_is_lhs ? lhs_neg : !lhs_neg;
            } else {
                big = lo_data; big_n = lo_n;
                small_p = aligned; small_n = aligned_n;
                result_negative = hi_is_lhs ? !lhs_neg : lhs_neg;
            }

            uint64_t rbuf[BUF_LIMIT + 1];
            mpn::sub(rbuf, big, big_n, small_p, small_n);
            size_t rn = mpn::normalized_size(rbuf, big_n);
            if (rn == 0) {
                result = Float::zero();
                if (eff >= INT_MAX) { result.effective_bits_ = INT_MAX; result.requested_bits_ = INT_MAX; }
                else { result.effective_bits_ = 0; result.requested_bits_ = req; }
                return;
            }
            size_t lsb = 0;
            while (lsb < rn && rbuf[lsb] == 0) ++lsb;
            size_t ns = rn - lsb;
            if (ns > 0) {
                result.mantissa_.m_words.resize_uninitialized(ns);
                std::memcpy(result.mantissa_.m_words.data(), rbuf + lsb, ns * sizeof(uint64_t));
                result.mantissa_.m_sign = 1;
            } else {
                result.mantissa_.m_words.resize_uninitialized(0);
                result.mantissa_.m_sign = 0;
            }
            result.mantissa_.m_state = NumericState::Normal;

            int64_t result_exp = base_exp + static_cast<int64_t>(lsb) * 64;
            FLOATOPS_SET_FIELDS(result, result_negative, result_exp, eff, req);

            if (ns > 0 && eff < INT_MAX) {
                int64_t result_mag = static_cast<int64_t>(result.mantissa_.bitLength()) + result_exp;
                int lost_bits = static_cast<int>(std::max(int64_t(0), max_mag - result_mag));
                result.effective_bits_ = std::max(1, eff - lost_bits);
            }
            FLOATOPS_APPLY_PRECISION(result, result.effective_bits_, req);
            return;
        }
    }

    // Large size: write directly into result
    // In the aliasing case (&result == &lhs or &rhs), delegate to the operator
    if (&result == &lhs || &result == &rhs) {
        result = lhs - rhs;
        return;
    }

    // Same sign, large size: perform the shift + subtract directly
    // Expand into a heap buffer as in add
    const uint64_t* hi_d;
    size_t hi_n;
    const uint64_t* lo_d;
    size_t lo_n;
    int64_t abs_diff;
    int64_t base_exp;
    bool hi_is_lhs;

    if (exp_diff > 0) {
        hi_d = lhs.mantissa_.data(); hi_n = lhs.mantissa_.size();
        lo_d = rhs.mantissa_.data(); lo_n = rhs.mantissa_.size();
        abs_diff = exp_diff;
        base_exp = rhs.exponent_;
        hi_is_lhs = true;
    } else if (exp_diff < 0) {
        hi_d = rhs.mantissa_.data(); hi_n = rhs.mantissa_.size();
        lo_d = lhs.mantissa_.data(); lo_n = lhs.mantissa_.size();
        abs_diff = -exp_diff;
        base_exp = lhs.exponent_;
        hi_is_lhs = false;
    } else {
        // exp_diff == 0, large size: direct subtraction
        const uint64_t* a = lhs.mantissa_.data();
        const uint64_t* b = rhs.mantissa_.data();
        size_t an = lhs.mantissa_.size(), bn = rhs.mantissa_.size();

        int cmp = mpn::cmp(a, an, b, bn);
        if (cmp == 0) {
            result = Float::zero();
            if (eff >= INT_MAX) { result.effective_bits_ = INT_MAX; result.requested_bits_ = INT_MAX; }
            else { result.effective_bits_ = 0; result.requested_bits_ = req; }
            return;
        }

        bool result_negative;
        if (cmp < 0) {
            std::swap(a, b); std::swap(an, bn);
            result_negative = !lhs_neg;
        } else {
            result_negative = lhs_neg;
        }

        result.mantissa_.m_words.resize_uninitialized(an);
        uint64_t* rp = result.mantissa_.m_words.data();
        mpn::sub(rp, a, an, b, bn);
        size_t rn = mpn::normalized_size(rp, an);

        if (rn == 0) {
            result = Float::zero();
            if (eff >= INT_MAX) { result.effective_bits_ = INT_MAX; result.requested_bits_ = INT_MAX; }
            else { result.effective_bits_ = 0; result.requested_bits_ = req; }
            return;
        }
        size_t lsb = 0;
        while (lsb < rn && rp[lsb] == 0) ++lsb;
        size_t ns = rn - lsb;
        if (lsb > 0) std::memmove(rp, rp + lsb, ns * sizeof(uint64_t));
        result.mantissa_.m_words.resize_uninitialized(ns);
        result.mantissa_.m_sign = (ns > 0) ? 1 : 0;
        result.mantissa_.m_state = NumericState::Normal;

        int64_t result_exp = lhs.exponent_ + static_cast<int64_t>(lsb) * 64;
        FLOATOPS_SET_FIELDS(result, result_negative, result_exp, eff, req);
        if (ns > 0 && eff < INT_MAX) {
            int64_t result_mag = static_cast<int64_t>(result.mantissa_.bitLength()) + result_exp;
            int lost_bits = static_cast<int>(std::max(int64_t(0), max_mag - result_mag));
            result.effective_bits_ = std::max(1, eff - lost_bits);
        }
        FLOATOPS_APPLY_PRECISION(result, result.effective_bits_, req);
        return;
    }

    // exp_diff != 0, large size: shift + subtract
    {
        size_t word_shift = static_cast<size_t>(abs_diff) / 64;
        unsigned bit_shift = static_cast<unsigned>(abs_diff % 64);
        size_t shifted_top = word_shift + hi_n + (bit_shift ? 1 : 0);
        size_t total_n = std::max(shifted_top, lo_n) + 1;

        result.mantissa_.m_words.resize_uninitialized(total_n);
        uint64_t* rp = result.mantissa_.m_words.data();

        // Shift hi and place it
        if (word_shift > 0) std::memset(rp, 0, word_shift * sizeof(uint64_t));
        size_t shifted_actual;
        if (bit_shift == 0) {
            std::memcpy(rp + word_shift, hi_d, hi_n * sizeof(uint64_t));
            shifted_actual = word_shift + hi_n;
        } else {
            rp[word_shift + hi_n] = mpn::lshift(rp + word_shift, hi_d, hi_n, bit_shift);
            shifted_actual = word_shift + hi_n + (rp[word_shift + hi_n] ? 1 : 0);
        }
        for (size_t i = shifted_actual; i < total_n; i++) rp[i] = 0;
        size_t rn = shifted_actual;

        int cmp = mpn::cmp(rp, rn, lo_d, lo_n);
        if (cmp == 0) {
            result = Float::zero();
            if (eff >= INT_MAX) { result.effective_bits_ = INT_MAX; result.requested_bits_ = INT_MAX; }
            else { result.effective_bits_ = 0; result.requested_bits_ = req; }
            return;
        }

        bool result_negative;
        if (cmp > 0) {
            // rp > lo: rp -= lo (in-place)
            mpn::sub(rp, rp, rn, lo_d, lo_n);
            result_negative = hi_is_lhs ? lhs_neg : !lhs_neg;
        } else {
            // lo > rp: lo - rp into a new buffer
            // rp is result's buffer, so sub(rp, lo, lo_n, rp, rn) directly is r==b aliasing
            // In mpn::sub(r, a, an, b, bn), r == b is safe since each element is read before written
            mpn::sub(rp, lo_d, lo_n, rp, rn);
            rn = lo_n;
            result_negative = hi_is_lhs ? !lhs_neg : lhs_neg;
        }

        rn = mpn::normalized_size(rp, rn);
        if (rn == 0) {
            result = Float::zero();
            if (eff >= INT_MAX) { result.effective_bits_ = INT_MAX; result.requested_bits_ = INT_MAX; }
            else { result.effective_bits_ = 0; result.requested_bits_ = req; }
            return;
        }
        size_t lsb = 0;
        while (lsb < rn && rp[lsb] == 0) ++lsb;
        size_t ns = rn - lsb;
        if (lsb > 0) std::memmove(rp, rp + lsb, ns * sizeof(uint64_t));
        result.mantissa_.m_words.resize_uninitialized(ns);
        result.mantissa_.m_sign = (ns > 0) ? 1 : 0;
        result.mantissa_.m_state = NumericState::Normal;

        int64_t result_exp = base_exp + static_cast<int64_t>(lsb) * 64;
        FLOATOPS_SET_FIELDS(result, result_negative, result_exp, eff, req);
        if (ns > 0 && eff < INT_MAX) {
            int64_t result_mag = static_cast<int64_t>(result.mantissa_.bitLength()) + result_exp;
            int lost_bits = static_cast<int>(std::max(int64_t(0), max_mag - result_mag));
            result.effective_bits_ = std::max(1, eff - lost_bits);
        }
        FLOATOPS_APPLY_PRECISION(result, result.effective_bits_, req);
    }
}

void FloatOps::div(const Float& lhs, const Float& rhs, Float& result) {
    // ── P1-1: delegate small precision to the operator/ fast path ──
    if (lhs.mantissa_.size() <= 2 && rhs.mantissa_.size() == 1) {
        result = lhs / rhs;
        return;
    }

    // Special values
    if (lhs.isNaN() || rhs.isNaN()) [[unlikely]] { result = Float::nan(); return; }
    if (lhs.isInfinity() || rhs.isInfinity()) [[unlikely]] {
        result = lhs / rhs;
        return;
    }
    if (rhs.isZero()) [[unlikely]] {
        result = lhs / rhs;
        return;
    }
    if (lhs.isZero()) [[unlikely]] {
        result = Float();
        result.effective_bits_ = lhs.effective_bits_;
        result.requested_bits_ = mergeRequested(lhs.requested_bits_, rhs.requested_bits_);
        return;
    }

    int eff = mergeEffective(lhs.effective_bits_, rhs.effective_bits_);
    int req = mergeRequested(lhs.requested_bits_, rhs.requested_bits_);

    // Share the exact÷exact precision resolution with operator/ (resolveDivPrecision).
    auto _dp = resolveDivPrecision(eff, req);
    bool both_exact = _dp.both_exact;
    int eff_calc = _dp.eff_calc, req_calc = _dp.req_calc;
    int compute_prec = _dp.compute_prec;

    // Operate with raw pointers (avoid an Int copy)
    const uint64_t* ld = lhs.mantissa_.data();
    size_t ln = lhs.mantissa_.size();
    const uint64_t* rd = rhs.mantissa_.data();
    size_t rn = rhs.mantissa_.size();
    int64_t result_exponent = lhs.exponent_ - rhs.exponent_;

    // Word-wise truncation (pointer adjustment avoids an Int copy + >>=)
    if (!both_exact) {
        int div_bl = static_cast<int>(rhs.mantissa_.bitLength());
        if (div_bl > compute_prec) {
            int words_to_drop = (div_bl - compute_prec) / 64;
            if (words_to_drop > 0 && static_cast<size_t>(words_to_drop) < rn) {
                rd += words_to_drop;
                rn -= words_to_drop;
                result_exponent -= words_to_drop * 64;
            }
        }
        int lhs_bl = static_cast<int>(lhs.mantissa_.bitLength());
        if (lhs_bl > compute_prec) {
            int words_to_drop = (lhs_bl - compute_prec) / 64;
            if (words_to_drop > 0 && static_cast<size_t>(words_to_drop) < ln) {
                ld += words_to_drop;
                ln -= words_to_drop;
                result_exponent += words_to_drop * 64;
            }
        }
    }

    // Compute the scaling amount: shift so the quotient yields compute_prec bits
    // quotient_bits = (ln*64 + scaling) - rn*64 ≈ compute_prec + guard
    int lhs_bits = static_cast<int>(ln) * 64;
    int div_bits = static_cast<int>(rn) * 64;
    int scaling = compute_prec + 74 + std::max(0, div_bits - lhs_bits);
    size_t word_shift = static_cast<size_t>(scaling) / 64;
    unsigned bit_shift = static_cast<unsigned>(scaling % 64);

    // Build the scaled dividend directly in the arena (avoid an Int copy + <<=)
    size_t sn = ln + word_shift + (bit_shift > 0 ? 1 : 0);
    ScratchScope scope;
    auto& arena = getThreadArena();
    uint64_t* scaled = arena.alloc_limbs(sn + 2);  // +2: sentinel + overflow
    std::memset(scaled, 0, word_shift * sizeof(uint64_t));
    if (bit_shift == 0) {
        std::memcpy(scaled + word_shift, ld, ln * sizeof(uint64_t));
        sn = word_shift + ln;
    } else {
        uint64_t carry = mpn::lshift(scaled + word_shift, ld, ln, bit_shift);
        sn = word_shift + ln;
        if (carry) { scaled[sn] = carry; sn++; }
    }
    result_exponent -= scaling;

    bool result_negative = lhs.is_negative_ != rhs.is_negative_;

    // 1-limb divisor fast path: divide directly with divmod_1
    if (rn == 1) {
        result.mantissa_.m_words.resize_uninitialized(sn);
        uint64_t remainder_val = mpn::divmod_1(
            result.mantissa_.m_words.data(), scaled, sn, rd[0]);

        // Remove MSB zero words
        size_t qn = sn;
        while (qn > 0 && result.mantissa_.m_words[qn - 1] == 0) --qn;
        result.mantissa_.m_words.resize_uninitialized(qn);
        result.mantissa_.m_sign = (qn > 0) ? 1 : 0;
        result.mantissa_.m_state = NumericState::Normal;

        bool exact_division = (remainder_val == 0);
        if (!exact_division && qn > 0) {
            result.mantissa_.m_words[0] |= 1;
        }

        FLOATOPS_SET_FIELDS(result, result_negative,
                        result_exponent,
                        both_exact && exact_division ? INT_MAX : eff_calc,
                        both_exact && exact_division ? INT_MAX : req_calc);
        FLOATOPS_TRIM(result);
        if (!(both_exact && exact_division)) {
            FLOATOPS_APPLY_PRECISION(result, eff_calc, req_calc);
        }
        return;
    }

    // General path: call mpn::divide directly (avoid the overhead of IntOps::divmod)
    size_t qn_max = sn - rn + 1;
    uint64_t* q_buf = arena.alloc_limbs(qn_max + 1);
    uint64_t* r_buf = arena.alloc_limbs(rn);
    size_t scratch_size = mpn::divide_scratch_size(sn, rn);
    uint64_t* scratch = arena.alloc_limbs(scratch_size);

    std::memset(q_buf, 0, (qn_max + 1) * sizeof(uint64_t));
    size_t qn = mpn::divide(q_buf, r_buf, scaled, sn, rd, rn, scratch);

    // If the remainder is non-zero, set the LSB (rounding information)
    bool exact_division = (mpn::normalized_size(r_buf, rn) == 0);
    if (!exact_division && qn > 0) {
        q_buf[0] |= 1;
    }

    // Write the quotient directly into result.mantissa_
    if (qn > 0) {
        result.mantissa_.m_words.resize_uninitialized(qn);
        std::memcpy(result.mantissa_.m_words.data(), q_buf, qn * sizeof(uint64_t));
        result.mantissa_.m_sign = 1;
    } else {
        // quotient == 0 but the dividend is non-zero → minimum value 1
        result.mantissa_.m_words.resize_uninitialized(1);
        result.mantissa_.m_words[0] = 1;
        result.mantissa_.m_sign = 1;
    }
    result.mantissa_.m_state = NumericState::Normal;

    FLOATOPS_SET_FIELDS(result, result_negative,
                    result_exponent,
                    both_exact && exact_division ? INT_MAX : eff_calc,
                    both_exact && exact_division ? INT_MAX : req_calc);
    FLOATOPS_TRIM(result);
    if (!(both_exact && exact_division)) {
        FLOATOPS_APPLY_PRECISION(result, eff_calc, req_calc);
    }
}

// =============================================================================
// 3-argument transcendental functions (thin wrappers)
// =============================================================================
// Internally calls the corresponding sangi::xxx(const Float&, int) and move-assigns to result.
// A true kernel-level in-place implementation requires an internal refactor (each FloatMath
// implementation must update the result argument directly instead of creating a Float result).
// For now these are thin wrappers providing API compatibility, so the bench harness can emit a fair
// measurement column (variant="3arg").

void FloatOps::sqrt(const Float& x, Float& result) {
    int prec = Float::requestedPrecision(result);
    result = sangi::sqrt(x, prec);
}

void FloatOps::cbrt(const Float& x, Float& result) {
    int prec = Float::requestedPrecision(result);
    result = sangi::cbrt(x, prec);
}

void FloatOps::exp(const Float& x, Float& result) {
    int prec = Float::requestedPrecision(result);
    result = sangi::exp(x, prec);
}

void FloatOps::log(const Float& x, Float& result) {
    int prec = Float::requestedPrecision(result);
    result = sangi::log(x, prec);
}

void FloatOps::sin(const Float& x, Float& result) {
    int prec = Float::requestedPrecision(result);
    result = sangi::sin(x, prec);
}

void FloatOps::cos(const Float& x, Float& result) {
    int prec = Float::requestedPrecision(result);
    result = sangi::cos(x, prec);
}

void FloatOps::tan(const Float& x, Float& result) {
    int prec = Float::requestedPrecision(result);
    result = sangi::tan(x, prec);
}

void FloatOps::atan(const Float& x, Float& result) {
    int prec = Float::requestedPrecision(result);
    result = sangi::atan(x, prec);
}

void FloatOps::sinh(const Float& x, Float& result) {
    int prec = Float::requestedPrecision(result);
    result = sangi::sinh(x, prec);
}

void FloatOps::cosh(const Float& x, Float& result) {
    int prec = Float::requestedPrecision(result);
    result = sangi::cosh(x, prec);
}

void FloatOps::tanh(const Float& x, Float& result) {
    int prec = Float::requestedPrecision(result);
    result = sangi::tanh(x, prec);
}

} // namespace sangi