balanced_ternary/
tryte.rs

1use crate::{
2    Digit,
3    Digit::{Neg, Pos, Zero},
4    Ternary,
5};
6use alloc::string::{String, ToString};
7use core::fmt::{Display, Formatter};
8use core::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg as StdNeg, Not, Sub};
9
10/// The `Tryte<S>` struct represents a Copy type balanced ternary number with exactly S digits (6 by default).
11/// Each digit in a balanced ternary system can have one of three values: -1, 0, or 1.
12///
13/// A [Tryte<6>] can holds value between `-364` and `+364`.
14///
15/// The underlying representation of the number is an array of SIZE `Digit` values.
16/// This struct provides conversion methods to and from other formats.
17///
18/// # Default SIZE
19///
20/// `SIZE` is 6 by default (the size of a tryte in a Setun computer).
21///
22/// > **6 trits ~= 9.505 bits**
23///
24/// > `-364` to `364`
25///
26/// # Warning
27///
28/// Because arithmetic operations are performed in with 64 bits integers, `SIZE` cannot be > 40.
29///
30/// > **40 trits ~= 63,398 bits**
31/// >
32/// > `-6 078 832 729 528 464 400` to `6 078 832 729 528 464 400`
33///
34#[derive(Clone, PartialEq, Eq, Hash, Debug, Copy)]
35pub struct Tryte<const SIZE: usize = 6> {
36    /// The raw representation of the `Tryte` as SIZE ternary digits.
37    raw: [Digit; SIZE],
38}
39
40impl<const SIZE: usize> Tryte<SIZE> {
41    /// `++...++`
42    pub const MAX: Self = Self::new([Pos; SIZE]);
43    /// `--...--`
44    pub const MIN: Self = Self::new([Neg; SIZE]);
45    /// `00...00`
46    pub const ZERO: Self = Self::new([Zero; SIZE]);
47
48    /// Creates a new `Tryte` instance from a given array of `Digit`s.
49    ///
50    /// # Arguments
51    ///
52    /// * `raw` - An array of exactly SIZE `Digit` values representing the balanced ternary digits.
53    ///
54    /// # Returns
55    ///
56    /// A new `Tryte` instance with the specified balanced ternary digits.
57    ///
58    /// # Panics
59    ///
60    /// Panic if `SIZE > 40` as 41 trits would be too much information for 64 bits.
61    ///
62    /// # Examples
63    ///
64    /// ```
65    /// use balanced_ternary::{Tryte, Digit::{Pos, Zero, Neg}};
66    ///
67    /// let digits = [Pos, Zero, Neg, Zero, Pos, Neg];
68    /// let tryte = Tryte::new(digits);
69    /// assert_eq!(tryte.to_digit_slice(), &digits);
70    /// ```
71    pub const fn new(digits: [Digit; SIZE]) -> Self {
72        if SIZE > 40 {
73            panic!("Cannot construct a Tryte with more than 40 digits (~63.5 bits).")
74        }
75        Self { raw: digits }
76    }
77
78    /// Converts the `Tryte` into its `Ternary` representation.
79    ///
80    /// # Returns
81    ///
82    /// A `Ternary` object representing the same balanced ternary number.
83    pub fn to_ternary(&self) -> Ternary {
84        Ternary::new(self.raw.to_vec())
85    }
86
87    /// Retrieves a slice containing the digits of the `Tryte`.
88    ///
89    /// # Returns
90    ///
91    /// A slice referencing the six-digit array of the `Tryte`.
92    ///
93    /// This function allows access to the raw representation of the
94    /// balanced ternary number as a slice of `Digit` values.
95    pub const fn to_digit_slice(&self) -> &[Digit] {
96        &self.raw
97    }
98
99    /// Creates a `Tryte` from the given `Ternary`.
100    ///
101    /// # Arguments
102    ///
103    /// * `v` - A reference to a `Ternary` object.
104    ///
105    /// # Panics
106    ///
107    /// This function panics if the `Ternary` contains more than SIZE digits.
108    pub fn from_ternary(v: &Ternary) -> Self {
109        if v.log() > SIZE {
110            panic!(
111                "Cannot convert a Ternary with more than {} digits to a Tryte<{}>.",
112                SIZE, SIZE
113            );
114        }
115        let mut digits = [Zero; SIZE];
116        for (i, d) in v.digits.iter().rev().enumerate() {
117            digits[SIZE - 1 - i] = *d;
118        }
119        Self::new(digits)
120    }
121
122    /// Converts the `Tryte` into a signed 64-bit integer.
123    ///
124    /// # Returns
125    ///
126    /// A `i64` representing the decimal value of the `Tryte`.
127    pub fn to_i64(&self) -> i64 {
128        self.to_ternary().to_dec()
129    }
130
131    /// Creates a `Tryte` from a signed 64-bit integer.
132    ///
133    /// # Arguments
134    ///
135    /// * `v` - A signed 64-bit integer.
136    ///
137    /// # Returns
138    ///
139    /// A `Tryte` representing the equivalent ternary number.
140    pub fn from_i64(v: i64) -> Self {
141        Self::from_ternary(&Ternary::from_dec(v))
142    }
143
144    /// Retrieves the digit at the specified index in the `Tryte`.
145    ///
146    /// # Arguments
147    ///
148    /// * `index` - The index of the digit to retrieve (0-based, right-to-left).
149    ///
150    /// # Returns
151    ///
152    /// The `Digit` at the specified index.
153    ///
154    /// # Panics
155    ///
156    /// This function panics if the index is greater than 5.
157    pub fn digit(&self, index: usize) -> Digit {
158        if index > SIZE - 1 {
159            panic!(
160                "Cannot access a digit at index {}. Tryte<{}> has only {} digits.",
161                index, SIZE, SIZE
162            );
163        }
164        *self.raw.iter().rev().nth(index).unwrap()
165    }
166
167    /// See [Ternary::each].
168    pub fn each(&self, f: impl Fn(Digit) -> Digit) -> Self {
169        Self::from_ternary(&self.to_ternary().each(f))
170    }
171
172    /// See [Ternary::each_with].
173    pub fn each_with(&self, f: impl Fn(Digit, Digit) -> Digit, with: Digit) -> Self {
174        Self::from_ternary(&self.to_ternary().each_with(f, with))
175    }
176
177    /// See [Ternary::each_zip].
178    pub fn each_zip(&self, f: impl Fn(Digit, Digit) -> Digit, other: Self) -> Self {
179        Self::from_ternary(&self.to_ternary().each_zip(f, other.to_ternary()))
180    }
181
182    /// See [Ternary::each_zip_carry].
183    pub fn each_zip_carry(
184        &self,
185        f: impl Fn(Digit, Digit, Digit) -> (Digit, Digit),
186        other: Self,
187    ) -> Self {
188        Self::from_ternary(&self.to_ternary().each_zip_carry(f, other.to_ternary()))
189    }
190}
191
192impl<const SIZE: usize> Display for Tryte<SIZE> {
193    /// Formats the `Tryte` for display.
194    ///
195    /// The `Tryte` is displayed in its balanced ternary representation
196    /// as a SIZE-character string.
197    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
198        write!(f, "{:01$}", self.to_ternary().to_string(), SIZE)
199    }
200}
201
202impl<const SIZE: usize> StdNeg for Tryte<SIZE> {
203    type Output = Tryte<SIZE>;
204    fn neg(self) -> Self::Output {
205        Self::from_ternary(&-&self.to_ternary())
206    }
207}
208
209impl<const SIZE: usize> Add for Tryte<SIZE> {
210    type Output = Tryte<SIZE>;
211
212    fn add(self, rhs: Self) -> Self::Output {
213        Self::from_ternary(&(&self.to_ternary() + &rhs.to_ternary()))
214    }
215}
216
217impl<const SIZE: usize> Sub for Tryte<SIZE> {
218    type Output = Tryte<SIZE>;
219
220    fn sub(self, rhs: Self) -> Self::Output {
221        Self::from_ternary(&(&self.to_ternary() - &rhs.to_ternary()))
222    }
223}
224
225impl<const SIZE: usize> Mul for Tryte<SIZE> {
226    type Output = Tryte<SIZE>;
227
228    fn mul(self, rhs: Self) -> Self::Output {
229        Self::from_ternary(&(&self.to_ternary() * &rhs.to_ternary()))
230    }
231}
232
233impl<const SIZE: usize> Div for Tryte<SIZE> {
234    type Output = Tryte<SIZE>;
235
236    fn div(self, rhs: Self) -> Self::Output {
237        Self::from_ternary(&(&self.to_ternary() / &rhs.to_ternary()))
238    }
239}
240
241impl<const SIZE: usize> BitAnd for Tryte<SIZE> {
242    type Output = Tryte<SIZE>;
243    fn bitand(self, rhs: Self) -> Self::Output {
244        Self::from_ternary(&(&self.to_ternary() & &rhs.to_ternary()))
245    }
246}
247
248impl<const SIZE: usize> BitOr for Tryte<SIZE> {
249    type Output = Tryte<SIZE>;
250    fn bitor(self, rhs: Self) -> Self::Output {
251        Self::from_ternary(&(&self.to_ternary() | &rhs.to_ternary()))
252    }
253}
254
255impl<const SIZE: usize> BitXor for Tryte<SIZE> {
256    type Output = Tryte<SIZE>;
257    fn bitxor(self, rhs: Self) -> Self::Output {
258        Self::from_ternary(&(&self.to_ternary() ^ &rhs.to_ternary()))
259    }
260}
261
262impl<const SIZE: usize> Not for Tryte<SIZE> {
263    type Output = Tryte<SIZE>;
264    fn not(self) -> Self::Output {
265        -self
266    }
267}
268
269impl<const SIZE: usize> From<Ternary> for Tryte<SIZE> {
270    fn from(value: Ternary) -> Self {
271        Tryte::from_ternary(&value)
272    }
273}
274
275impl<const SIZE: usize> From<Tryte<SIZE>> for Ternary {
276    fn from(value: Tryte<SIZE>) -> Self {
277        value.to_ternary()
278    }
279}
280
281impl<const SIZE: usize> From<&str> for Tryte<SIZE> {
282    fn from(value: &str) -> Self {
283        Self::from_ternary(&Ternary::parse(value))
284    }
285}
286
287impl<const SIZE: usize> From<String> for Tryte<SIZE> {
288    fn from(value: String) -> Self {
289        Self::from(value.as_str())
290    }
291}
292
293impl<const SIZE: usize> From<Tryte<SIZE>> for String {
294    fn from(value: Tryte<SIZE>) -> Self {
295        value.to_string()
296    }
297}
298
299impl<const SIZE: usize> From<i64> for Tryte<SIZE> {
300    fn from(value: i64) -> Self {
301        Self::from_i64(value)
302    }
303}
304
305impl<const SIZE: usize> From<Tryte<SIZE>> for i64 {
306    fn from(value: Tryte<SIZE>) -> Self {
307        value.to_i64()
308    }
309}
310
311#[cfg(test)]
312#[test]
313pub fn test_tryte() {
314    let tryte = Tryte::<6>::from_i64(255);
315    assert_eq!(tryte.to_i64(), 255);
316    assert_eq!(tryte.to_string(), "+00++0");
317
318    let tryte = Tryte::<6>::from_i64(16);
319    assert_eq!(tryte.to_i64(), 16);
320    assert_eq!(tryte.to_string(), "00+--+");
321
322    assert_eq!(Tryte::<6>::MAX.to_string(), "++++++");
323    assert_eq!(Tryte::<6>::MAX.to_i64(), 364);
324    assert_eq!(Tryte::<6>::MIN.to_string(), "------");
325    assert_eq!(Tryte::<6>::MIN.to_i64(), -364);
326    assert_eq!(Tryte::<6>::ZERO.to_string(), "000000");
327    assert_eq!(Tryte::<6>::ZERO.to_i64(), 0);
328}