dashu-int 0.6.0-rc.4

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
//! Division functions.

use crate::{
    arch::word::{DoubleWord, Word},
    helper_macros::debug_assert_zero,
    math::{shl_dword, shr_word, FastDivideNormalized, FastDivideNormalized2},
    memory::{self, Memory},
    primitive::{
        double_word, extend_word, highest_dword, lowest_dword, split_dword, split_hi_word,
        WORD_BITS,
    },
    shift,
};
use alloc::alloc::Layout;
use static_assertions::const_assert;

mod divide_conquer;
mod simple;
pub(crate) use simple::div_rem_highest_word;

/// If divisor or quotient is at most this length, use the simple division algorithm.
const THRESHOLD_SIMPLE_DEFAULT: usize = 32;
const_assert!(THRESHOLD_SIMPLE_DEFAULT >= simple::MIN_LEN);

/// Environment-variable override for the division threshold.
///
/// When the `tuning` feature is active the user may set
/// `DASHU_THRESHOLD_SIMPLE_DIV` to override the compile-time default. Values
/// below [`simple::MIN_LEN`] are clamped to that constant.
mod threshold {
    #[inline]
    pub fn simple() -> usize {
        #[cfg(feature = "tuning")]
        {
            if let Ok(s) = std::env::var("DASHU_THRESHOLD_SIMPLE_DIV") {
                if let Ok(v) = s.parse::<usize>() {
                    return v.max(super::simple::MIN_LEN);
                }
            }
        }
        super::THRESHOLD_SIMPLE_DEFAULT
    }
}

/// Normalize a divisor represented as words.
///
/// Returns (shift, fast division for the top words).
#[inline]
pub(crate) fn normalize(words: &mut [Word]) -> (u32, FastDivideNormalized2) {
    let shift = words.last().unwrap().leading_zeros();
    debug_assert_zero!(shift::shl_in_place(words, shift));
    let top_words = highest_dword(words);
    (shift, FastDivideNormalized2::new(top_words))
}

/// words = words / rhs
///
/// rhs must be non-zero
///
/// Returns words % rhs. Panics if `words` is empty.
#[must_use]
pub fn div_by_word_in_place(words: &mut [Word], rhs: Word) -> Word {
    debug_assert!(rhs != 0 && !words.is_empty());

    if rhs == 1 {
        return 0;
    } else if rhs.is_power_of_two() {
        let shift = rhs.trailing_zeros();
        let rem = shift::shr_in_place(words, shift);
        return rem >> (WORD_BITS - shift);
    }

    let shift = rhs.leading_zeros();
    let fast_div_rhs = FastDivideNormalized::new(rhs << shift);
    fast_div_by_word_in_place(words, shift, fast_div_rhs)
}

/// words = words / rhs
///
/// Returns words % rhs.
#[must_use]
pub(crate) fn fast_div_by_word_in_place(
    words: &mut [Word],
    shift: u32,
    fast_div_rhs: FastDivideNormalized,
) -> Word {
    let mut rem = shift::shl_in_place(words, shift);

    for word in words.iter_mut().rev() {
        let a = double_word(*word, rem);
        let (q, r) = fast_div_rhs.div_rem_2by1(a);
        *word = q;
        rem = r;
    }
    rem >> shift
}

/// Panics if `words` is empty
pub const fn rem_by_word(words: &[Word], rhs: Word) -> Word {
    debug_assert!(rhs != 0 && !words.is_empty());

    // shortcut
    if rhs.is_power_of_two() {
        return words[0] & (rhs - 1);
    }

    // calculate remainder without normalizing the words
    let shift = rhs.leading_zeros();
    let fast_div_rhs = FastDivideNormalized::new(rhs << shift);
    let rem = fast_rem_by_normalized_word(words, fast_div_rhs);

    // normalize the remainder
    let a = extend_word(rem) << shift;
    let (_, rem) = fast_div_rhs.div_rem_2by1(a);
    rem >> shift
}

