sigma_types/
on_unit.rs

1//! Types on the unit interval (between 0 and 1),
2//! either inclusive or exclusive at each extreme.
3
4use {
5    crate::{One, Sigma, Test, Zero},
6    core::{cmp::Ordering, fmt, marker::PhantomData},
7};
8
9/// Term expected to be on the unit interval (between 0 and 1) was not.
10#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
11pub struct NotOnUnit<
12    'i,
13    Input: One + PartialOrd + Zero + fmt::Debug,
14    const INCLUSIVE_AT_ZERO: bool,
15    const INCLUSIVE_AT_ONE: bool,
16>(&'i Input);
17
18impl<
19    Input: One + PartialOrd + Zero + fmt::Debug,
20    const INCLUSIVE_AT_ZERO: bool,
21    const INCLUSIVE_AT_ONE: bool,
22> fmt::Display for NotOnUnit<'_, Input, INCLUSIVE_AT_ZERO, INCLUSIVE_AT_ONE>
23{
24    #[inline]
25    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26        #![expect(
27            clippy::use_debug,
28            reason = "Intentional and informative, not just forgotten print-debugging"
29        )]
30
31        let Self(u) = *self;
32        write!(
33            f,
34            "Not on {}0, 1{}: {u:#?}",
35            if INCLUSIVE_AT_ZERO { '[' } else { '(' },
36            if INCLUSIVE_AT_ONE { ']' } else { ')' },
37        )
38    }
39}
40
41/// Terms on the unit interval (between 0 and 1),
42/// either inclusive or exclusive at each extreme.
43pub type OnUnit<Input, const INCLUSIVE_AT_ZERO: bool, const INCLUSIVE_AT_ONE: bool> =
44    Sigma<Input, OnUnitInvariant<Input, INCLUSIVE_AT_ZERO, INCLUSIVE_AT_ONE>>;
45
46/// Terms on the unit interval (between 0 and 1),
47/// either inclusive or exclusive at each extreme.
48#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
49pub struct OnUnitInvariant<
50    Input: One + PartialOrd + Zero + fmt::Debug,
51    const INCLUSIVE_AT_ZERO: bool,
52    const INCLUSIVE_AT_ONE: bool,
53>(PhantomData<Input>);
54
55impl<
56    Input: One + PartialOrd + Zero + fmt::Debug,
57    const INCLUSIVE_AT_ZERO: bool,
58    const INCLUSIVE_AT_ONE: bool,
59> Test<Input, 1> for OnUnitInvariant<Input, INCLUSIVE_AT_ZERO, INCLUSIVE_AT_ONE>
60{
61    const ADJECTIVE: &str = "on the unit interval";
62    type Error<'i>
63        = NotOnUnit<'i, Input, INCLUSIVE_AT_ZERO, INCLUSIVE_AT_ONE>
64    where
65        Input: 'i;
66
67    #[inline]
68    fn test([input]: [&Input; 1]) -> Result<(), Self::Error<'_>> {
69        match input.partial_cmp(&Input::ZERO) {
70            None | Some(Ordering::Less) => return Err(NotOnUnit(input)),
71            Some(Ordering::Equal) => {
72                if !INCLUSIVE_AT_ZERO {
73                    return Err(NotOnUnit(input));
74                }
75            }
76            Some(Ordering::Greater) => {}
77        }
78        match input.partial_cmp(&Input::ONE) {
79            None | Some(Ordering::Greater) => return Err(NotOnUnit(input)),
80            Some(Ordering::Equal) => {
81                if !INCLUSIVE_AT_ONE {
82                    return Err(NotOnUnit(input));
83                }
84            }
85            Some(Ordering::Less) => {}
86        }
87        Ok(())
88    }
89}