Shared Rust kernels

catalogue/comparison

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

compare · 10 lines · calc-func/src/catalogue/comparison.rs on GitHub
fn compare(
    id: FunctionId,
    arguments: &[CalcValue],
    services: &EvalServices<'_>,
    want: Want,
    holds: fn(Ordering) -> bool,
) -> Result<CalcValue, FunctionFault> {
    let site = Site::new(id, services);
    Site::finish(order(&site, arguments, want).map(|ordering| CalcValue::logical(holds(ordering))))
}

Used by 6 functions.

Operand::kind · 8 lines · calc-func/src/catalogue/comparison.rs on GitHub
/// The order of kinds: numbers, then text, then logicals.
fn kind(self) -> u8 {
    match self {
        Operand::Number(_) | Operand::Overflow(_) | Operand::Blank => 0,
        Operand::Text(_) => 1,
        Operand::Logical(_) => 2,
    }
}

Used by 7 functions.

Operand::blank · 9 lines · calc-func/src/catalogue/comparison.rs on GitHub
/// The empty value of this operand's kind, which a blank compares as.
fn blank(self) -> Self {
    match self {
        Operand::Number(_) | Operand::Overflow(_) => Operand::Number(0.0),
        Operand::Text(_) => Operand::Text(&[]),
        Operand::Logical(_) => Operand::Logical(false),
        Operand::Blank => Operand::Blank,
    }
}

Used by 7 functions.

order · 34 lines · calc-func/src/catalogue/comparison.rs on GitHub
pub(super) fn order(site: &Site<'_>, arguments: &[CalcValue], want: Want) -> Step<Ordering> {
    site.scalar_operands(arguments)?;
    let mut operands = [Operand::Blank; 2];
    for (index, argument) in arguments.iter().enumerate() {
        operands[index] = match argument.core_ref() {
            CoreRef::Number(n) if argument.is_excel_overflow() => Operand::Overflow(n),
            CoreRef::Number(n) => Operand::Number(site.argument(index, coerce::operand(n))?),
            CoreRef::Text(text) => Operand::Text(text.utf16_code_units()),
            CoreRef::Logical(b) => Operand::Logical(b),
            CoreRef::Empty | CoreRef::Missing => Operand::Blank,
            CoreRef::Error(code) => return Err(Exit::Error(code)),
            CoreRef::Array(_) | CoreRef::Reference(_) => {
                return Err(site.not_modelled(Some(index)));
            }
        };
    }
    let (left, right) = match (operands[0], operands[1]) {
        (Operand::Blank, Operand::Blank) => return Ok(Ordering::Equal),
        (Operand::Blank, other) => (other.blank(), other),
        (other, Operand::Blank) => (other, other.blank()),
        pair => pair,
    };
    match (left, right) {
        (Operand::Number(a), Operand::Number(b)) => numbers(site, a, b),
        (Operand::Overflow(a), Operand::Number(b))
        | (Operand::Number(a), Operand::Overflow(b))
        | (Operand::Overflow(a), Operand::Overflow(b)) => overflow_numbers(site, a, b),
        (Operand::Text(a), Operand::Text(b)) => {
            texts(a, b, want).map_err(|beyond| site.not_modelled(Some(beyond.operand)))
        }
        (Operand::Logical(a), Operand::Logical(b)) => Ok(a.cmp(&b)),
        (a, b) => Ok(a.kind().cmp(&b.kind())),
    }
}

Used by 7 functions.

overflow_numbers · 21 lines · calc-func/src/catalogue/comparison.rs on GitHub
/// The order of qualified overflow encodings and already-validated numeric operands.
/// Callers must retain the CalcValue qualification; arbitrary NaNs are not admitted.
pub(super) fn overflow_numbers(site: &Site<'_>, a: f64, b: f64) -> Step<Ordering> {
    if !(a.is_nan() || b.is_nan()) {
        return numbers(site, a, b);
    }
    if a.to_bits() == b.to_bits() {
        return Ok(Ordering::Equal);
    }
    if a.is_sign_negative() != b.is_sign_negative() {
        return Ok(a.total_cmp(&b));
    }
    let half = |number: f64| {
        if number.is_nan() {
            f64::from_bits(number.to_bits() - (1_u64 << 52))
        } else {
            number * 0.5
        }
    };
    numbers(site, half(a), half(b))
}

Used by 12 functions.

numbers · 27 lines · calc-func/src/catalogue/comparison.rs on GitHub
/// The order of two ordinary finite numbers. Equal numbers need no rounding. For different numbers the
/// nearness test computes their difference and [`APART`] of the larger magnitude at the call's
/// arithmetic sites, which round under its numeric mode. Numbers farther apart order by value;
/// nearer ones by their 15-digit roundings, refused ([`in_band`]) where Excel's rounding may
/// depart from the exact one and the departure could decide the comparison.
pub(super) fn numbers(site: &Site<'_>, a: f64, b: f64) -> Step<Ordering> {
    if !a.is_finite() || !b.is_finite() {
        return Err(site.not_modelled(None));
    }
    if a == b {
        return Ok(Ordering::Equal);
    }
    let difference = site.subtract(a, b)?.abs();
    let apart = site.multiply(a.abs().max(b.abs()), APART)?;
    if difference > apart || a.is_sign_negative() != b.is_sign_negative() {
        return Ok(a.total_cmp(&b));
    }
    let (Some(x), Some(y)) = (Roundings::of(a.abs()), Roundings::of(b.abs())) else {
        return Err(in_band(site));
    };
    match (x.other, y.other) {
        (None, None) if x.nearest == y.nearest => Ok(Ordering::Equal),
        (None, None) => Ok(a.total_cmp(&b)),
        _ if x.disjoint(&y) => Ok(a.total_cmp(&b)),
        _ => Err(in_band(site)),
    }
}

