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