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