balanced_ternary/lib.rs
1//! A [balanced ternary](https://en.wikipedia.org/wiki/Balanced_ternary) data structure.
2//!
3//! A `Ternary` object in this module represents a number in the balanced ternary numeral system.
4//! Balanced ternary is a non-standard positional numeral system that uses three digits: {-1, 0, +1}
5//! represented here as `Neg` for -1, `Zero` for 0, and `Pos` for +1. It is useful in some domains
6//! of computer science and mathematics due to its arithmetic properties and representation
7//! symmetry.
8//!
9//! # Data Structures
10//!
11//! - **`Digit` Enum**:
12//! Represents a single digit for balanced ternary values, with possible values:
13//! - `Neg` for -1
14//! - `Zero` for 0
15//! - `Pos` for +1
16//!
17//! - **`Ternary` Struct**:
18//! Represents a balanced ternary number as a collection of `Digit`s.
19//! Provides utility functions for conversion, parsing, and manipulation.
20//!
21//! # Examples
22//!
23//! ## Converting between representations:
24//! ```rust
25//! use balanced_ternary::*;
26//!
27//! let ternary = Ternary::from_dec(5);
28//! assert_eq!(ternary.to_string(), "+--");
29//! assert_eq!(ternary.to_dec(), 5);
30//!
31//! let parsed = Ternary::parse("+--");
32//! assert_eq!(parsed.to_string(), "+--");
33//! assert_eq!(parsed.to_dec(), 5);
34//! ```
35//!
36//! ## Negative numbers:
37//! ```rust
38//! use balanced_ternary::*;
39//!
40//! let neg_five = Ternary::from_dec(-5);
41//! assert_eq!(neg_five.to_string(), "-++");
42//! assert_eq!(neg_five.to_dec(), -5);
43//!
44//! let negated = -&neg_five;
45//! assert_eq!(negated.to_string(), "+--");
46//! assert_eq!(negated.to_dec(), 5);
47//! ```
48//!
49//! ## Larger numbers:
50//! ```rust
51//! use balanced_ternary::*;
52//!
53//! let big = Ternary::from_dec(121);
54//! assert_eq!(big.to_string(), "+++++");
55//! assert_eq!(big.to_dec(), 121);
56//!
57//! let neg_big = Ternary::from_dec(-121);
58//! assert_eq!(neg_big.to_string(), "-----");
59//! assert_eq!(neg_big.to_dec(), -121);
60//! ```
61//!
62//! ## Operations
63//! ```
64//! use balanced_ternary::Ternary;
65//!
66//! let repr9 = Ternary::parse("+00");
67//! let repr4 = Ternary::parse("++");
68//! let repr13 = &repr9 + &repr4;
69//! let repr17 = &repr13 + &repr4;
70//! let repr34 = &repr17 + &repr17;
71//!
72//! assert_eq!(repr13.to_string(), "+++");
73//! assert_eq!(repr17.to_string(), "+-0-");
74//! assert_eq!(repr34.to_string(), "++-+");
75//!
76//! let repr30 = &repr34 - &repr4;
77//! assert_eq!(repr30.to_dec(), 30);
78//! assert_eq!(repr30.to_string(), "+0+0");
79//! ```
80//!
81#![no_std]
82extern crate alloc;
83
84use alloc::string::{String, ToString};
85use alloc::vec::Vec;
86use alloc::{format, vec};
87use core::fmt::{Display, Formatter};
88use core::str::FromStr;
89
90/// Provides helper functions for formatting integers in a given radix.
91///
92/// Used internally to convert decimal numbers into their ternary representation.
93/// - `x`: The number to be formatted.
94/// - `radix`: The base of the numeral system.
95///
96/// Returns a string representation of the number in the specified base.
97fn format_radix(x: i64, radix: u32) -> String {
98 let mut result = vec![];
99 let sign = x.signum();
100 let mut x = x.abs() as u64;
101 loop {
102 let m = (x % radix as u64) as u32;
103 x /= radix as u64;
104 result.push(core::char::from_digit(m, radix).unwrap());
105 if x == 0 {
106 break;
107 }
108 }
109 format!(
110 "{}{}",
111 if sign == -1 { "-" } else { "" },
112 result.into_iter().rev().collect::<String>()
113 )
114}
115
116pub mod digit;
117
118pub use crate::digit::{
119 Digit,
120 Digit::{Neg, Pos, Zero},
121};
122
123/// Converts a character into a `Digit`.
124///
125/// # Arguments
126/// * `from` - A single character (`+`, `0`, or `-`).
127/// * **Panics** if the input character is invalid.
128///
129/// # Returns
130/// * A `Digit` enum corresponding to the character.
131///
132/// # Example
133/// ```
134/// use balanced_ternary::{trit, Digit};
135///
136/// let digit = trit('+');
137/// assert_eq!(digit, Digit::Pos);
138/// ```
139pub fn trit(from: char) -> Digit {
140 Digit::from_char(from)
141}
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/// ```
166pub fn ter(from: &str) -> Ternary {
167 Ternary::parse(from)
168}
169
170#[cfg(feature = "tryte")]
171/// Creates a `Tryte` object from a string representation of a balanced ternary number.
172///
173/// This function first converts the input string representation into a `Ternary` object
174/// using the `ter` function, and then constructs a `Tryte` from that `Ternary`.
175///
176/// # Panics
177///
178/// This function panics if the `Ternary` contains more than 6 digits or if an input character is invalid.
179///
180/// # Arguments
181///
182/// * `from` - A string slice representing the balanced ternary number. It must contain
183/// valid balanced ternary characters (`+`, `0`, or `-`) only.
184/// * Panics if an input character is invalid.
185///
186/// # Returns
187///
188/// A `Tryte` object constructed from the provided balanced ternary string.
189///
190/// # Example
191/// ```
192/// use balanced_ternary::{tryte, Tryte};
193///
194/// let tryte_value = tryte("+0+0");
195/// assert_eq!(tryte_value.to_string(), "00+0+0");
196/// ```
197pub fn tryte(from: &str) -> Tryte {
198 Tryte::from_ternary(&ter(from))
199}
200
201/// Represents a balanced ternary number using a sequence of `Digit`s.
202///
203/// Provides functions for creating, parsing, converting, and manipulating balanced ternary numbers.
204#[derive(Debug, Clone, PartialEq, Eq, Hash)]
205pub struct Ternary {
206 digits: Vec<Digit>,
207}
208
209impl Ternary {
210 /// Creates a new balanced ternary number from a vector of `Digit`s.
211 pub fn new(digits: Vec<Digit>) -> Ternary {
212 Ternary { digits }
213 }
214
215 /// Returns the number of digits (length) of the balanced ternary number.
216 pub fn log(&self) -> usize {
217 self.digits.len()
218 }
219
220 /// Retrieves a slice containing the digits of the `Ternary`.
221 ///
222 /// # Returns
223 ///
224 /// A slice referencing the digits vec of the `Ternary`.
225 ///
226 /// This function allows access to the raw representation of the
227 /// balanced ternary number as a slice of `Digit` values.
228 pub fn to_digit_slice(&self) -> &[Digit] {
229 self.digits.as_slice()
230 }
231
232 /// Returns a reference to the [Digit] indexed by `index` if it exists.
233 ///
234 /// Digits are indexed **from the right**:
235 /// ```
236 /// use balanced_ternary::Ternary;
237 ///
238 /// // Indexes :
239 /// // 32
240 /// // 4||1
241 /// // 5||||0
242 /// // ||||||
243 /// // vvvvvv
244 /// let ternary = Ternary::parse("+++--+");
245 /// assert_eq!(ternary.get_digit(1).unwrap().to_char(), '-')
246 /// ```
247 pub fn get_digit(&self, index: usize) -> Option<&Digit> {
248 self.digits.iter().rev().nth(index)
249 }
250
251 /// Parses a string representation of a balanced ternary number into a `Ternary` object.
252 ///
253 /// Each character in the string must be one of `+`, `0`, or `-`.
254 pub fn parse(str: &str) -> Self {
255 let mut repr = Ternary::new(vec![]);
256 for c in str.chars() {
257 repr.digits.push(Digit::from_char(c));
258 }
259 repr
260 }
261
262 /// Converts the `Ternary` object to its integer (decimal) representation.
263 ///
264 /// Calculates the sum of each digit's value multiplied by the appropriate power of 3.
265 pub fn to_dec(&self) -> i64 {
266 let mut dec = 0;
267 for (rank, digit) in self.digits.iter().rev().enumerate() {
268 dec += digit.to_i8() as i64 * 3_i64.pow(rank as u32);
269 }
270 dec
271 }
272
273 /// Creates a balanced ternary number from a decimal integer.
274 ///
275 /// The input number is converted into its balanced ternary representation,
276 /// with digits represented as `Digit`s.
277 pub fn from_dec(dec: i64) -> Self {
278 let sign = dec.signum();
279 let str = format_radix(dec.abs(), 3);
280 let mut carry = 0u8;
281 let mut repr = Ternary::new(vec![]);
282 for digit in str.chars().rev() {
283 let digit = u8::from_str(&digit.to_string()).unwrap() + carry;
284 if digit < 2 {
285 repr.digits.push(Digit::from_i8(digit as i8));
286 carry = 0;
287 } else if digit == 2 {
288 repr.digits.push(Digit::from_i8(-1));
289 carry = 1;
290 } else if digit == 3 {
291 repr.digits.push(Digit::from_i8(0));
292 carry = 1;
293 } else {
294 panic!("Ternary::from_dec(): Invalid digit: {}", digit);
295 }
296 }
297 if carry == 1 {
298 repr.digits.push(Digit::from_i8(1));
299 }
300 repr.digits.reverse();
301 if sign == -1 {
302 -&repr
303 } else {
304 repr
305 }
306 }
307
308 /// Converts the balanced ternary number to its unbalanced representation as a string.
309 ///
310 /// The unbalanced representation treats the digits as standard ternary (0, 1, 2),
311 /// instead of balanced ternary (-1, 0, +1). Negative digits are handled by
312 /// calculating the decimal value of the balanced ternary number and converting
313 /// it back to an unbalanced ternary string.
314 ///
315 /// Returns:
316 /// * `String` - The unbalanced ternary representation of the number, where each
317 /// digit is one of `0`, `1`, or `2`.
318 ///
319 /// Example:
320 /// ```
321 /// use balanced_ternary::Ternary;
322 ///
323 /// let repr = Ternary::parse("+--");
324 /// assert_eq!(repr.to_unbalanced(), "12");
325 /// assert_eq!(repr.to_dec(), 5);
326 /// let repr = Ternary::parse("-++");
327 /// assert_eq!(repr.to_unbalanced(), "-12");
328 /// assert_eq!(repr.to_dec(), -5);
329 /// ```
330 pub fn to_unbalanced(&self) -> String {
331 format_radix(self.to_dec(), 3)
332 }
333
334 /// Parses a string representation of an unbalanced ternary number into a `Ternary` object.
335 ///
336 /// The string must only contain characters valid in the unbalanced ternary numeral system (`0`, `1`, or `2`).
337 /// Each character is directly converted into its decimal value and then interpreted as a balanced ternary number.
338 ///
339 /// # Arguments
340 ///
341 /// * `unbalanced` - A string slice representing the unbalanced ternary number.
342 ///
343 /// # Returns
344 ///
345 /// A `Ternary` object representing the same value as the input string in balanced ternary form.
346 ///
347 /// # Panics
348 ///
349 /// This function will panic if the string is not a valid unbalanced ternary number.
350 /// For instance, if it contains characters other than `0`, `1`, or `2`.
351 ///
352 /// # Examples
353 ///
354 /// ```
355 /// use balanced_ternary::Ternary;
356 ///
357 /// let ternary = Ternary::from_unbalanced("-12");
358 /// assert_eq!(ternary.to_string(), "-++");
359 /// assert_eq!(ternary.to_dec(), -5);
360 /// ```
361 pub fn from_unbalanced(unbalanced: &str) -> Self {
362 Self::from_dec(i64::from_str_radix(unbalanced, 3).unwrap())
363 }
364}
365
366impl Display for Ternary {
367 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
368 let mut str = String::new();
369 for digit in self.digits.iter() {
370 str.push(digit.to_char());
371 }
372 write!(f, "{}", str)
373 }
374}
375
376pub mod operations;
377
378pub mod conversions;
379
380#[cfg(feature = "tryte")]
381pub mod tryte;
382
383#[cfg(feature = "tryte")]
384pub use crate::tryte::Tryte;
385
386#[cfg(test)]
387#[test]
388fn test_ternary() {
389 use crate::*;
390
391 let repr5 = Ternary::new(vec![Pos, Neg, Neg]);
392 assert_eq!(repr5.to_dec(), 5);
393 let repr5 = Ternary::from_dec(5);
394 assert_eq!(repr5.to_dec(), 5);
395
396 let repr13 = Ternary::new(vec![Pos, Pos, Pos]);
397 assert_eq!(repr13.to_dec(), 13);
398
399 let repr14 = Ternary::parse("+---");
400 let repr15 = Ternary::parse("+--0");
401 assert_eq!(repr14.to_dec(), 14);
402 assert_eq!(repr15.to_dec(), 15);
403 assert_eq!(repr14.to_string(), "+---");
404 assert_eq!(repr15.to_string(), "+--0");
405
406 let repr120 = Ternary::from_dec(120);
407 assert_eq!(repr120.to_dec(), 120);
408 assert_eq!(repr120.to_string(), "++++0");
409 let repr121 = Ternary::from_dec(121);
410 assert_eq!(repr121.to_dec(), 121);
411 assert_eq!(repr121.to_string(), "+++++");
412
413 let repr_neg_5 = Ternary::parse("-++");
414 assert_eq!(repr_neg_5.to_dec(), -5);
415 assert_eq!(repr_neg_5.to_string(), "-++");
416
417 let repr_neg_5 = Ternary::from_dec(-5);
418 assert_eq!(repr_neg_5.to_dec(), -5);
419 assert_eq!(repr_neg_5.to_string(), "-++");
420
421 let repr_neg_121 = Ternary::from_dec(-121);
422 assert_eq!(repr_neg_121.to_dec(), -121);
423 assert_eq!(repr_neg_121.to_string(), "-----");
424
425 let test = Ternary::from_dec(18887455);
426 assert_eq!(test.to_dec(), 18887455);
427 assert_eq!(test.to_string(), "++00--0--+-0++0+");
428
429 let unbalanced = Ternary::from_unbalanced("12");
430 assert_eq!(unbalanced.to_dec(), 5);
431 assert_eq!(unbalanced.to_string(), "+--");
432
433 let unbalanced = Ternary::from_unbalanced("-12");
434 assert_eq!(unbalanced.to_dec(), -5);
435 assert_eq!(unbalanced.to_string(), "-++");
436
437 let unbalanced = Ternary::from_dec(121);
438 assert_eq!(unbalanced.to_unbalanced(), "11111");
439 assert_eq!(unbalanced.to_string(), "+++++");
440}