balanced_ternary/
lib.rs

1//! A [balanced ternary](https://en.wikipedia.org/wiki/Balanced_ternary) data structure.
2//!
3//! A `Ternary` object in this module represents a number in the balanced ternary numeral system.
4//! Balanced ternary is a non-standard positional numeral system that uses three digits: {-1, 0, +1}
5//! represented here as `Neg` for -1, `Zero` for 0, and `Pos` for +1. It is useful in some domains
6//! of computer science and mathematics due to its arithmetic properties and representation
7//! symmetry.
8//!
9//! # Data Structures
10//!
11//! - **`Digit` Enum**:
12//!     Represents a single digit for balanced ternary values, with possible values:
13//!     - `Neg` for -1
14//!     - `Zero` for 0
15//!     - `Pos` for +1
16//!
17//! - **`Ternary` Struct**:
18//!     Represents a balanced ternary number as a collection of `Digit`s.
19//!     Provides utility functions for conversion, parsing, and manipulation.
20//!
21//! # Examples
22//!
23//! ## Converting between representations:
24//! ```rust
25//! use balanced_ternary::*;
26//!
27//! let ternary = Ternary::from_dec(5);
28//! assert_eq!(ternary.to_string(), "+--");
29//! assert_eq!(ternary.to_dec(), 5);
30//!
31//! let parsed = Ternary::parse("+--");
32//! assert_eq!(parsed.to_string(), "+--");
33//! assert_eq!(parsed.to_dec(), 5);
34//! ```
35//!
36//! ## Negative numbers:
37//! ```rust
38//! use balanced_ternary::*;
39//!
40//! let neg_five = Ternary::from_dec(-5);
41//! assert_eq!(neg_five.to_string(), "-++");
42//! assert_eq!(neg_five.to_dec(), -5);
43//!
44//! let negated = -&neg_five;
45//! assert_eq!(negated.to_string(), "+--");
46//! assert_eq!(negated.to_dec(), 5);
47//! ```
48//!
49//! ## Larger numbers:
50//! ```rust
51//! use balanced_ternary::*;
52//!
53//! let big = Ternary::from_dec(121);
54//! assert_eq!(big.to_string(), "+++++");
55//! assert_eq!(big.to_dec(), 121);
56//!
57//! let neg_big = Ternary::from_dec(-121);
58//! assert_eq!(neg_big.to_string(), "-----");
59//! assert_eq!(neg_big.to_dec(), -121);
60//! ```
61//!
62//! ## Operations
63//! ```
64//! use balanced_ternary::Ternary;
65//!
66//! let repr9 = Ternary::parse("+00");
67//! let repr4 = Ternary::parse("++");
68//! let repr13 = &repr9 + &repr4;
69//! let repr17 = &repr13 + &repr4;
70//! let repr34 = &repr17 + &repr17;
71//!
72//! assert_eq!(repr13.to_string(), "+++");
73//! assert_eq!(repr17.to_string(), "+-0-");
74//! assert_eq!(repr34.to_string(), "++-+");
75//!
76//! let repr30 = &repr34 - &repr4;
77//! assert_eq!(repr30.to_dec(), 30);
78//! assert_eq!(repr30.to_string(), "+0+0");
79//! ```
80//!
81#![no_std]
82extern crate alloc;
83
84use alloc::string::{String, ToString};
85use alloc::vec::Vec;
86use alloc::{format, vec};
87use core::fmt::{Display, Formatter};
88use core::str::FromStr;
89
90/// Provides helper functions for formatting integers in a given radix.
91///
92/// Used internally to convert decimal numbers into their ternary representation.
93/// - `x`: The number to be formatted.
94/// - `radix`: The base of the numeral system.
95///
96/// Returns a string representation of the number in the specified base.
97fn format_radix(x: i64, radix: u32) -> String {
98    let mut result = vec![];
99    let sign = x.signum();
100    let mut x = x.unsigned_abs();
101    loop {
102        let m = (x % radix as u64) as u32;
103        x /= radix as u64;
104        result.push(core::char::from_digit(m, radix).unwrap());
105        if x == 0 {
106            break;
107        }
108    }
109    format!(
110        "{}{}",
111        if sign == -1 { "-" } else { "" },
112        result.into_iter().rev().collect::<String>()
113    )
114}
115
116pub mod digit;
117
118pub use crate::digit::{
119    Digit,
120    Digit::{Neg, Pos, Zero},
121};
122
123/// Converts a character into a `Digit`.
124///
125/// # Arguments
126/// * `from` - A single character (`+`, `0`, or `-`).
127/// * **Panics** if the input character is invalid.
128///
129/// # Returns
130/// * A `Digit` enum corresponding to the character.
131///
132/// # Example
133/// ```
134/// use balanced_ternary::{trit, Digit};
135///
136/// let digit = trit('+');
137/// assert_eq!(digit, Digit::Pos);
138/// ```
139pub const fn trit(from: char) -> Digit {
140    Digit::from_char(from)
141}
142
143/// Converts a string representation of a balanced ternary number into a `Ternary` object.
144///
145/// This function is a convenient shorthand for creating `Ternary` numbers
146/// from string representations. The input string must consist of balanced
147/// ternary characters: `+`, `0`, and `-`.
148///
149/// # Arguments
150///
151/// * `from` - A string slice representing the balanced ternary number.
152/// * **Panics** if an input character is invalid.
153///
154/// # Returns
155///
156/// A `Ternary` object created from the provided string representation.
157///
158/// # Example
159/// ```
160/// use balanced_ternary::{ter, Ternary};
161///
162/// let ternary = ter("+-0+");
163/// assert_eq!(ternary.to_string(), "+-0+");
164/// ```
165pub fn ter(from: &str) -> Ternary {
166    Ternary::parse(from)
167}
168
169#[cfg(feature = "tryte")]
170/// Creates a `Tryte` object from a string representation of a balanced ternary number.
171/// It contains approximately 9.5 bits of information.
172///
173/// This function first converts the input string representation into a `Ternary` object
174/// using the `ter` function, and then constructs a `Tryte` from that `Ternary`.
175///
176/// # Panics
177///
178/// This function panics if the `Ternary` contains more than 6 digits or if an input character is invalid.
179///
180/// # Arguments
181///
182/// * `from` - A string slice representing the balanced ternary number. It must contain
183///   valid balanced ternary characters (`+`, `0`, or `-`) only.
184/// * Panics if an input character is invalid.
185///
186/// # Returns
187///
188/// A `Tryte` object constructed from the provided balanced ternary string.
189///
190/// # Example
191/// ```
192/// use balanced_ternary::{tryte, Tryte};
193///
194/// let tryte_value = tryte("+0+0");
195/// assert_eq!(tryte_value.to_string(), "00+0+0");
196/// ```
197pub fn tryte(from: &str) -> Tryte {
198    Tryte::from_ternary(&ter(from))
199}
200
201/// Represents a balanced ternary number using a sequence of `Digit`s.
202///
203/// Provides functions for creating, parsing, converting, and manipulating balanced ternary numbers.
204#[derive(Debug, Clone, PartialEq, Eq, Hash)]
205pub struct Ternary {
206    digits: Vec<Digit>,
207}
208
209impl Ternary {
210    /// Creates a new balanced ternary number from a vector of `Digit`s.
211    pub fn new(digits: Vec<Digit>) -> Ternary {
212        Ternary { digits }
213    }
214
215    /// Returns the number of digits (length) of the balanced ternary number.
216    pub fn log(&self) -> usize {
217        self.digits.len()
218    }
219
220    /// Retrieves a slice containing the digits of the `Ternary`.
221    ///
222    /// # Returns
223    ///
224    /// A slice referencing the digits vec of the `Ternary`.
225    ///
226    /// This function allows access to the raw representation of the
227    /// balanced ternary number as a slice of `Digit` values.
228    pub fn to_digit_slice(&self) -> &[Digit] {
229        self.digits.as_slice()
230    }
231
232    /// Returns a reference to the [Digit] indexed by `index` if it exists.
233    ///
234    /// Digits are indexed **from the right**:
235    /// ```
236    /// use balanced_ternary::Ternary;
237    ///
238    /// // Indexes :
239    /// //                              32
240    /// //                             4||1
241    /// //                            5||||0
242    /// //                            ||||||
243    /// //                            vvvvvv
244    /// let ternary = Ternary::parse("+++--+");
245    /// assert_eq!(ternary.get_digit(1).unwrap().to_char(), '-')
246    /// ```
247    pub fn get_digit(&self, index: usize) -> Option<&Digit> {
248        self.digits.iter().rev().nth(index)
249    }
250
251    /// Parses a string representation of a balanced ternary number into a `Ternary` object.
252    ///
253    /// Each character in the string must be one of `+`, `0`, or `-`.
254    pub fn parse(str: &str) -> Self {
255        let mut repr = Ternary::new(vec![]);
256        for c in str.chars() {
257            repr.digits.push(Digit::from_char(c));
258        }
259        repr
260    }
261
262    /// Converts the `Ternary` object to its integer (decimal) representation.
263    ///
264    /// Calculates the sum of each digit's value multiplied by the appropriate power of 3.
265    pub fn to_dec(&self) -> i64 {
266        let mut dec = 0;
267        for (rank, digit) in self.digits.iter().rev().enumerate() {
268            dec += digit.to_i8() as i64 * 3_i64.pow(rank as u32);
269        }
270        dec
271    }
272
273    /// Creates a balanced ternary number from a decimal integer.
274    ///
275    /// The input number is converted into its balanced ternary representation,
276    /// with digits represented as `Digit`s.
277    pub fn from_dec(dec: i64) -> Self {
278        let sign = dec.signum();
279        let str = format_radix(dec.abs(), 3);
280        let mut carry = 0u8;
281        let mut repr = Ternary::new(vec![]);
282        for digit in str.chars().rev() {
283            let digit = u8::from_str(&digit.to_string()).unwrap() + carry;
284            if digit < 2 {
285                repr.digits.push(Digit::from_i8(digit as i8));
286                carry = 0;
287            } else if digit == 2 {
288                repr.digits.push(Digit::from_i8(-1));
289                carry = 1;
290            } else if digit == 3 {
291                repr.digits.push(Digit::from_i8(0));
292                carry = 1;
293            } else {
294                panic!("Ternary::from_dec(): Invalid digit: {}", digit);
295            }
296        }
297        if carry == 1 {
298            repr.digits.push(Digit::from_i8(1));
299        }
300        repr.digits.reverse();
301        if sign == -1 {
302            -&repr
303        } else {
304            repr
305        }
306    }
307
308    /// Converts the balanced ternary number to its unbalanced representation as a string.
309    ///
310    /// The unbalanced representation treats the digits as standard ternary (0, 1, 2),
311    /// instead of balanced ternary (-1, 0, +1). Negative digits are handled by
312    /// calculating the decimal value of the balanced ternary number and converting
313    /// it back to an unbalanced ternary string.
314    ///
315    /// Returns:
316    /// * `String` - The unbalanced ternary representation of the number, where each
317    /// digit is one of `0`, `1`, or `2`.
318    ///
319    /// Example:
320    /// ```
321    /// use balanced_ternary::Ternary;
322    ///
323    /// let repr = Ternary::parse("+--");
324    /// assert_eq!(repr.to_unbalanced(), "12");
325    /// assert_eq!(repr.to_dec(), 5);
326    /// let repr = Ternary::parse("-++");
327    /// assert_eq!(repr.to_unbalanced(), "-12");
328    /// assert_eq!(repr.to_dec(), -5);
329    /// ```
330    pub fn to_unbalanced(&self) -> String {
331        format_radix(self.to_dec(), 3)
332    }
333
334    /// Parses a string representation of an unbalanced ternary number into a `Ternary` object.
335    ///
336    /// The string must only contain characters valid in the unbalanced ternary numeral system (`0`, `1`, or `2`).
337    /// Each character is directly converted into its decimal value and then interpreted as a balanced ternary number.
338    ///
339    /// # Arguments
340    ///
341    /// * `unbalanced` - A string slice representing the unbalanced ternary number.
342    ///
343    /// # Returns
344    ///
345    /// A `Ternary` object representing the same value as the input string in balanced ternary form.
346    ///
347    /// # Panics
348    ///
349    /// This function will panic if the string is not a valid unbalanced ternary number.
350    /// For instance, if it contains characters other than `0`, `1`, or `2`.
351    ///
352    /// # Examples
353    ///
354    /// ```
355    /// use balanced_ternary::Ternary;
356    ///
357    /// let ternary = Ternary::from_unbalanced("-12");
358    /// assert_eq!(ternary.to_string(), "-++");
359    /// assert_eq!(ternary.to_dec(), -5);
360    /// ```
361    pub fn from_unbalanced(unbalanced: &str) -> Self {
362        Self::from_dec(i64::from_str_radix(unbalanced, 3).unwrap())
363    }
364
365    /// Applies a transformation function to each digit of the balanced ternary number,
366    /// returning a new `Ternary` object with the transformed digits.
367    ///
368    /// This method keeps the order of the digits unchanged while applying the provided
369    /// transformation function `f` to each digit individually.
370    ///
371    /// # Arguments
372    ///
373    /// * `f` - A closure or function that takes a `Digit` and returns a transformed `Digit`.
374    ///
375    /// # Returns
376    ///
377    /// * `Self` - A new `Ternary` object containing the transformed digits.
378    ///
379    /// # Digit transformations
380    ///
381    /// These methods (unary operators) from the [Digit] type can be called directly.
382    ///
383    /// * Returns either `Pos` or `Neg`:
384    ///     * [Digit::possibly]
385    ///     * [Digit::necessary]
386    ///     * [Digit::contingently]
387    ///     * [Digit::ht_not]
388    /// * Returns either `Zero` or `Pos` or `Neg`.
389    ///     * [Digit::pre]
390    ///     * [Digit::post]
391    ///     * `Digit::not`
392    ///     * `Digit::neg`
393    ///     * [Digit::absolute_positive]
394    ///     * [Digit::positive]
395    ///     * [Digit::not_negative]
396    ///     * [Digit::not_positive]
397    ///     * [Digit::negative]
398    ///     * [Digit::absolute_negative]
399    ///
400    /// # Examples
401    /// ```
402    /// use balanced_ternary::{Ternary, Digit};
403    ///
404    /// let orig_ternary = Ternary::parse("+0-");
405    /// let transformed = orig_ternary.each(Digit::necessary);
406    /// assert_eq!(transformed.to_string(), "+--");
407    /// let transformed = orig_ternary.each(Digit::positive);
408    /// assert_eq!(transformed.to_string(), "+00");
409    /// let transformed = orig_ternary.each(Digit::not_negative);
410    /// assert_eq!(transformed.to_string(), "++0");
411    /// let transformed = orig_ternary.each(Digit::absolute_negative);
412    /// assert_eq!(transformed.to_string(), "-0-");
413    /// ```
414    pub fn each(&self, f: impl Fn(Digit) -> Digit) -> Self {
415        let mut repr = Ternary::new(vec![]);
416        for digit in self.digits.iter() {
417            repr.digits.push(f(*digit));
418        }
419        repr
420    }
421
422    /// Applies a transformation function to each digit of the balanced ternary number,
423    /// using an additional parameter for the transformation process, returning a new `Ternary`
424    /// object with the transformed digits.
425    ///
426    /// This method keeps the order of the digits unchanged while applying the provided
427    /// transformation function `f` to each digit individually, along with the provided extra
428    /// `other` digit.
429    ///
430    /// # Arguments
431    ///
432    /// * `f` - A closure or function that takes a `Digit` and an additional `Digit`,
433    ///         and returns a transformed `Digit`.
434    /// * `other` - An additional `Digit` to be passed to the transformation function `f`.
435    ///
436    /// # Returns
437    ///
438    /// * `Self` - A new `Ternary` object containing the transformed digits.
439    ///
440    /// # Digit transformations
441    ///
442    /// These methods (binary operators) from the [Digit] type can be called directly.
443    ///
444    /// * `Digit::mul`
445    /// * `Digit::div`
446    /// * `Digit::bitand` (k3/p3 and)
447    /// * [Digit::bi3_and]
448    /// * `Digit::bitor`  (k3/p3 or)
449    /// * [Digit::bi3_or]
450    /// * `Digit::bitxor` (k3/p3 xor)
451    /// * [Digit::k3_imply]
452    /// * [Digit::k3_equiv]
453    /// * [Digit::bi3_imply]
454    /// * [Digit::l3_imply]
455    /// * [Digit::rm3_imply]
456    /// * [Digit::ht_imply]
457    ///
458    /// # Examples
459    /// ```
460    /// use std::ops::Mul;
461    /// use balanced_ternary::{Ternary, Digit};
462    ///
463    /// let original = Ternary::parse("+-0");
464    /// let transformed = original.each_with(Digit::mul, Digit::Neg);
465    /// assert_eq!(transformed.to_string(), "-+0");
466    /// ```
467    pub fn each_with(&self, f: impl Fn(Digit, Digit) -> Digit, other: Digit) -> Self {
468        let mut repr = Ternary::new(vec![]);
469        for digit in self.digits.iter() {
470            repr.digits.push(f(*digit, other));
471        }
472        repr
473    }
474
475    /// Applies a transformation function to each digit of the balanced ternary number,
476    /// along with a corresponding digit from another `Ternary` number.
477    ///
478    /// This method ensures that the digits of both `Ternary` objects are aligned from the least
479    /// significant to the most significant digit. If the `other` `Ternary` has fewer digits
480    /// than the current one, the process is reversed to handle the shorter `Ternary` consistently.
481    /// The result is a new `Ternary` object where each digit was transformed using the provided function `f`.
482    ///
483    /// # Arguments
484    ///
485    /// * `f` - A closure or function that takes two arguments:
486    ///     * a `Digit` from the current `Ternary`,
487    ///     * a `Digit` from the corresponding position in the `other` `Ternary`.
488    ///     * The function must return a transformed `Digit`.
489    /// * `other` - A `Ternary` object with digits to process alongside the digits of the current object.
490    ///
491    /// # Returns
492    ///
493    /// * `Self` - A new `Ternary` object containing the transformed digits.
494    ///
495    /// # Examples
496    ///
497    /// ```
498    /// use std::ops::Mul;
499    /// use balanced_ternary::{Ternary, Digit};
500    ///
501    /// let ternary1 = Ternary::parse("-+0-+0-+0");
502    /// let ternary2 = Ternary::parse("---000+++");
503    ///
504    /// let result = ternary1.each_zip(Digit::mul, ternary2.clone());
505    /// assert_eq!(result.to_string(), "+-0000-+0");
506    ///
507    /// let result = ternary1.each_zip(Digit::k3_imply, ternary2.clone());
508    /// assert_eq!(result.to_string(), "+-0+00+++");
509    /// let result = ternary1.each_zip(Digit::bi3_imply, ternary2.clone());
510    /// assert_eq!(result.to_string(), "+-0000++0");
511    /// let result = ternary1.each_zip(Digit::ht_imply, ternary2.clone());
512    /// assert_eq!(result.to_string(), "+--+0++++");
513    /// ```
514    pub fn each_zip(&self, f: impl Fn(Digit, Digit) -> Digit, other: Self) -> Self {
515        if self.digits.len() < other.digits.len() {
516            return other.each_zip(f, self.clone());
517        }
518        let mut repr = Ternary::new(vec![]);
519        for (i, digit) in self.digits.iter().rev().enumerate() {
520            let d_other = other.get_digit(i).unwrap();
521            let res = f(*digit, *d_other);
522            repr.digits.push(res);
523        }
524        repr.digits.reverse();
525        repr
526    }
527
528    /// Applies a transformation function to each digit of the balanced ternary number,
529    /// along with a corresponding digit from another `Ternary` number, and a carry digit.
530    ///
531    /// This method processes the digits in reverse order (from the least significant to the most significant),
532    /// keeping their transformed order correct by reversing the result afterward. Each digit from the
533    /// current `Ternary` object is processed with the corresponding digit from another `Ternary` object
534    /// and a carry digit using the provided closure or function `f`.
535    ///
536    /// # Arguments
537    ///
538    /// * `f` - A closure or function that takes three arguments:
539    ///     * a `Digit` from the current `Ternary`,
540    ///     * a `Digit` from the corresponding position in the `other` `Ternary`,
541    ///     * and the current carry `Digit`.
542    ///     * The function must return a tuple containing `(carry: Digit, transformed: Digit)`.
543    /// * `other` - A `Ternary` object with digits to process alongside the digits of the current object.
544    ///
545    /// # Returns
546    ///
547    /// * `Self` - A new `Ternary` object containing the transformed digits.
548    ///
549    /// # Notes
550    ///
551    /// The carry digit is initially `Zero` and is passed between each step of the transformation process.
552    /// If the `other` `Ternary` has fewer digits than the current one, the missing digits in `other`
553    /// are treated as `Zero`.
554    ///
555    /// # Examples
556    ///
557    /// ```
558    /// use balanced_ternary::{Digit, Ternary};
559    ///
560    /// let ternary1 = Ternary::parse("+-0");
561    /// let ternary2 = Ternary::parse("-+0");
562    ///
563    /// // Transformation function that adds digits with a carry digit
564    /// let combine = |d1: Digit, d2: Digit, carry: Digit| -> (Digit, Digit) {
565    ///     // Simple example operation: this just illustrates transforming with carry.
566    ///     // Replace with meaningful logic as needed for your application.
567    ///     let sum = d1.to_i8() + d2.to_i8() + carry.to_i8();
568    ///     (Digit::from_i8(sum / 3), Digit::from_i8(sum % 3))
569    /// };
570    ///
571    /// let result = ternary1.each_zip_carry(combine, ternary2.clone()).trim();
572    /// assert_eq!(result.to_string(), (&ternary1 + &ternary2).to_string());
573    /// ```
574    pub fn each_zip_carry(
575        &self,
576        f: impl Fn(Digit, Digit, Digit) -> (Digit, Digit),
577        other: Self,
578    ) -> Self {
579        if self.digits.len() < other.digits.len() {
580            return other.each_zip_carry(f, self.clone());
581        }
582        let mut repr = Ternary::new(vec![]);
583        let mut carry = Zero;
584        for (i, digit) in self.digits.iter().rev().enumerate() {
585            let d_other = other.get_digit(i).unwrap();
586            let (c, res) = f(*digit, *d_other, carry);
587            carry = c;
588            repr.digits.push(res);
589        }
590        repr.digits.reverse();
591        repr
592    }
593
594    /// Removes leading `Zero` digits from the `Ternary` number, effectively trimming
595    /// it down to its simplest representation. The resulting `Ternary` number
596    /// will still represent the same value.
597    ///
598    /// # Returns
599    ///
600    /// * `Self` - A new `Ternary` object, trimmed of leading zeros.
601    ///
602    /// # Examples
603    ///
604    /// ```
605    /// use balanced_ternary::{ Neg, Pos, Ternary, Zero};
606    ///
607    /// let ternary = Ternary::new(vec![Zero, Zero, Pos, Neg]);
608    /// let trimmed = ternary.trim();
609    /// assert_eq!(trimmed.to_string(), "+-");
610    /// ```
611    ///
612    /// # Notes
613    ///
614    /// This method does not mutate the original `Ternary` object but returns a new representation.
615    pub fn trim(&self) -> Self {
616        if self.to_dec() == 0 {
617            return Ternary::parse("0");
618        }
619        let mut repr = Ternary::new(vec![]);
620        let mut first_digit = false;
621        for digit in self.digits.iter() {
622            if !first_digit && digit != &Zero {
623                first_digit = true;
624            }
625            if first_digit {
626                repr.digits.push(*digit);
627            }
628        }
629        repr
630    }
631
632    /// Adjusts the representation of the `Ternary` number to have a fixed number of digits.
633    ///
634    /// If the current `Ternary` has fewer digits than the specified `length`, leading zero digits
635    /// will be added to the `Ternary` to match the desired length. If the current `Ternary` has
636    /// more digits than the specified `length`, it will be returned unmodified.
637    ///
638    /// # Arguments
639    ///
640    /// * `length` - The desired length of the `Ternary` number.
641    ///
642    /// # Returns
643    ///
644    /// * `Self` - A new `Ternary` object with the specified fixed length.
645    ///
646    /// # Notes
647    ///
648    /// If `length` is smaller than the existing number of digits, the function does not truncate
649    /// the number but instead returns the original `Ternary` unchanged.
650    ///
651    /// # Examples
652    ///
653    /// ```
654    /// use balanced_ternary::{Ternary, Zero, Pos};
655    ///
656    /// let ternary = Ternary::new(vec![Pos]);
657    /// let fixed = ternary.with_length(5);
658    /// assert_eq!(fixed.to_string(), "0000+");
659    ///
660    /// let fixed = ternary.with_length(1);
661    /// assert_eq!(fixed.to_string(), "+");
662    /// ```
663    pub fn with_length(&self, length: usize) -> Self {
664        if length < self.log() {
665            return self.clone();
666        }
667        let zeroes = vec![Zero; length - self.log()];
668        let mut repr = Ternary::new(vec![]);
669        repr.digits.extend(zeroes);
670        repr.digits.extend(self.digits.iter().cloned());
671        repr
672    }
673
674    /// Converts the `Ternary` number into a string representation by applying a given
675    /// transformation function to each digit of the ternary number.
676    ///
677    /// # Arguments
678    ///
679    /// * `transform` - A function or closure that takes a `Digit` and returns a `char`, representing the digit.
680    ///
681    /// # Returns
682    ///
683    /// A `String`-based representation of the `Ternary` number resulting from
684    /// applying the transformation to its digits.
685    ///
686    /// # Examples
687    ///
688    /// ```
689    /// use balanced_ternary::{Digit, Pos, Neg, Zero, Ternary};
690    ///
691    /// let ternary = Ternary::new(vec![Pos, Zero, Neg]);
692    ///
693    /// let custom_repr = ternary.to_string_repr(Digit::to_char_t);
694    /// assert_eq!(custom_repr, "10T");
695    /// let custom_repr = ternary.to_string_repr(Digit::to_char_theta);
696    /// assert_eq!(custom_repr, "10Θ");
697    /// let custom_repr = ternary.to_string_repr(Digit::to_char);
698    /// assert_eq!(custom_repr, "+0-");
699    /// ```
700    ///
701    /// # Notes
702    ///
703    /// * The function provides flexibility to define custom string representations
704    ///   for the ternary number digits.
705    /// * Call to `Ternary::to_string()` is equivalent to `Ternary::to_string_repr(Digit::to_char)`.
706    pub fn to_string_repr<F: Fn(&Digit) -> char>(&self, transform: F) -> String {
707        let mut str = String::new();
708        for digit in self.digits.iter() {
709            str.push(transform(digit));
710        }
711        str
712    }
713}
714
715impl Display for Ternary {
716    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
717        write!(f, "{}", self.to_string_repr(Digit::to_char))
718    }
719}
720
721pub mod operations;
722
723pub mod conversions;
724
725#[cfg(feature = "tryte")]
726pub mod tryte;
727
728#[cfg(feature = "tryte")]
729pub use crate::tryte::Tryte;
730
731#[cfg(test)]
732#[test]
733fn test_ternary() {
734    use crate::*;
735
736    let repr5 = Ternary::new(vec![Pos, Neg, Neg]);
737    assert_eq!(repr5.to_dec(), 5);
738    let repr5 = Ternary::from_dec(5);
739    assert_eq!(repr5.to_dec(), 5);
740
741    let repr13 = Ternary::new(vec![Pos, Pos, Pos]);
742    assert_eq!(repr13.to_dec(), 13);
743
744    let repr14 = Ternary::parse("+---");
745    let repr15 = Ternary::parse("+--0");
746    assert_eq!(repr14.to_dec(), 14);
747    assert_eq!(repr15.to_dec(), 15);
748    assert_eq!(repr14.to_string(), "+---");
749    assert_eq!(repr15.to_string(), "+--0");
750
751    let repr120 = Ternary::from_dec(120);
752    assert_eq!(repr120.to_dec(), 120);
753    assert_eq!(repr120.to_string(), "++++0");
754    let repr121 = Ternary::from_dec(121);
755    assert_eq!(repr121.to_dec(), 121);
756    assert_eq!(repr121.to_string(), "+++++");
757
758    let repr_neg_5 = Ternary::parse("-++");
759    assert_eq!(repr_neg_5.to_dec(), -5);
760    assert_eq!(repr_neg_5.to_string(), "-++");
761
762    let repr_neg_5 = Ternary::from_dec(-5);
763    assert_eq!(repr_neg_5.to_dec(), -5);
764    assert_eq!(repr_neg_5.to_string(), "-++");
765
766    let repr_neg_121 = Ternary::from_dec(-121);
767    assert_eq!(repr_neg_121.to_dec(), -121);
768    assert_eq!(repr_neg_121.to_string(), "-----");
769
770    let test = Ternary::from_dec(18887455);
771    assert_eq!(test.to_dec(), 18887455);
772    assert_eq!(test.to_string(), "++00--0--+-0++0+");
773
774    let unbalanced = Ternary::from_unbalanced("12");
775    assert_eq!(unbalanced.to_dec(), 5);
776    assert_eq!(unbalanced.to_string(), "+--");
777
778    let unbalanced = Ternary::from_unbalanced("-12");
779    assert_eq!(unbalanced.to_dec(), -5);
780    assert_eq!(unbalanced.to_string(), "-++");
781
782    let unbalanced = Ternary::from_dec(121);
783    assert_eq!(unbalanced.to_unbalanced(), "11111");
784    assert_eq!(unbalanced.to_string(), "+++++");
785}
786
787#[cfg(test)]
788#[test]
789fn test_operations() {
790    fn test_ternary_eq(a: Ternary, b: &str) {
791        let repr = Ternary::parse(b);
792        assert_eq!(a.to_string(), repr.to_string());
793    }
794    fn test_binary_op(a: &Ternary, f: impl Fn(Digit, Digit) -> Digit, b: &Ternary, c: &str) {
795        test_ternary_eq(a.each_zip(f, b.clone()), c);
796    }
797
798    use core::ops::{BitAnd, BitOr, BitXor, Mul, Not};
799
800    let short = Ternary::parse("-0+");
801    let long = Ternary::parse("---000+++");
802    let other = Ternary::parse("-0+-0+-0+");
803
804    // K3
805    test_ternary_eq(short.each(Digit::not), "+0-");
806    test_binary_op(&long, Digit::bitand, &other, "----00-0+");
807    test_binary_op(&long, Digit::bitor, &other, "-0+00++++");
808    test_binary_op(&long, Digit::bitxor, &other, "-0+000+0-");
809    test_binary_op(&long, Digit::k3_equiv, &other, "+0-000-0+");
810    test_binary_op(&long, Digit::k3_imply, &other, "+++00+-0+");
811
812    // HT
813    test_ternary_eq(short.each(Digit::ht_not), "+--");
814    test_binary_op(&long, Digit::ht_imply, &other, "+++-++-0+");
815
816    // BI3
817    test_binary_op(&long, Digit::bi3_and, &other, "-0-000-0+");
818    test_binary_op(&long, Digit::bi3_or, &other, "-0+000+0+");
819    test_binary_op(&long, Digit::bi3_imply, &other, "+0+000-0+");
820
821    // L3
822    test_ternary_eq(short.each(Digit::possibly), "-++");
823    test_ternary_eq(short.each(Digit::necessary), "--+");
824    test_ternary_eq(short.each(Digit::contingently), "-+-");
825    test_binary_op(&long, Digit::l3_imply, &other, "+++0++-0+");
826
827    // PARA / RM3
828    test_binary_op(&long, Digit::rm3_imply, &other, "+++-0+--+");
829    test_binary_op(&long, Digit::para_imply, &other, "+++-0+-0+");
830
831    // Other operations
832    test_ternary_eq(short.each(Digit::post), "0+-");
833    test_ternary_eq(short.each(Digit::pre), "+-0");
834    test_ternary_eq(short.each(Digit::absolute_positive), "+0+");
835    test_ternary_eq(short.each(Digit::positive), "00+");
836    test_ternary_eq(short.each(Digit::not_negative), "0++");
837    test_ternary_eq(short.each(Digit::not_positive), "--0");
838    test_ternary_eq(short.each(Digit::negative), "-00");
839    test_ternary_eq(short.each(Digit::absolute_negative), "-0-");
840
841    test_binary_op(&long, Digit::mul, &other, "+0-000-0+");
842}