pub(crate) const fn fast_rem_by_normalized_word(
    words: &[Word],
    fast_div_rhs: FastDivideNormalized,
) -> Word {
    debug_assert!(!words.is_empty());

    // first calculate the highest remainder
    let (last, words_lo) = split_hi_word(words);
    let mut rem = fast_div_rhs.div_rem_1by1(last).1;

    // then iterate through the words. Use a manual loop because for loop is not yet const.
    let mut i = words_lo.len();
    while i > 0 {
        i -= 1;
        let a = double_word(words_lo[i], rem);
        rem = fast_div_rhs.div_rem_2by1(a).1;
    }

    rem
}

/// words = words / rhs
///
/// rhs must not fit in a word, there could be one leading zero in words.
///
/// Returns words % rhs. Panics if `words` is too short (<= 2 words)
pub fn div_by_dword_in_place(words: &mut [Word], rhs: DoubleWord) -> DoubleWord {
    debug_assert!(rhs > Word::MAX as DoubleWord, "call div_by_word_in_place when rhs is small");
    debug_assert!(words.len() >= 2);

    if rhs.is_power_of_two() {
        let first = shift::shr_in_place_one_word(words);
        let shift = rhs.trailing_zeros() - WORD_BITS;
        if shift == 0 {
            return extend_word(first);
        } else {
            let n2 = shift::shr_in_place(words, shift);
            let (n1, n0) = shr_word(first, shift);
            return double_word(n0, n1 | n2) >> (WORD_BITS - shift);
        }
    }

    let shift = rhs.leading_zeros();
    debug_assert!(shift < WORD_BITS); // high word of rhs must not be zero
    let fast_div_rhs = FastDivideNormalized2::new(rhs << shift);
    fast_div_by_dword_in_place(words, shift, fast_div_rhs)
}

/// words = words / rhs
///
/// Returns words % rhs.
#[must_use]
pub(crate) fn fast_div_by_dword_in_place(
    words: &mut [Word],
    shift: u32,
    fast_div_rhs: FastDivideNormalized2,
) -> DoubleWord {
    debug_assert!(words.len() >= 2 && shift < WORD_BITS);
    let hi = shift::shl_in_place(words, shift);

    // first div [hi, last word, second last word] by rhs
    let (top_hi, words_lo) = words.split_last_mut().unwrap();
    let (top_lo, words_lo) = words_lo.split_last_mut().unwrap();
    let (q, mut rem) = fast_div_rhs.div_rem_3by2(*top_lo, double_word(*top_hi, hi));
    *top_hi = 0;
    *top_lo = q;

    // chunk the words into double words, and do 4by2 divisions
    let mut dwords = words_lo.rchunks_exact_mut(2);
    for chunk in &mut dwords {
        let dword = lowest_dword(chunk);
        let (q, new_rem) = fast_div_rhs.div_rem_4by2(dword, rem);
        let (new_lo, new_hi) = split_dword(q);
        *chunk.first_mut().unwrap() = new_lo;
        *chunk.last_mut().unwrap() = new_hi;
        rem = new_rem;
    }

    // there might be a single word left, do a 3by2 division
    let r = dwords.into_remainder();
    if !r.is_empty() {
        debug_assert!(r.len() == 1);
        let r0 = r.first_mut().unwrap();
        let (q, new_rem) = fast_div_rhs.div_rem_3by2(*r0, rem);
        *r0 = q;
        rem = new_rem;
    }

    rem >> shift
}

/// words % rhs, panics if `words` is too short (<= 2 words) or rhs fits in a single Word.
pub const fn rem_by_dword(words: &[Word], rhs: DoubleWord) -> DoubleWord {
    debug_assert!(rhs > Word::MAX as DoubleWord, "call div_by_word_in_place when rhs is small");
    debug_assert!(words.len() >= 2);

    if rhs.is_power_of_two() {
        return double_word(words[0], words[1]) & (rhs - 1);
    }

    // calculate remainder without normalizing the words
    let shift = rhs.leading_zeros();
    debug_assert!(shift < WORD_BITS);
    let fast_div_rhs = FastDivideNormalized2::new(rhs << shift);
    let rem = fast_rem_by_normalized_dword(words, fast_div_rhs);

    // normalize the remainder
    let (a0, a1, a2) = shl_dword(rem, shift);
    let (_, rem) = fast_div_rhs.div_rem_3by2(a0, double_word(a1, a2));
    rem >> shift
}

