balanced_ternary/
tryte.rs

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