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}