Used by 37 functions.

in_band · 6 lines · calc-func/src/catalogue/comparison.rs on GitHub
/// The refusal of two nearly equal numbers in a band where Excel's rounding departs from the
/// exact one (a near tie of [`NEAR_TIE_BITS`], [`TOP_BINADE`], [`FINER`]): not modelled, naming no
/// argument, since the pair decides it. Bead dc-j9k owns Excel's rule in these bands.
fn in_band(site: &Site<'_>) -> Exit {
    site.not_modelled(None)
}

Used by 37 functions.

Rounded::up · 12 lines · calc-func/src/catalogue/comparison.rs on GitHub
/// The next rounding up: one more in the last digit, carrying into a new leading digit.
fn up(self) -> Rounded {
    let digits = self.digits + 1;
    if digits == 1_000_000_000_000_000 {
        Rounded {
            digits: 100_000_000_000_000,
            exponent: self.exponent + 1,
        }
    } else {
        Rounded { digits, ..self }
    }
}

Used by 37 functions.

Roundings::of · 63 lines · calc-func/src/catalogue/comparison.rs on GitHub
/// The roundings of a finite magnitude, or `None` where Excel rounds it one digit finer
/// ([`FINER`]) or by a rule not modelled ([`TOP_BINADE`]).
fn of(magnitude: f64) -> Option<Roundings> {
    if magnitude >= TOP_BINADE
        || FINER
            .iter()
            .any(|&(low, high)| (low..=high).contains(&magnitude))
    {
        return None;
    }
    if magnitude == 0.0 {
        let zero = Rounded {
            digits: 0,
            exponent: 0,
        };
        return Some(Roundings {
            nearest: zero,
            other: None,
        });
    }
    let exact = ExactDecimal::binary(magnitude);
    let all = exact.coefficient.digits();
    let length = all.len();
    let exponent = i32::try_from(length).expect("a digit count") - 1 + exact.exponent;
    let kept = &all[..length.min(DIGITS)];
    let digits = kept.parse::<u64>().expect("decimal digits")
        * 10u64.pow(u32::try_from(DIGITS - kept.len()).expect("a digit count"));
    let down = Rounded { digits, exponent };
    if length <= DIGITS {
        return Some(Roundings {
            nearest: down,
            other: None,
        });
    }
    // With r the dropped part of the m dropped digits, the tie is at r = 10^m / 2: the number
    // rounds up from it, and is near it when |2r - 10^m| * 2^(NEAR_TIE_BITS - 1) is below the
    // coefficient.
    let dropped = u32::try_from(length - DIGITS).expect("a digit count");
    let power = Natural::pow10(dropped);
    let (_, remainder) = exact.coefficient.div_rem(&power);
    let mut twice = remainder.clone();
    twice.add(&remainder);
    let up = twice >= power;
    let distance = if up {
        twice.subtract(&power);
        twice
    } else {
        let mut gap = power.clone();
        gap.subtract(&twice);
        gap
    };
    let near = !distance.is_zero()
        && distance.times(&Natural::from_u64(1u64 << (NEAR_TIE_BITS - 1))) < exact.coefficient;
    let (nearest, other) = if up {
        (down.up(), down)
    } else {
        (down, down.up())
    };
    Some(Roundings {
        nearest,
        other: near.then_some(other),
    })
}

Used by 37 functions.

Roundings::disjoint · 8 lines · calc-func/src/catalogue/comparison.rs on GitHub
/// Whether no rounding of `self` is a rounding of `other`.
fn disjoint(&self, other: &Roundings) -> bool {
    let of = |r: &Roundings| [Some(r.nearest), r.other];
    of(self)
        .iter()
        .flatten()
        .all(|x| !of(other).iter().flatten().any(|y| x == y))
}

Used by 37 functions.

texts · 13 lines · calc-func/src/catalogue/comparison.rs on GitHub
/// The order of two texts as the comparison operators give it, or [`Beyond`] when either holds a
/// character beyond the collation's repertoire and they are not the same. For [`Want::Equality`]
/// any unequal pair is `Less`.
pub(super) fn texts(a: &[u16], b: &[u16], want: Want) -> Result<Ordering, Beyond> {
    if a == b {
        return Ok(Ordering::Equal);
    }
    let ordering = collation::compare(a, b)?;
    Ok(match want {
        Want::Equality if ordering.is_ne() => Ordering::Less,
        Want::Equality | Want::Order => ordering,
    })
}

Used by 38 functions.