Skip to main content

vildrose_core/
trit.rs

1//! A single balanced ternary property. Analogous to a bit in binary, just with three states: N, Z and P.
2use std::fmt::Write;
3
4/// A single balanced ternary property. Analogous to a bit in binary, just with three states: N, Z and P.
5#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
6#[repr(i8)]
7pub enum Trit {
8    /// Trit value referring to, negative, -1 or unknown
9    N = -1,
10    /// Trit value referring to, zero, 0, blank or false
11    Z = 0,
12    /// Trit value referring to, positive, 1 or true
13    P = 1,
14}
15
16/// Helper function for indexes for the static tables.
17const fn idx(t: Trit) -> usize {
18    (t as i8 + 1).cast_unsigned() as usize
19}
20
21/// Tritwise minimum (follows Kleene AND logic).
22///
23/// ```text
24///     N  Z  P
25///  N [N, N, N]
26///  Z [N, Z, Z]
27///  P [N, Z, P]
28/// ```
29static TMIN: [Trit; 9] = {
30    use Trit::{N, P, Z};
31    [N, N, N, N, Z, Z, N, Z, P]
32};
33
34/// Tritwise maximum (follows Kleene OR logic).
35///
36/// ```text
37///     N  Z  P
38///  N [N, Z, P]
39///  Z [Z, Z, P]
40///  P [P, P, P]
41/// ```
42static TMAX: [Trit; 9] = {
43    use Trit::{N, P, Z};
44    [N, Z, P, Z, Z, P, P, P, P]
45};
46
47/// Sum trit from single-trit addition (without carry-in).
48///
49/// ```text
50///      N  Z  P
51///  N [ P, N, Z]   (-1)+(-1)=-2 → trit P carry N
52///  Z [ N, Z, P]   (-1)+0  =-1 → trit N carry Z
53///  P [ Z, P, N]   (-1)+1  = 0 → trit Z carry Z
54/// ```
55static ADD_SUM: [Trit; 9] = {
56    use Trit::{N, P, Z};
57    [P, N, Z, N, Z, P, Z, P, N]
58};
59
60/// Carry trit from single-trit addition (without carry-in).
61///
62/// Only N+N produces a negative carry, only P+P a positive carry.
63///
64/// ```text
65///     N  Z  P
66///  N [N, Z, Z]
67///  Z [Z, Z, Z]
68///  P [Z, Z, P]
69/// ```
70static ADD_CARRY: [Trit; 9] = {
71    use Trit::{N, P, Z};
72    [N, Z, Z, Z, Z, Z, Z, Z, P]
73};
74
75/// Consensus function (majority voting).
76///
77/// Returns Z if the inputs disagree or if either input is Z.
78/// Returns the shared value only if both inputs agree.
79///
80/// ```text
81///        N    Z    P
82/// N  [   N    Z    Z  ]  consensus(N,N) = N, consensus(N,Z) = Z, consensus(N,P) = Z
83/// Z  [   Z    Z    Z  ]  consensus with Z is always Z (absorbing)
84/// P  [   Z    Z    P  ]  consensus(P,N) = Z, consensus(P,Z) = Z, consensus(P,P) = P
85/// ```
86static CONSENSUS: [Trit; 9] = {
87    use Trit::{N, P, Z};
88    [N, Z, Z, Z, Z, Z, Z, Z, P]
89};
90
91// <- Implementation logic starts here
92impl Trit {
93    /// Construct a new trit using an i8
94    ///
95    /// For untrusted input, use [`TryFrom<i8>`].
96    // const is used here for better caching later
97    pub const fn new(val: i8) -> Self {
98        match val {
99            -1 => Self::N,
100            1 => Self::P,
101            _ => Self::Z,
102        }
103    }
104
105    /// Return value as i8 for a trit
106    pub const fn value(self) -> i8 {
107        self as i8
108    }
109
110    /// Return the opposite (negated) for a trit
111    #[must_use]
112    pub const fn negate(self) -> Self {
113        match self {
114            Self::N => Self::P,
115            Self::Z => Self::Z,
116            Self::P => Self::N,
117        }
118    }
119
120    /// Return the absolute value (no negatives) for a trit
121    #[must_use]
122    pub const fn abs(self) -> Self {
123        match self {
124            Self::N => Self::P,
125            other => other,
126        }
127    }
128
129    /// Return the incremented value for a trit, wrapping  P -> N.
130    #[must_use]
131    pub const fn inc(self) -> Self {
132        match self {
133            Self::N => Self::Z,
134            Self::Z => Self::P,
135            Self::P => Self::N,
136        }
137    }
138
139    /// Return the decremented value for a trit, wrapping N -> P.
140    #[must_use]
141    pub const fn dec(self) -> Self {
142        match self {
143            Self::N => Self::P,
144            Self::Z => Self::N,
145            Self::P => Self::Z,
146        }
147    }
148
149    /// Return the sign of a trit (returns itself)
150    ///
151    /// It's implemented here, for compatibility with Word27 and other types
152    #[must_use]
153    pub const fn sign(self) -> Self {
154        self
155    }
156
157    /// Return whether a trit is zero (0)
158    pub const fn is_zero(self) -> bool {
159        matches!(self, Self::Z)
160    }
161
162    /// Return whether a trit is positive (+1)
163    pub const fn is_positive(self) -> bool {
164        matches!(self, Self::P)
165    }
166
167    /// Return whether a trit is negative (-1)
168    pub const fn is_negative(self) -> bool {
169        matches!(self, Self::N)
170    }
171
172    /// Tritwise minimum (follows Kleene AND logic).
173    #[must_use]
174    #[inline]
175    pub const fn tmin(self, other: Self) -> Self {
176        TMIN[idx(self) * 3 + idx(other)]
177    }
178
179    /// Tritwise maximum (follows Kleene OR logic).
180    #[must_use]
181    #[inline]
182    pub const fn tmax(self, other: Self) -> Self {
183        TMAX[idx(self) * 3 + idx(other)]
184    }
185
186    /// Tritwise NOT (follows Kleene NOT logic).
187    #[must_use]
188    #[inline]
189    pub const fn tnot(self) -> Self {
190        match self {
191            Self::N => Self::P,
192            Self::Z => Self::Z,
193            Self::P => Self::N,
194        }
195    }
196
197    /// Returns the sign of a trit (returns itself)
198    #[must_use]
199    #[inline]
200    pub const fn clip(self) -> Self {
201        self
202    }
203
204    /// Single-trit addition. Returns (sum, carry).
205    ///
206    /// The carry must be propagated by the caller into the next trit position.
207    #[must_use]
208    #[inline]
209    pub const fn add(self, other: Self) -> (Self, Self) {
210        let i = idx(self) * 3 + idx(other);
211        (ADD_SUM[i], ADD_CARRY[i])
212    }
213
214    /// Consensus: Z if either is Z, P if equal, N if opposite.
215    #[must_use]
216    #[inline]
217    pub const fn consensus(self, other: Self) -> Self {
218        CONSENSUS[idx(self) * 3 + idx(other)]
219    }
220}
221
222impl From<Trit> for i8 {
223    #[inline]
224    fn from(t: Trit) -> Self {
225        t as Self
226    }
227}
228
229impl TryFrom<i8> for Trit {
230    type Error = &'static str;
231
232    fn try_from(val: i8) -> Result<Self, Self::Error> {
233        match val {
234            -1 => Ok(Self::N),
235            0 => Ok(Self::Z),
236            1 => Ok(Self::P),
237            _ => Err("Trit value must be -1, 0, or 1."),
238        }
239    }
240}
241
242impl std::fmt::Display for Trit {
243    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
244        f.write_char(match self {
245            Self::N => 'N',
246            Self::Z => 'Z',
247            Self::P => 'P',
248        })
249    }
250}
251
252// <- Tests start here
253#[cfg(test)]
254mod tests {
255    use super::*;
256
257    // const ratchet
258    const _: Trit = Trit::N.negate();
259    const _: Trit = Trit::P.tmin(Trit::Z);
260    const _: Trit = Trit::N.tmax(Trit::P);
261    const _: (Trit, Trit) = Trit::P.add(Trit::P);
262    const _: () = assert!(Trit::N.negate() as i8 == 1);
263    const _: () = assert!(Trit::P.add(Trit::P).1 as i8 == 1);
264
265    // clip
266    const _: Trit = Trit::N.clip();
267    const _: () = assert!(Trit::P.clip() as i8 == 1);
268    const _: () = assert!(Trit::N.clip() as i8 == -1);
269    const _: () = assert!(Trit::Z.clip() as i8 == 0);
270}