Shared Rust kernels

ported/combin

All modules · quoted at commit 9e1884369a67 of DnaCalc/DnaCalc. Comments are the source's own.

combin_kernel · 65 lines · calc-func/src/ported/combin.rs on GitHub
/// Worksheet `COMBIN` on the current x64 Excel reference build.
///
/// W109 identified a cyclic rising-factor graph rather than the conventional
/// multiply-first recurrence. After `k = min(k, n-k)`, Excel evaluates
///
/// `product(i=2..=k, (n-k+i-1)/i) * n`
///
/// in ascending `i`, storing every quotient and accumulator product from x87
/// PC64 to binary64 (`RN53(RN64(op))`), then multiplying `n` last through the
/// same stored-x87 operation. This graph is exact on 505 legacy rows, 20,713
/// current-build discovery rows, and a frozen 1,024-row publication heldout.
pub fn combin_kernel(n: f64, k: f64) -> Result<f64, WorksheetErrorCode> {
    use crate::ported::excel_numeric::{excel_x87_div, excel_x87_mul};

    // Range.Value2 cannot inject NaN/infinity into the live worksheet probe,
    // but the direct Rust kernel must not inherit saturating float-to-int casts.
    if !n.is_finite() || !k.is_finite() {
        return Err(WorksheetErrorCode::Num);
    }
    // The current x64 reference uses denormals-are-zero at this admission
    // seam. Preserve the sign on the zero itself; ordinary `< 0` checks then
    // admit either subnormal sign as zero while rejecting negative normals.
    let n = if n.is_subnormal() {
        0.0_f64.copysign(n)
    } else {
        n
    };
    let k = if k.is_subnormal() {
        0.0_f64.copysign(k)
    } else {
        k
    };
    let n = trunc_nonnegative(n)?;
    let raw_k = trunc_nonnegative(k)?;
    if n > COMBIN_MAX_N {
        return Err(WorksheetErrorCode::Num);
    }
    if raw_k > n {
        return Err(WorksheetErrorCode::Num);
    }
    let k = raw_k.min(n - raw_k);
    if k == 0 {
        return Ok(1.0);
    }

    let mut acc = 1.0;
    for i in 2..=k {
        let numerator = (n - k + i - 1) as f64;
        let factor = excel_x87_div(numerator, i as f64);
        acc = excel_x87_mul(acc, factor);
        // With k reduced to min(k,n-k), every remaining factor is greater
        // than one. Once the accumulator becomes nonfinite it cannot recover;
        // short-circuiting preserves the typed #NUM result and prevents an
        // admitted near-central 32-bit input from walking ~1e9 iterations.
        if !acc.is_finite() {
            return Err(WorksheetErrorCode::Num);
        }
    }
    let result = excel_x87_mul(acc, n as f64);
    if result.is_finite() {
        Ok(result)
    } else {
        Err(WorksheetErrorCode::Num)
    }
}

Used by 2 functions.