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 GitHubfn 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 GitHubpub(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.