dashu-int 0.6.0

Arbitrary-precision integer math library for Rust, balancing ergonomics and efficiency. Provides UBig and IBig (unsigned and signed), with small values inlined on the stack. Efficient arithmetic, modular arithmetic, and number theory (pow, ilog, gcd, gcd_ext); two's-complement bit operations; parsing and formatting in base 2-36; optional serde, rand, num-traits, rkyv, and zeroize.
Documentation
//! Toom-Cook-3 squaring algorithm.

use crate::{
    add,
    arch::word::{SignedWord, Word},
    div,
    helper_macros::debug_assert_zero,
    math,
    memory::{self, Memory},
    mul, shift, sqr,
    Sign::*,
};
use alloc::alloc::Layout;

/// Minimum supported length.
pub const MIN_LEN: usize = 16;

/// Temporary memory required for squaring.
///
/// n bounds the operand length in words.
pub fn memory_requirement_up_to(n: usize) -> Layout {
    // Conservative bound matching the Toom-3 multiplication formula.
    // Slightly tighter than mul (no b_eval/b02), but the recurrence
    // includes recursive sqr calls which use the same formula.
    let num_words = 4 * n + 13 * (math::ceil_log2(n) as usize);
    memory::array_layout::<Word>(num_words)
}

/// b = a². b must be filled with zeros. n >= MIN_LEN.
///
/// Evaluates a(x) = a0 + a1·x + a2·x² at 0, 1, −1, 2, ∞, squares
/// each evaluation, then interpolates via the same formulas as Toom-3
/// multiplication.
pub fn square(b: &mut [Word], a: &[Word], memory: &mut Memory) {
    let n = a.len();
    debug_assert!(n >= MIN_LEN && b.len() == 2 * n);

    // Split into 3 parts. a2 may be shorter.
    let n3 = (n + 2) / 3;
    let n3_short = n - 2 * n3;

    let (a0, a12) = a.split_at(n3);
    let (a1, a2) = a12.split_at(n3);

    let mut carry: SignedWord = 0;
    let mut carry_c0: SignedWord = 0; // at 2*n3
    let mut carry_c1: SignedWord = 0; // at 3*n3+2
    let mut carry_c2: SignedWord = 0; // at 4*n3+2
    let mut carry_c3: SignedWord = 0; // at 5*n3+2

    // Evaluate at 0: V(0) = sqr(a0).
    // c_0 += V(0), c_2 -= V(0), t1 = 3·V(0).
    let (t1, mut memory) = memory.allocate_slice_fill(2 * n3 + 2, 0);
    {
        let t1_short = &mut t1[..2 * n3];
        sqr::sqr(t1_short, a0, &mut memory);
        carry_c0 += add::add_signed_same_len_in_place(&mut b[..2 * n3], Positive, t1_short);
        carry_c2 += add::add_signed_in_place(&mut b[2 * n3..4 * n3 + 2], Negative, t1_short);
        t1[2 * n3] = mul::mul_word_in_place(t1_short, 3);
        t1[2 * n3 + 1] = 0;
    }

    // Evaluate at 2: a_eval = a0 + 2·a1 + 4·a2, V(2) = sqr(a_eval).
    // t1 += V(2).
    let (a_eval, mut memory) = memory.allocate_slice_copy_fill(n3 + 1, a0, 0);
    {
        a_eval[n3] = mul::add_mul_word_same_len_in_place(&mut a_eval[..n3], 2, a1);
        a_eval[n3] += mul::add_mul_word_in_place(&mut a_eval[..n3], 4, a2);
        // V(2) = sqr(a_eval), accumulate into t1.
        let (v2, mut mem) = memory.allocate_slice_fill(2 * (n3 + 1), 0);
        sqr::sqr(v2, a_eval, &mut mem);
        debug_assert_zero!(add::add_signed_same_len_in_place(t1, Positive, v2));
    }

    // Evaluate at ∞: V(∞) = sqr(a2).
    // c_2 -= V(∞), c_4 += V(∞), t1 -= 12·V(∞).
    // Now t1 = 3·V(0) + V(2) − 12·V(∞).
    {
        let (c_eval, mut memory) = memory.allocate_slice_fill(2 * n3 + 2, 0);
        let c_short = &mut c_eval[..2 * n3_short];
        sqr::sqr(c_short, a2, &mut memory);
        carry_c2 += add::add_signed_in_place(&mut b[2 * n3..4 * n3 + 2], Negative, c_short);
        carry += add::add_signed_same_len_in_place(&mut b[4 * n3..], Positive, c_short);
        c_eval[2 * n3_short] = mul::mul_word_in_place(c_short, 12);
        debug_assert_zero!(add::sub_in_place(t1, &c_eval[..2 * n3_short + 1]));
    }

    // Evaluate at 1 and −1.
    let (t2, mut memory) = memory.allocate_slice_fill(2 * n3 + 2, 0);
    {
        // a02 = a0 + a2
        let (a02, mut memory) = memory.allocate_slice_copy_fill(n3 + 1, a0, 0);
        a02[n3] = Word::from(add::add_in_place(&mut a02[..n3], a2));

        // V(1) = sqr(a02 + a1), store in t2.
        // c_1 += V(1).
        a_eval.copy_from_slice(a02);
        a_eval[n3] += Word::from(add::add_same_len_in_place(&mut a_eval[..n3], a1));
        sqr::sqr(t2, a_eval, &mut memory);
        carry_c1 += add::add_signed_in_place(&mut b[n3..3 * n3 + 2], Positive, t2);

        // V(−1) = sqr(|a02 − a1|).
        // Compute |a02 − a1| into a_eval.
        a_eval.copy_from_slice(a02);
        let neg_sign = add::sub_in_place_with_sign(a_eval, a1);
        if neg_sign == Negative {
            // |a02 − a1| = a1 − a02.
            a_eval[..n3].copy_from_slice(a1);
            a_eval[n3] = 0;
            debug_assert_zero!(add::sub_in_place(a_eval, a02));
        }
        // Exit block — a02 freed.
    }

    // V(−1) result into c_eval, then add to t2 and 2· to t1.
    let (c_eval, mut memory) = memory.allocate_slice_fill(2 * (n3 + 1), 0);
    sqr::sqr(c_eval, a_eval, &mut memory);

    // t2 += V(−1),   t1 += 2·V(−1).   Both always Positive.
    // Now: t1 = 3·V(0) + 2·V(−1) + V(2) − 12·V(∞),
    //      t2 = V(1) + V(−1).
    debug_assert_zero!(add::add_signed_same_len_in_place(t2, Positive, c_eval));
    debug_assert_zero!(mul::add_mul_word_same_len_in_place(t1, 2, c_eval));

    // Interpolate.
    let t1_rem = div::div_by_word_in_place(t1, 6);
    let t2_rem = shift::shr_in_place(t2, 1);
    assert_eq!(t1_rem, 0);
    assert_eq!(t2_rem, 0);

    // c_1 -= t1, c_3 += t1.
    // c_2 += t2, c_3 -= t2.
    carry_c1 += add::add_signed_same_len_in_place(&mut b[n3..3 * n3 + 2], Negative, t1);
    carry_c3 += add::add_signed_same_len_in_place(&mut b[3 * n3..5 * n3 + 2], Positive, t1);
    carry_c2 += add::add_signed_same_len_in_place(&mut b[2 * n3..4 * n3 + 2], Positive, t2);
    carry_c3 += add::add_signed_same_len_in_place(&mut b[3 * n3..5 * n3 + 2], Negative, t2);

    // Apply carries.
    carry_c1 += add::add_signed_word_in_place(&mut b[2 * n3..3 * n3 + 2], carry_c0);
    carry_c2 += add::add_signed_word_in_place(&mut b[3 * n3 + 2..4 * n3 + 2], carry_c1);
    carry_c3 += add::add_signed_word_in_place(&mut b[4 * n3 + 2..5 * n3 + 2], carry_c2);
    carry += add::add_signed_word_in_place(&mut b[5 * n3 + 2..], carry_c3);

    debug_assert!(carry.abs() <= 1);
}