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