balanced_ternary/
tryte.rs

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