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};
72
73/// Provides helper functions for formatting integers in a given radix.
74///
75/// Used internally to convert decimal numbers into their ternary representation.
76/// - `x`: The number to be formatted.
77/// - `radix`: The base of the numeral system.
78///
79/// Returns a string representation of the number in the specified base.
80#[cfg(feature = "ternary-string")]
81fn format_radix(x: i64, radix: u32) -> String {
82    let mut result = vec![];
83    let sign = x.signum();
84    let mut x = x.unsigned_abs();
85    loop {
86        let m = (x % radix as u64) as u32;
87        x /= radix as u64;
88        result.push(core::char::from_digit(m, radix).unwrap());
89        if x == 0 {
90            break;
91        }
92    }
93    format!(
94        "{}{}",
95        if sign == -1 { "-" } else { "" },
96        result.into_iter().rev().collect::<String>()
97    )
98}
99
100mod digit;
101
102pub use crate::digit::{
103    Digit,
104    Digit::{Neg, Pos, Zero},
105};
106
107/// Converts a character into a `Digit`.
108///
109/// # Arguments
110/// * `from` - A single character (`+`, `0`, or `-`).
111/// * **Panics** if the input character is invalid.
112///
113/// # Returns
114/// * A `Digit` enum corresponding to the character.
115///
116/// # Example
117/// ```
118/// use balanced_ternary::{trit, Digit};
119///
120/// let digit = trit('+');
121/// assert_eq!(digit, Digit::Pos);
122/// ```
123pub const fn trit(from: char) -> Digit {
124    Digit::from_char(from)
125}
126
127/// Converts a string representation of a balanced ternary number into a `Ternary` object.
128///
129/// This function is a convenient shorthand for creating `Ternary` numbers
130/// from string representations. The input string must consist of balanced
131/// ternary characters: `+`, `0`, and `-`.
132///
133/// # Arguments
134///
135/// * `from` - A string slice representing the balanced ternary number.
136/// * **Panics** if an input character is invalid.
137///
138/// # Returns
139///
140/// A `Ternary` object created from the provided string representation.
141///
142/// # Example
143/// ```
144/// use balanced_ternary::{ter, Ternary};
145///
146/// let ternary = ter("+-0+");
147/// assert_eq!(ternary.to_string(), "+-0+");
148/// ```
149#[cfg(feature = "ternary-string")]
150pub fn ter(from: &str) -> Ternary {
151    Ternary::parse(from)
152}
153
154#[cfg(feature = "tryte")]
155/// Creates a `Tryte` object from a string representation of a balanced ternary number.
156/// It contains approximately 9.5 bits of information.
157///
158/// This function first converts the input string representation into a `Ternary` object
159/// using the `ter` function, and then constructs a `Tryte` from that `Ternary`.
160///
161/// # Panics
162///
163/// This function panics if the `Ternary` contains more than 6 digits or if an input character is invalid.
164///
165/// # Arguments
166///
167/// * `from` - A string slice representing the balanced ternary number. It must contain
168///   valid balanced ternary characters (`+`, `0`, or `-`) only.
169/// * Panics if an input character is invalid.
170///
171/// # Returns
172///
173/// A `Tryte` object constructed from the provided balanced ternary string.
174///
175/// # Example
176/// ```
177/// use balanced_ternary::{tryte, Tryte};
178///
179/// let tryte_value = tryte("+0+0");
180/// assert_eq!(tryte_value.to_string(), "00+0+0");
181/// ```
182pub fn tryte(from: &str) -> Tryte {
183    Tryte::from_ternary(&ter(from))
184}
185
186/// Creates a `DataTernary` object from a string representation of a balanced ternary number.
187///
188/// This function converts the provided string representation of a balanced ternary number
189/// into a `DataTernary` object. It first parses the input string into a `Ternary`
190/// using the `ter` function, and then constructs the `DataTernary`.
191///
192/// # Arguments
193///
194/// * `from` - A string slice that contains a valid balanced ternary number.
195///   Valid characters are `+`, `0`, and `-`.
196///
197/// # Panics
198///
199/// * Panics if the input contains invalid balanced ternary characters.
200///
201/// # Returns
202///
203/// A `DataTernary` object constructed from the input string.
204///
205/// # Example
206/// ```
207/// use balanced_ternary::{dter, DataTernary};
208///
209/// let data_ternary = dter("+-0-");
210/// assert_eq!(data_ternary.to_string(), "0+-0-");
211/// ```
212#[cfg(feature = "ternary-store")]
213pub fn dter(from: &str) -> DataTernary {
214    DataTernary::from_ternary(ter(from))
215}
216
217/// Represents a balanced ternary number using a sequence of `Digit`s.
218///
219/// Provides functions for creating, parsing, converting, and manipulating balanced ternary numbers.
220#[derive(Debug, Clone, PartialEq, Eq, Hash)]
221#[cfg(feature = "ternary-string")]
222pub struct Ternary {
223    digits: Vec<Digit>,
224}
225
226#[cfg(feature = "ternary-string")]
227impl Ternary {
228    /// Creates a new balanced ternary number from a vector of `Digit`s.
229    pub fn new(digits: Vec<Digit>) -> Ternary {
230        Ternary { digits }
231    }
232
233    /// Returns the number of digits (length) of the balanced ternary number.
234    pub fn log(&self) -> usize {
235        self.digits.len()
236    }
237
238    /// Retrieves a slice containing the digits of the `Ternary`.
239    ///
240    /// # Returns
241    ///
242    /// A slice referencing the digits vec of the `Ternary`.
243    ///
244    /// This function allows access to the raw representation of the
245    /// balanced ternary number as a slice of `Digit` values.
246    pub fn to_digit_slice(&self) -> &[Digit] {
247        self.digits.as_slice()
248    }
249
250    /// Returns a reference to the [Digit] indexed by `index` if it exists.
251    ///
252    /// Digits are indexed **from the right**:
253    /// ```
254    /// use balanced_ternary::Ternary;
255    ///
256    /// // Indexes :
257    /// //                              32
258    /// //                             4||1
259    /// //                            5||||0
260    /// //                            ||||||
261    /// //                            vvvvvv
262    /// let ternary = Ternary::parse("+++--+");
263    /// assert_eq!(ternary.get_digit(1).unwrap().to_char(), '-')
264    /// ```
265    pub fn get_digit(&self, index: usize) -> Option<&Digit> {
266        self.digits.iter().rev().nth(index)
267    }
268
269    /// Parses a string representation of a balanced ternary number into a `Ternary` object.
270    ///
271    /// Each character in the string must be one of `+`, `0`, or `-`.
272    pub fn parse(str: &str) -> Self {
273        let mut repr = Ternary::new(vec![]);
274        for c in str.chars() {
275            repr.digits.push(Digit::from_char(c));
276        }
277        repr
278    }
279
280    /// Converts the `Ternary` object to its integer (decimal) representation.
281    ///
282    /// Calculates the sum of each digit's value multiplied by the appropriate power of 3.
283    pub fn to_dec(&self) -> i64 {
284        let mut dec = 0;
285        for (rank, digit) in self.digits.iter().rev().enumerate() {
286            dec += digit.to_i8() as i64 * 3_i64.pow(rank as u32);
287        }
288        dec
289    }
290
291    /// Creates a balanced ternary number from a decimal integer.
292    ///
293    /// The input number is converted into its balanced ternary representation,
294    /// with digits represented as `Digit`s.
295    pub fn from_dec(dec: i64) -> Self {
296        let sign = dec.signum();
297        let str = format_radix(dec.abs(), 3);
298        let mut carry = 0u8;
299        let mut repr = Ternary::new(vec![]);
300        for digit in str.chars().rev() {
301            let digit = u8::from_str(&digit.to_string()).unwrap() + carry;
302            if digit < 2 {
303                repr.digits.push(Digit::from_i8(digit as i8));
304                carry = 0;
305            } else if digit == 2 {
306                repr.digits.push(Digit::from_i8(-1));
307                carry = 1;
308            } else if digit == 3 {
309                repr.digits.push(Digit::from_i8(0));
310                carry = 1;
311            } else {
312                panic!("Ternary::from_dec(): Invalid digit: {}", digit);
313            }
314        }
315        if carry == 1 {
316            repr.digits.push(Digit::from_i8(1));
317        }
318        repr.digits.reverse();
319        if sign == -1 {
320            -&repr
321        } else {
322            repr
323        }
324    }
325
326    /// Converts the balanced ternary number to its unbalanced representation as a string.
327    ///
328    /// The unbalanced representation treats the digits as standard ternary (0, 1, 2),
329    /// instead of balanced ternary (-1, 0, +1). Negative digits are handled by
330    /// calculating the decimal value of the balanced ternary number and converting
331    /// it back to an unbalanced ternary string.
332    ///
333    /// Returns:
334    /// * `String` - The unbalanced ternary representation of the number, where each
335    /// digit is one of `0`, `1`, or `2`.
336    ///
337    /// Example:
338    /// ```
339    /// use balanced_ternary::Ternary;
340    ///
341    /// let repr = Ternary::parse("+--");
342    /// assert_eq!(repr.to_unbalanced(), "12");
343    /// assert_eq!(repr.to_dec(), 5);
344    /// let repr = Ternary::parse("-++");
345    /// assert_eq!(repr.to_unbalanced(), "-12");
346    /// assert_eq!(repr.to_dec(), -5);
347    /// ```
348    pub fn to_unbalanced(&self) -> String {
349        format_radix(self.to_dec(), 3)
350    }
351
352    /// Parses a string representation of an unbalanced ternary number into a `Ternary` object.
353    ///
354    /// The string must only contain characters valid in the unbalanced ternary numeral system (`0`, `1`, or `2`).
355    /// Each character is directly converted into its decimal value and then interpreted as a balanced ternary number.
356    ///
357    /// # Arguments
358    ///
359    /// * `unbalanced` - A string slice representing the unbalanced ternary number.
360    ///
361    /// # Returns
362    ///
363    /// A `Ternary` object representing the same value as the input string in balanced ternary form.
364    ///
365    /// # Panics
366    ///
367    /// This function will panic if the string is not a valid unbalanced ternary number.
368    /// For instance, if it contains characters other than `0`, `1`, or `2`.
369    ///
370    /// # Examples
371    ///
372    /// ```
373    /// use balanced_ternary::Ternary;
374    ///
375    /// let ternary = Ternary::from_unbalanced("-12");
376    /// assert_eq!(ternary.to_string(), "-++");
377    /// assert_eq!(ternary.to_dec(), -5);
378    /// ```
379    pub fn from_unbalanced(unbalanced: &str) -> Self {
380        Self::from_dec(i64::from_str_radix(unbalanced, 3).unwrap())
381    }
382
383    /// Removes leading `Zero` digits from the `Ternary` number, effectively trimming
384    /// it down to its simplest representation. The resulting `Ternary` number
385    /// will still represent the same value.
386    ///
387    /// # Returns
388    ///
389    /// * `Self` - A new `Ternary` object, trimmed of leading zeros.
390    ///
391    /// # Examples
392    ///
393    /// ```
394    /// use balanced_ternary::{ Neg, Pos, Ternary, Zero};
395    ///
396    /// let ternary = Ternary::new(vec![Zero, Zero, Pos, Neg]);
397    /// let trimmed = ternary.trim();
398    /// assert_eq!(trimmed.to_string(), "+-");
399    /// ```
400    ///
401    /// # Notes
402    ///
403    /// This method does not mutate the original `Ternary` object but returns a new representation.
404    pub fn trim(&self) -> Self {
405        if self.to_dec() == 0 {
406            return Ternary::parse("0");
407        }
408        let mut repr = Ternary::new(vec![]);
409        let mut first_digit = false;
410        for digit in self.digits.iter() {
411            if !first_digit && digit != &Zero {
412                first_digit = true;
413            }
414            if first_digit {
415                repr.digits.push(*digit);
416            }
417        }
418        repr
419    }
420
421    /// Adjusts the representation of the `Ternary` number to have a fixed number of digits.
422    ///
423    /// If the current `Ternary` has fewer digits than the specified `length`, leading zero digits
424    /// will be added to the `Ternary` to match the desired length. If the current `Ternary` has
425    /// more digits than the specified `length`, it will be returned unmodified.
426    ///
427    /// # Arguments
428    ///
429    /// * `length` - The desired length of the `Ternary` number.
430    ///
431    /// # Returns
432    ///
433    /// * `Self` - A new `Ternary` object with the specified fixed length.
434    ///
435    /// # Notes
436    ///
437    /// If `length` is smaller than the existing number of digits, the function does not truncate
438    /// the number but instead returns the original `Ternary` unchanged.
439    ///
440    /// # Examples
441    ///
442    /// ```
443    /// use balanced_ternary::{Ternary, Zero, Pos};
444    ///
445    /// let ternary = Ternary::new(vec![Pos]);
446    /// let fixed = ternary.with_length(5);
447    /// assert_eq!(fixed.to_string(), "0000+");
448    ///
449    /// let fixed = ternary.with_length(1);
450    /// assert_eq!(fixed.to_string(), "+");
451    /// ```
452    pub fn with_length(&self, length: usize) -> Self {
453        if length < self.log() {
454            return self.clone();
455        }
456        let zeroes = vec![Zero; length - self.log()];
457        let mut repr = Ternary::new(vec![]);
458        repr.digits.extend(zeroes);
459        repr.digits.extend(self.digits.iter().cloned());
460        repr
461    }
462
463    /// Converts the `Ternary` number into a string representation by applying a given
464    /// transformation function to each digit of the ternary number.
465    ///
466    /// # Arguments
467    ///
468    /// * `transform` - A function or closure that takes a `Digit` and returns a `char`, representing the digit.
469    ///
470    /// # Returns
471    ///
472    /// A `String`-based representation of the `Ternary` number resulting from
473    /// applying the transformation to its digits.
474    ///
475    /// # Examples
476    ///
477    /// ```
478    /// use balanced_ternary::{Digit, Pos, Neg, Zero, Ternary};
479    ///
480    /// let ternary = Ternary::new(vec![Pos, Zero, Neg]);
481    ///
482    /// let custom_repr = ternary.to_string_repr(Digit::to_char_t);
483    /// assert_eq!(custom_repr, "10T");
484    /// let custom_repr = ternary.to_string_repr(Digit::to_char_theta);
485    /// assert_eq!(custom_repr, "10Θ");
486    /// let custom_repr = ternary.to_string_repr(Digit::to_char);
487    /// assert_eq!(custom_repr, "+0-");
488    /// ```
489    ///
490    /// # Notes
491    ///
492    /// * The function provides flexibility to define custom string representations
493    ///   for the ternary number digits.
494    /// * Call to `Ternary::to_string()` is equivalent to `Ternary::to_string_repr(Digit::to_char)`.
495    pub fn to_string_repr<F: Fn(&Digit) -> char>(&self, transform: F) -> String {
496        let mut str = String::new();
497        for digit in self.digits.iter() {
498            str.push(transform(digit));
499        }
500        str
501    }
502
503    /// Concatenates the current `Ternary` number with another `Ternary` number.
504    ///
505    /// This function appends the digits of the provided `Ternary` object to the digits
506    /// of the current `Ternary` object, creating a new `Ternary` number as the result.
507    ///
508    /// # Arguments
509    ///
510    /// * `other` - A reference to the `Ternary` number to be concatenated to the current one.
511    ///
512    /// # Returns
513    ///
514    /// * `Ternary` - A new `Ternary` object formed by concatenating the digits.
515    ///
516    /// # Examples
517    ///
518    /// ```
519    /// use balanced_ternary::{Ternary, Pos, Zero, Neg};
520    ///
521    /// let ternary1 = Ternary::new(vec![Pos, Zero]);
522    /// let ternary2 = Ternary::new(vec![Neg, Pos]);
523    ///
524    /// let concatenated = ternary1.concat(&ternary2);
525    /// assert_eq!(concatenated.to_string(), "+0-+");
526    /// ```
527    pub fn concat(&self, other: &Ternary) -> Ternary {
528        let mut t = Ternary::new(vec![]);
529        t.digits.extend(self.digits.iter().cloned());
530        t.digits.extend(other.digits.iter().cloned());
531        t
532    }
533}
534
535#[cfg(feature = "ternary-string")]
536impl DigitOperate for Ternary {
537    fn to_digits(&self) -> Vec<Digit> {
538        self.to_digit_slice().to_vec()
539    }
540
541    fn digit(&self, index: usize) -> Option<Digit> {
542        self.get_digit(index).cloned()
543    }
544
545    fn each(&self, f: impl Fn(Digit) -> Digit) -> Self {
546        let mut repr = Ternary::new(vec![]);
547        for digit in self.digits.iter() {
548            repr.digits.push(f(*digit));
549        }
550        repr
551    }
552
553    fn each_with(&self, f: impl Fn(Digit, Digit) -> Digit, other: Digit) -> Self {
554        let mut repr = Ternary::new(vec![]);
555        for digit in self.digits.iter() {
556            repr.digits.push(f(*digit, other));
557        }
558        repr
559    }
560
561    fn each_zip(&self, f: impl Fn(Digit, Digit) -> Digit, other: Self) -> Self {
562        if self.digits.len() < other.digits.len() {
563            return other.each_zip(f, self.clone());
564        }
565        let other = other.with_length(self.digits.len());
566        let mut repr = Ternary::new(vec![]);
567        for (i, digit) in self.digits.iter().rev().enumerate() {
568            let d_other = other.get_digit(i).unwrap();
569            let res = f(*digit, *d_other);
570            repr.digits.push(res);
571        }
572        repr.digits.reverse();
573        repr
574    }
575
576    fn each_zip_carry(
577        &self,
578        f: impl Fn(Digit, Digit, Digit) -> (Digit, Digit),
579        other: Self,
580    ) -> Self {
581        if self.digits.len() < other.digits.len() {
582            return other.each_zip_carry(f, self.clone());
583        }
584        let other = other.with_length(self.digits.len());
585        let mut repr = Ternary::new(vec![]);
586        let mut carry = Zero;
587        for (i, digit) in self.digits.iter().rev().enumerate() {
588            let d_other = other.get_digit(i).unwrap();
589            let (c, res) = f(*digit, *d_other, carry);
590            carry = c;
591            repr.digits.push(res);
592        }
593        repr.digits.reverse();
594        repr
595    }
596}
597
598#[cfg(feature = "ternary-string")]
599impl Display for Ternary {
600    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
601        write!(f, "{}", self.to_string_repr(Digit::to_char))
602    }
603}
604
605#[cfg(feature = "ternary-string")]
606mod operations;
607
608mod conversions;
609
610#[cfg(feature = "ternary-store")]
611mod store;
612
613#[cfg(feature = "ternary-store")]
614pub use crate::store::{Ter40, DataTernary, TritsChunk};
615
616#[cfg(feature = "tryte")]
617mod tryte;
618
619#[cfg(feature = "tryte")]
620pub use crate::tryte::Tryte;
621
622#[cfg(test)]
623#[cfg(feature = "ternary-string")]
624#[test]
625fn test_ternary() {
626    use crate::*;
627
628    let repr5 = Ternary::new(vec![Pos, Neg, Neg]);
629    assert_eq!(repr5.to_dec(), 5);
630    let repr5 = Ternary::from_dec(5);
631    assert_eq!(repr5.to_dec(), 5);
632
633    let repr13 = Ternary::new(vec![Pos, Pos, Pos]);
634    assert_eq!(repr13.to_dec(), 13);
635
636    let repr14 = Ternary::parse("+---");
637    let repr15 = Ternary::parse("+--0");
638    assert_eq!(repr14.to_dec(), 14);
639    assert_eq!(repr15.to_dec(), 15);
640    assert_eq!(repr14.to_string(), "+---");
641    assert_eq!(repr15.to_string(), "+--0");
642
643    let repr120 = Ternary::from_dec(120);
644    assert_eq!(repr120.to_dec(), 120);
645    assert_eq!(repr120.to_string(), "++++0");
646    let repr121 = Ternary::from_dec(121);
647    assert_eq!(repr121.to_dec(), 121);
648    assert_eq!(repr121.to_string(), "+++++");
649
650    let repr_neg_5 = Ternary::parse("-++");
651    assert_eq!(repr_neg_5.to_dec(), -5);
652    assert_eq!(repr_neg_5.to_string(), "-++");
653
654    let repr_neg_5 = Ternary::from_dec(-5);
655    assert_eq!(repr_neg_5.to_dec(), -5);
656    assert_eq!(repr_neg_5.to_string(), "-++");
657
658    let repr_neg_121 = Ternary::from_dec(-121);
659    assert_eq!(repr_neg_121.to_dec(), -121);
660    assert_eq!(repr_neg_121.to_string(), "-----");
661
662    let test = Ternary::from_dec(18887455);
663    assert_eq!(test.to_dec(), 18887455);
664    assert_eq!(test.to_string(), "++00--0--+-0++0+");
665
666    let unbalanced = Ternary::from_unbalanced("12");
667    assert_eq!(unbalanced.to_dec(), 5);
668    assert_eq!(unbalanced.to_string(), "+--");
669
670    let unbalanced = Ternary::from_unbalanced("-12");
671    assert_eq!(unbalanced.to_dec(), -5);
672    assert_eq!(unbalanced.to_string(), "-++");
673
674    let unbalanced = Ternary::from_dec(121);
675    assert_eq!(unbalanced.to_unbalanced(), "11111");
676    assert_eq!(unbalanced.to_string(), "+++++");
677}
678
679#[cfg(test)]
680#[cfg(feature = "ternary-string")]
681#[test]
682fn test_each() {
683    use crate::*;
684    let ternary = Ternary::parse("+0-");
685    assert_eq!(ternary.each(Digit::possibly).to_string(), "++-");
686}
687
688#[cfg(test)]
689#[cfg(feature = "ternary-string")]
690#[test]
691fn test_operations() {
692    fn test_ternary_eq(a: Ternary, b: &str) {
693        let repr = Ternary::parse(b);
694        assert_eq!(a.to_string(), repr.to_string());
695    }
696    fn test_binary_op(a: &Ternary, f: impl Fn(Digit, Digit) -> Digit, b: &Ternary, c: &str) {
697        test_ternary_eq(a.each_zip(f, b.clone()), c);
698    }
699
700    use core::ops::{BitAnd, BitOr, BitXor, Mul, Not};
701
702    let short = Ternary::parse("-0+");
703    let long = Ternary::parse("---000+++");
704    let other = Ternary::parse("-0+-0+-0+");
705
706    // K3
707    test_ternary_eq(short.each(Digit::not), "+0-");
708    test_binary_op(&long, Digit::bitand, &other, "----00-0+");
709    test_binary_op(&long, Digit::bitor, &other, "-0+00++++");
710    test_binary_op(&long, Digit::bitxor, &other, "-0+000+0-");
711    test_binary_op(&long, Digit::k3_equiv, &other, "+0-000-0+");
712    test_binary_op(&long, Digit::k3_imply, &other, "+++00+-0+");
713
714    // HT
715    test_ternary_eq(short.each(Digit::ht_not), "+--");
716    test_binary_op(&long, Digit::ht_imply, &other, "+++-++-0+");
717
718    // BI3
719    test_binary_op(&long, Digit::bi3_and, &other, "-0-000-0+");
720    test_binary_op(&long, Digit::bi3_or, &other, "-0+000+0+");
721    test_binary_op(&long, Digit::bi3_imply, &other, "+0+000-0+");
722
723    // L3
724    test_ternary_eq(short.each(Digit::possibly), "-++");
725    test_ternary_eq(short.each(Digit::necessary), "--+");
726    test_ternary_eq(short.each(Digit::contingently), "-+-");
727    test_binary_op(&long, Digit::l3_imply, &other, "+++0++-0+");
728
729    // PARA / RM3
730    test_binary_op(&long, Digit::rm3_imply, &other, "+++-0+--+");
731    test_binary_op(&long, Digit::para_imply, &other, "+++-0+-0+");
732
733    // Other operations
734    test_ternary_eq(short.each(Digit::post), "0+-");
735    test_ternary_eq(short.each(Digit::pre), "+-0");
736    test_ternary_eq(short.each(Digit::absolute_positive), "+0+");
737    test_ternary_eq(short.each(Digit::positive), "00+");
738    test_ternary_eq(short.each(Digit::not_negative), "0++");
739    test_ternary_eq(short.each(Digit::not_positive), "--0");
740    test_ternary_eq(short.each(Digit::negative), "-00");
741    test_ternary_eq(short.each(Digit::absolute_negative), "-0-");
742
743    test_binary_op(&long, Digit::mul, &other, "+0-000-0+");
744}