#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Predicate {
Orient2d,
Orient3d,
Incircle,
Insphere,
}
impl Predicate {
#[cfg(feature = "stage-counters")]
const fn index(self) -> usize {
match self {
Self::Orient2d => 0,
Self::Orient3d => 1,
Self::Incircle => 2,
Self::Insphere => 3,
}
}
#[must_use]
pub const fn all() -> [Self; 4] {
[
Self::Orient2d,
Self::Orient3d,
Self::Incircle,
Self::Insphere,
]
}
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::Orient2d => "orient2d",
Self::Orient3d => "orient3d",
Self::Incircle => "incircle",
Self::Insphere => "insphere",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Outcome {
Fast,
Exact,
ExactZero,
NonFinite,
}
impl Outcome {
#[cfg(feature = "stage-counters")]
const fn index(self) -> usize {
match self {
Self::Fast => 0,
Self::Exact => 1,
Self::NonFinite => 2,
Self::ExactZero => 3,
}
}
}
#[cfg(not(feature = "stage-counters"))]
#[inline(always)]
pub fn record(_predicate: Predicate, _outcome: Outcome) {}
#[cfg(feature = "stage-counters")]
mod enabled {
use super::{Outcome, Predicate};
use core::sync::atomic::{AtomicU64, Ordering};
#[allow(clippy::declare_interior_mutable_const)]
const ROW: [AtomicU64; 4] = [
AtomicU64::new(0),
AtomicU64::new(0),
AtomicU64::new(0),
AtomicU64::new(0),
];
static COUNTS: [[AtomicU64; 4]; 4] = [ROW, ROW, ROW, ROW];
#[inline]
pub fn record(predicate: Predicate, outcome: Outcome) {
COUNTS[predicate.index()][outcome.index()].fetch_add(1, Ordering::Relaxed);
}
#[inline]
pub fn read(predicate: Predicate, outcome: Outcome) -> u64 {
COUNTS[predicate.index()][outcome.index()].load(Ordering::Relaxed)
}
pub fn reset() {
for row in &COUNTS {
for cell in row {
cell.store(0, Ordering::Relaxed);
}
}
}
}
#[cfg(feature = "stage-counters")]
pub use enabled::record;
#[cfg(feature = "stage-counters")]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Counts {
pub fast: u64,
pub exact: u64,
pub non_finite: u64,
pub exact_zero: u64,
}
#[cfg(feature = "stage-counters")]
impl Counts {
#[must_use]
pub const fn total(self) -> u64 {
self.fast + self.exact + self.non_finite
}
#[must_use]
pub fn exact_rate(self) -> Option<f64> {
let answered = self.fast + self.exact;
if answered == 0 {
return None;
}
#[allow(clippy::cast_precision_loss)]
Some(self.exact as f64 / answered as f64)
}
}
#[cfg(feature = "stage-counters")]
#[must_use]
pub fn snapshot(predicate: Predicate) -> Counts {
Counts {
fast: enabled::read(predicate, Outcome::Fast),
exact: enabled::read(predicate, Outcome::Exact),
non_finite: enabled::read(predicate, Outcome::NonFinite),
exact_zero: enabled::read(predicate, Outcome::ExactZero),
}
}
#[cfg(feature = "stage-counters")]
pub fn reset() {
enabled::reset();
}
#[cfg(all(test, feature = "stage-counters"))]
mod tests {
use core::sync::atomic::{AtomicBool, Ordering};
use super::*;
static SERIAL: AtomicBool = AtomicBool::new(false);
struct Serial;
impl Serial {
fn lock() -> Self {
while SERIAL
.compare_exchange_weak(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_err()
{
core::hint::spin_loop();
}
Serial
}
}
impl Drop for Serial {
fn drop(&mut self) {
SERIAL.store(false, Ordering::Release);
}
}
#[test]
fn counters_record_and_reset() {
let _serial = Serial::lock();
reset();
record(Predicate::Orient2d, Outcome::Fast);
record(Predicate::Orient2d, Outcome::Fast);
record(Predicate::Orient2d, Outcome::Exact);
record(Predicate::Incircle, Outcome::NonFinite);
let o = snapshot(Predicate::Orient2d);
assert_eq!(o.fast, 2);
assert_eq!(o.exact, 1);
assert_eq!(o.total(), 3);
assert_eq!(
o.exact_zero, 0,
"ExactZero is recorded only where Stage 4 returns Zero"
);
let i = snapshot(Predicate::Incircle);
assert_eq!(i.non_finite, 1);
assert_eq!(i.exact_rate(), None);
reset();
assert_eq!(snapshot(Predicate::Orient2d).total(), 0);
}
#[test]
fn an_exact_zero_is_counted_as_exact_and_as_exact_zero() {
let _serial = Serial::lock();
reset();
let before = snapshot(Predicate::Orient2d);
let p = |k: f64| [1_400_000.0 + k / 1024.0, 900_000.0 + 3.0 * k / 1024.0];
assert_eq!(
crate::orient2d(p(0.0), p(1.0), p(5.0)),
Ok(crate::Sign::Zero)
);
let after = snapshot(Predicate::Orient2d);
assert!(after.exact > before.exact);
assert!(after.exact_zero > before.exact_zero);
assert!(after.exact_zero <= after.exact);
}
#[test]
fn exact_rate_excludes_non_finite_from_the_denominator() {
let c = Counts {
fast: 3,
exact: 1,
non_finite: 96,
exact_zero: 0,
};
assert!((c.exact_rate().expect("answered calls exist") - 0.25).abs() < 1e-12);
}
}