// Copyright (C) 2026 Kiyotsugu Arai // SPDX-License-Identifier: LGPL-3.0-or-later // Float.cpp // Implementation of the multiple-precision floating-point class #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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(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(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(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(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(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(-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(D); effective_bits_ = INT_MAX; requested_bits_ = INT_MAX; normalize(); } else { int num_bits = static_cast(numerator.bitLength()); int den_bits = static_cast(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(target_bits) - static_cast(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(*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(mantissa_.bitLength()) + exponent_; int64_t mag_rhs = static_cast(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(mantissa_.bitLength()) + exponent_; int lost_bits = static_cast(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(*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(mantissa_.bitLength()) + exponent_; int64_t mag_rhs = static_cast(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(mantissa_.bitLength()) + exponent_; int lost_bits = static_cast(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(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(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(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(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(shift - 1) / 64; unsigned gb = static_cast((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(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((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(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(mantissa_.bitLength()); // effective exponent = exponent_ + bit_length - 1 (position of MSB) int64_t msb_exp = exponent_ + static_cast(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(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(D)); Int product = extended_mantissa * pow5; int64_t shift = static_cast(D) + adjusted_exponent; Int N; if (shift >= 0) { N = product << static_cast(shift); } else { int rshift = static_cast(-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(std::floor(std::log10(val))) + 1; else intDigits = 0; } int expectedLen = intDigits + D; if (static_cast(digits.size()) > expectedLen && expectedLen > 0) { digits.resize(expectedLen); } std::string result; if (is_negative_) result += "-"; if (static_cast(digits.size()) <= D) { // Integer part is 0 (|value| < 1) result += "0."; if (D > static_cast(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(exp); fracBitCount = 0; } else { int shift = static_cast(-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(base)); fracDigits = static_cast(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(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(mantissa_.bitLength()); int64_t e_dec_est = static_cast( std::floor((static_cast(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(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(Dpoint)); Int product = extended_mantissa * pow5; int64_t shift = Dpoint + adj_exp; Int N; if (shift >= 0) { N = product << static_cast(shift); } else { int rshift = static_cast(-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(digits.size()) - Dpoint - 1; // Round to precision significant digits (round half up at the precision+1-th digit) if (static_cast(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(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::quiet_NaN(); if (isInfinity()) return is_negative_ ? -std::numeric_limits::infinity() : std::numeric_limits::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(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(shift); if (total_exp > 1023) { return is_negative_ ? -std::numeric_limits::infinity() : std::numeric_limits::infinity(); } if (total_exp < -1074) { return is_negative_ ? -0.0 : 0.0; } result = std::ldexp(result, static_cast(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(exponent_); } else if (exponent_ < 0) { // Negative exponent: right shift (division) result >>= static_cast(-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(mantissa_.bitLength()); // Compute the bit length accounting for the exponent return mantissa_bits + static_cast(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(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(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 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(std::ceil( static_cast(precision_bits) / (2.0 * std::log2(static_cast(n))))) + 5; int64_t n_sq = static_cast(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(N), 2.0)); int N_new = static_cast(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(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(std::ceil( static_cast(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(std::ceil( static_cast(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(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(n - k)); binom = binom / Int(static_cast(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(corr.mantissa().bitLength()); if (cb < -(static_cast(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 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::max)(); for (int m = 1; m <= max_m; m++) { int64_t denom_i = static_cast(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::min)() : term.exponent() + static_cast(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(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(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(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(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(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::max)(); for (int m = 1; static_cast(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(term.mantissa().bitLength()); if (lead_bits == (std::numeric_limits::max)()) lead_bits = tb; else if (tb < lead_bits - static_cast(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::max)(); for (int m = 1; static_cast(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(term.mantissa().bitLength()); if (lead_bits == (std::numeric_limits::max)()) lead_bits = tb; else if (tb < lead_bits - static_cast(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(corr.mantissa().bitLength()); if (cb < -(static_cast(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(corr.mantissa().bitLength()); if (cb < -(static_cast(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(term.mantissa().bitLength()); if (tb < -(static_cast(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(term.mantissa().bitLength()); if (tb < -(static_cast(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(term.mantissa().bitLength()); if (tb < -(static_cast(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 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> a; std::vector piv; int n; }; inline BLU blu_factor(std::vector> a, int wp) { int n = static_cast(a.size()); std::vector 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 blu_solve(const BLU& lu, const std::vector& b, int wp) { int n = lu.n; std::vector 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 cosrow(const Float& tv, int M1, int wp) { std::vector 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& c, int m, const Float& tv, int wp) { std::vector 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& refs, int m, std::vector& c, Float& delta, int wp) { int npts = m + 2, M1 = m + 1; std::vector> A(npts, std::vector(npts)); std::vector b(npts); for (int i = 0; i < npts; ++i) { std::vector 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 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& 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& 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& outRefs, const std::vector* 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 refs; if (initRefs && static_cast(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 grid(G + 1); for (int j = 0; j <= G; ++j) grid[j] = lobatto(j, G, pi, wp); std::vector c; Float delta(0); for (int it = 0; it < maxit; ++it) { solveRef(refs, m, c, delta, wp); std::vector 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 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 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(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(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 growRefs(const std::vector& refs, int target, int wp) { std::vector r = refs; std::sort(r.begin(), r.end()); while (static_cast(r.size()) < target) { int gi = 0; Float gmax(-1); for (int i = 0; i + 1 < static_cast(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 ms; std::vector sm; std::vector refs; bool have = false; for (int m = 4; m <= m_max; m += 2) { std::vector init = have ? growRefs(refs, m + 2, wp) : std::vector(); 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(ms.size()); std::vector 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> a; std::vector piv; int n; }; inline GkwLU gkw_luFactor(std::vector> a, int wp) { int n = static_cast(a.size()); std::vector 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 gkw_luSolve(const GkwLU& lu, const std::vector& b, int wp) { int n = lu.n; std::vector 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 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(wp * 0.4) + 5; // a+N ≳ 0.4·wp (same as the existing one) const int N = std::max(1, target_aN - static_cast(a.toDouble())); const int nS = sMax - 1; // s=2..sMax → index 0..nS-1 std::vector 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(PI_VAL * static_cast(target_aN)) + 10; auto bern = computeBernoulliNumbers(M, wp); // bern[k] = B_{2k} std::vector 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::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::min)() : correction.exponent() + static_cast(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(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(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 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> V(n, std::vector(n)), Z(n, std::vector(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 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> A(n, std::vector(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& 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>& M, const std::vector& v) { std::vector 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 c(n, Float(1)); normalize(c); const int iters = static_cast(wp / 1.6) + 18; for (int it = 0; it < iters; ++it) { std::vector y = gkw_luSolve(lu, matvec(V, c), wp); normalize(y); c = std::move(y); } std::vector 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(k)); Float term = divScalarF(lnx_pow, static_cast(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(contribution.mantissa().bitLength()); if (cb < -(static_cast(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(corr.mantissa().bitLength()); if (cb < -(static_cast(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 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(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(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(wp + 4)) break; digits[static_cast(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(mantissa_.bitLength()) - static_cast(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(1000), static_cast( 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(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(abs_diff) / 64; unsigned bit_shift = static_cast(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(lsb) * 64; result.is_negative_ = false; return result; } // Large size: legacy path (via Int) if (exp_diff > 0) { Int padded_mantissa = mantissa_ << static_cast(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(-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(mantissa_.bitLength()) - static_cast(rhs.mantissa_.bitLength()); int64_t exp_threshold = std::max(static_cast(1000), static_cast( 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(*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(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(*this).addUnsigned(rhs); } // Large size: exploit move if (exp_diff > 0) { mantissa_ <<= static_cast(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(-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(mantissa_.bitLength()) - static_cast(rhs.mantissa_.bitLength()); // Exponent-difference threshold (same as addUnsigned) int64_t exp_threshold = std::max(static_cast(1000), static_cast( 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(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(abs_diff) / 64; unsigned bit_shift = static_cast(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(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(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(-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(mantissa_.bitLength()) - static_cast(rhs.mantissa_.bitLength()); int64_t exp_threshold = std::max(static_cast(1000), static_cast( 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(*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(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(*this).subtractUnsigned(rhs); } // Large size: exploit move if (exp_diff > 0) { mantissa_ <<= static_cast(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(-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(mantissa_.bitLength()) - static_cast(rhs.mantissa_.bitLength()); int64_t exp_threshold = std::max(static_cast(1000), static_cast( 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(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(abs_diff) / 64; unsigned bit_shift = static_cast(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(lsb) * 64; return; } // Large size: via Int if (exp_diff > 0) { mantissa_ <<= static_cast(exp_diff); mantissa_ = std::move(mantissa_) + rhs.mantissa_; exponent_ = rhs.exponent_; } else { Int padded_rhs = rhs.mantissa_ << static_cast(-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(mantissa_.bitLength()) - static_cast(rhs.mantissa_.bitLength()); int64_t exp_threshold = std::max(static_cast(1000), static_cast( 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(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(abs_diff) / 64; unsigned bit_shift = static_cast(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(lsb) * 64; is_negative_ = result_negative; return; } // Large size: via Int if (exp_diff > 0) { mantissa_ <<= static_cast(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(-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(mantissa_.bitLength()) + exponent_; int64_t mag_rhs = static_cast(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(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(-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 implementation //====================================================================== Float numeric_traits::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::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(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(abs_diff) / 64; unsigned bit_shift = static_cast(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(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(lhs.mantissa_.bitLength()) + lhs.exponent_; int64_t mag_rhs = static_cast(rhs.mantissa_.bitLength()) + rhs.exponent_; int64_t max_mag = std::max(mag_lhs, mag_rhs); int64_t result_exp = base_exp + static_cast(lsb) * 64; // Compute result's bitLength (before fromRawLimbs) int result_bl = static_cast((ns - 1) * 64 + (64 - std::countl_zero(rbuf[lsb + ns - 1]))); int64_t result_mag = static_cast(result_bl) + result_exp; int lost_bits = static_cast(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(lhs.mantissa_.bitLength()) + lhs.exponent_; int64_t mag_rhs = static_cast(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(result.mantissa_.bitLength()) + result.exponent_; int lost_bits = static_cast(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(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(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(lhs.mantissa_.bitLength()) + lhs.exponent_; int64_t mag_rhs = static_cast(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(result.mantissa_.bitLength()) + result.exponent_; int lost_bits = static_cast(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(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(rhs)); } return operator+(std::move(rhs), static_cast(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(lhs.mantissa_.bitLength()) + lhs.exponent_; int64_t mag_rhs = static_cast(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(result.mantissa_.bitLength()) + result.exponent_; int lost_bits = static_cast(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(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(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(lhs.mantissa_.bitLength()) + lhs.exponent_; int64_t mag_rhs = static_cast(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(result.mantissa_.bitLength()) + result.exponent_; int lost_bits = static_cast(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(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(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(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((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(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(rbuf + skip, rn), 1); result_exponent = lhs_exp + rhs_exp + static_cast(skip) * 64; } else { size_t rn = total; if (rbuf[rn - 1] == 0) --rn; result_mantissa = Int::fromRawWords( std::span(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(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(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(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(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(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((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(rbuf + skip, rn), 1); result_exponent = lhs_exp + rhs_exp + static_cast(skip) * 64; } else { size_t rn = total; if (rbuf[rn - 1] == 0) --rn; result_mantissa = Int::fromRawWords( std::span(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(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(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(scaling) / 64; unsigned bit_shift = static_cast(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(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(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(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 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(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(divisor.bitLength()); int lhs_bl = static_cast(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(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(lhs), rhs); } if (rhs.mantissa_.size() >= NEWTON_DIV_THRESHOLD_RVAL) { return operator/(static_cast(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(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(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(divisor.bitLength()); int lhs_bl_rval = static_cast(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(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 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(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(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(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 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(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(rhs)); Float result = mulScalarF(lhs, static_cast(-(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(rhs)); Float result = mulScalarF(std::move(lhs), static_cast(-(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(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(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(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(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(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::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(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(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(value.mantissa_.bitLength()); int64_t total_exp = value.exponent_ + b; if (total_exp > static_cast(std::numeric_limits::max())) { *exp = std::numeric_limits::max(); } else if (total_exp < static_cast(std::numeric_limits::min())) { *exp = std::numeric_limits::min(); } else { *exp = static_cast(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(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(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(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(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(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(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(lhs.mantissa_.bitLength()) - static_cast(rhs.mantissa_.bitLength()); // If the exponent difference is too large: return the larger one int64_t exp_threshold = std::max(static_cast(1000), static_cast( 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(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(abs_diff) / 64; unsigned bit_shift = static_cast(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(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(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(abs_diff) / 64; unsigned bit_shift = static_cast(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(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(lhs.mantissa_.bitLength()) - static_cast(rhs.mantissa_.bitLength()); int64_t exp_threshold = std::max(static_cast(1000), static_cast( 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(lhs.mantissa_.bitLength()) + lhs.exponent_; int64_t mag_rhs = static_cast(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(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(result.mantissa_.bitLength()) + result_exp; int lost_bits = static_cast(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(abs_diff) / 64; unsigned bit_shift = static_cast(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(lsb) * 64; FLOATOPS_SET_FIELDS(result, result_negative, result_exp, eff, req); if (ns > 0 && eff < INT_MAX) { int64_t result_mag = static_cast(result.mantissa_.bitLength()) + result_exp; int lost_bits = static_cast(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(lsb) * 64; FLOATOPS_SET_FIELDS(result, result_negative, result_exp, eff, req); if (ns > 0 && eff < INT_MAX) { int64_t result_mag = static_cast(result.mantissa_.bitLength()) + result_exp; int lost_bits = static_cast(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(abs_diff) / 64; unsigned bit_shift = static_cast(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(lsb) * 64; FLOATOPS_SET_FIELDS(result, result_negative, result_exp, eff, req); if (ns > 0 && eff < INT_MAX) { int64_t result_mag = static_cast(result.mantissa_.bitLength()) + result_exp; int lost_bits = static_cast(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(rhs.mantissa_.bitLength()); if (div_bl > compute_prec) { int words_to_drop = (div_bl - compute_prec) / 64; if (words_to_drop > 0 && static_cast(words_to_drop) < rn) { rd += words_to_drop; rn -= words_to_drop; result_exponent -= words_to_drop * 64; } } int lhs_bl = static_cast(lhs.mantissa_.bitLength()); if (lhs_bl > compute_prec) { int words_to_drop = (lhs_bl - compute_prec) / 64; if (words_to_drop > 0 && static_cast(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(ln) * 64; int div_bits = static_cast(rn) * 64; int scaling = compute_prec + 74 + std::max(0, div_bits - lhs_bits); size_t word_shift = static_cast(scaling) / 64; unsigned bit_shift = static_cast(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