pub(crate) const fn fast_rem_by_normalized_dword(
    words: &[Word],
    fast_div_rhs: FastDivideNormalized2,
) -> DoubleWord {
    debug_assert!(words.len() >= 2);

    // first calculate the highest remainder
    let mut i = words.len() - 1;
    let top_dword = double_word(words[i - 1], words[i]);
    let mut rem = fast_div_rhs.div_rem_2by2(top_dword).1;

    // then iterate through the words
    // chunk the words into double words, and do 4by2 divisions
    while i > 2 {
        i -= 2;
        let top_dword = double_word(words[i - 1], words[i]);
        rem = fast_div_rhs.div_rem_4by2(top_dword, rem).1;
    }

    // there might be a single word left, do a 3by2 division
    if i == 2 {
        rem = fast_div_rhs.div_rem_3by2(words[0], rem).1
    }

    rem
}

/// Memory requirement for division.
pub fn memory_requirement_exact(lhs_len: usize, rhs_len: usize) -> Layout {
    assert!(lhs_len >= rhs_len && rhs_len >= 2);
    if rhs_len <= threshold::simple() || lhs_len - rhs_len <= threshold::simple() {
        memory::zero_layout()
    } else {
        divide_conquer::memory_requirement_exact(lhs_len, rhs_len)
    }
}

/// Divide lhs by rhs, replacing the top words of lhs by the quotient and the
/// bottom words of lhs by the remainder.
///
/// `lhs = [lhs % rhs, lhs / rhs]`
///
/// `rhs` must have at least 2 words and be normalized (the top bit must be 1).
/// `lhs` must be pre-applied the shift from fast_div_rhs_top.
///
/// Returns carry in the quotient. It is at most 1 because rhs is normalized.
#[must_use]
pub(crate) fn div_rem_in_place(
    lhs: &mut [Word],
    rhs: &[Word],
    fast_div_rhs_top: FastDivideNormalized2,
    memory: &mut Memory,
) -> bool {
    debug_assert!(lhs.len() >= rhs.len() && rhs.len() >= 2);

    if rhs.len() <= threshold::simple() || lhs.len() - rhs.len() <= threshold::simple() {
        simple::div_rem_in_place(lhs, rhs, fast_div_rhs_top)
    } else {
        divide_conquer::div_rem_in_place(lhs, rhs, fast_div_rhs_top, memory)
    }
}

/// Divide lhs by rhs, replacing the top words of lhs by the quotient and the
/// bottom words of lhs by the remainder.
///
/// `lhs = [lhs % rhs, lhs / rhs]`
///
/// `rhs` must have at least 2 words and be normalized (the top bit must be 1).
/// `lhs` should not be preprocessed, it will be shifted by this function
///
/// Returns carry in the quotient. It is at most 1 because rhs is normalized.
/// To get the actual remainder, the remainder should be shifted back.
pub(crate) fn div_rem_unshifted_in_place(
    lhs: &mut [Word],
    rhs: &[Word],
    shift: u32,
    fast_div_rhs_top: FastDivideNormalized2,
    memory: &mut Memory,
) -> Word {
    // prerequisite: let (shift, fast_div_rhs_top) = normalize(rhs);
    let lhs_carry = shift::shl_in_place(lhs, shift);
    let mut q_top = if lhs_carry > 0 {
        div_rem_highest_word(lhs_carry, lhs, rhs, fast_div_rhs_top)
    } else {
        0
    };
    let overflow = div_rem_in_place(lhs, rhs, fast_div_rhs_top, memory);
    q_top += overflow as Word;
    q_top
}