#![no_std]
extern crate alloc;
pub mod concepts;
#[cfg(feature = "ternary-string")]
use alloc::{format, string::String, string::ToString, vec, vec::Vec};
use crate::concepts::DigitOperate;
#[cfg(feature = "ternary-string")]
use core::{
fmt::{Display, Formatter},
str::FromStr,
error::Error,
cmp::Ordering,
};
#[cfg(feature = "ternary-string")]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseTernaryError;
#[cfg(feature = "ternary-string")]
impl Display for ParseTernaryError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(f, "invalid character in balanced ternary string")
}
}
#[cfg(feature = "ternary-string")]
impl Error for ParseTernaryError {}
#[cfg(feature = "ternary-string")]
fn format_radix(x: i64, radix: u32) -> String {
let mut result = vec![];
let sign = x.signum();
let mut x = x.unsigned_abs();
loop {
let m = (x % radix as u64) as u32;
x /= radix as u64;
result.push(core::char::from_digit(m, radix).unwrap());
if x == 0 {
break;
}
}
format!(
"{}{}",
if sign == -1 { "-" } else { "" },
result.into_iter().rev().collect::<String>()
)
}
mod digit;
pub use crate::digit::{
Digit,
Digit::{Neg, Pos, Zero},
};
pub const fn trit(from: char) -> Digit {
Digit::from_char(from)
}
#[cfg(feature = "ternary-string")]
pub fn ter(from: &str) -> Ternary {
Ternary::parse(from)
}
#[cfg(feature = "tryte")]
pub fn tryte(from: &str) -> Tryte {
Tryte::from_ternary(&ter(from))
}
#[cfg(feature = "ternary-store")]
pub fn dter(from: &str) -> DataTernary {
DataTernary::from_ternary(ter(from))
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg(feature = "ternary-string")]
pub struct Ternary {
digits: Vec<Digit>,
}
#[cfg(feature = "ternary-string")]
impl Ternary {
pub fn new(digits: Vec<Digit>) -> Ternary {
Ternary { digits }
}
pub fn log(&self) -> usize {
self.digits.len()
}
pub fn to_digit_slice(&self) -> &[Digit] {
self.digits.as_slice()
}
pub fn iter(&self) -> core::slice::Iter<'_, Digit> {
self.digits.iter()
}
pub fn get_digit(&self, index: usize) -> Option<&Digit> {
self.digits.iter().rev().nth(index)
}
pub fn parse(str: &str) -> Self {
let mut repr = Ternary::new(vec![]);
for c in str.chars() {
repr.digits.push(Digit::from_char(c));
}
repr
}
pub fn to_dec(&self) -> i64 {
let mut dec = 0;
for (rank, digit) in self.digits.iter().rev().enumerate() {
dec += digit.to_i8() as i64 * 3_i64.pow(rank as u32);
}
dec
}
pub fn from_dec(dec: i64) -> Self {
let sign = dec.signum();
let str = format_radix(dec.abs(), 3);
let mut carry = 0u8;
let mut repr = Ternary::new(vec![]);
for digit in str.chars().rev() {
let digit = <u8 as FromStr>::from_str(&digit.to_string()).unwrap() + carry;
if digit < 2 {
repr.digits.push(Digit::from_i8(digit as i8));
carry = 0;
} else if digit == 2 {
repr.digits.push(Digit::from_i8(-1));
carry = 1;
} else if digit == 3 {
repr.digits.push(Digit::from_i8(0));
carry = 1;
} else {
panic!("Ternary::from_dec(): Invalid digit: {}", digit);
}
}
if carry == 1 {
repr.digits.push(Digit::from_i8(1));
}
repr.digits.reverse();
if sign == -1 {
-&repr
} else {
repr
}
}
pub fn to_unbalanced(&self) -> String {
format_radix(self.to_dec(), 3)
}
pub fn from_unbalanced(unbalanced: &str) -> Self {
Self::from_dec(i64::from_str_radix(unbalanced, 3).unwrap())
}
pub fn trim(&self) -> Self {
if self.to_dec() == 0 {
return Ternary::parse("0");
}
let mut repr = Ternary::new(vec![]);
let mut first_digit = false;
for digit in self.digits.iter() {
if !first_digit && digit != &Zero {
first_digit = true;
}
if first_digit {
repr.digits.push(*digit);
}
}
repr
}
pub fn with_length(&self, length: usize) -> Self {
if length < self.log() {
return self.clone();
}
let zeroes = vec![Zero; length - self.log()];
let mut repr = Ternary::new(vec![]);
repr.digits.extend(zeroes);
repr.digits.extend(self.digits.iter().cloned());
repr
}
pub fn to_string_repr<F: Fn(&Digit) -> char>(&self, transform: F) -> String {
let mut str = String::new();
for digit in self.digits.iter() {
str.push(transform(digit));
}
str
}
pub fn concat(&self, other: &Ternary) -> Ternary {
let mut t = Ternary::new(vec![]);
t.digits.extend(self.digits.iter().cloned());
t.digits.extend(other.digits.iter().cloned());
t
}
}
#[cfg(feature = "ternary-string")]
impl DigitOperate for Ternary {
fn to_digits(&self) -> Vec<Digit> {
self.to_digit_slice().to_vec()
}
fn digit(&self, index: usize) -> Option<Digit> {
self.get_digit(index).cloned()
}
fn each(&self, f: impl Fn(Digit) -> Digit) -> Self {
let mut repr = Ternary::new(vec![]);
for digit in self.digits.iter() {
repr.digits.push(f(*digit));
}
repr
}
fn each_with(&self, f: impl Fn(Digit, Digit) -> Digit, other: Digit) -> Self {
let mut repr = Ternary::new(vec![]);
for digit in self.digits.iter() {
repr.digits.push(f(*digit, other));
}
repr
}
fn each_zip(&self, f: impl Fn(Digit, Digit) -> Digit, other: Self) -> Self {
if self.digits.len() < other.digits.len() {
return other.each_zip(f, self.clone());
}
let other = other.with_length(self.digits.len());
let mut repr = Ternary::new(vec![]);
for (i, digit) in self.digits.iter().rev().enumerate() {
let d_other = other.get_digit(i).unwrap();
let res = f(*digit, *d_other);
repr.digits.push(res);
}
repr.digits.reverse();
repr
}
fn each_zip_carry(
&self,
f: impl Fn(Digit, Digit, Digit) -> (Digit, Digit),
other: Self,
) -> Self {
if self.digits.len() < other.digits.len() {
return other.each_zip_carry(f, self.clone());
}
let other = other.with_length(self.digits.len());
let mut repr = Ternary::new(vec![]);
let mut carry = Zero;
for (i, digit) in self.digits.iter().rev().enumerate() {
let d_other = other.get_digit(i).unwrap();
let (c, res) = f(*digit, *d_other, carry);
carry = c;
repr.digits.push(res);
}
repr.digits.reverse();
repr
}
}
#[cfg(feature = "ternary-string")]
impl Display for Ternary {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.to_string_repr(Digit::to_char))
}
}
#[cfg(feature = "ternary-string")]
impl FromStr for Ternary {
type Err = ParseTernaryError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.chars().all(|c| matches!(c, '+' | '0' | '-')) {
Ok(Ternary::parse(s))
} else {
Err(ParseTernaryError)
}
}
}
#[cfg(feature = "ternary-string")]
impl Ord for Ternary {
fn cmp(&self, other: &Self) -> Ordering {
self.to_dec().cmp(&other.to_dec())
}
}
#[cfg(feature = "ternary-string")]
impl PartialOrd for Ternary {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[cfg(feature = "ternary-string")]
impl IntoIterator for Ternary {
type Item = Digit;
type IntoIter = alloc::vec::IntoIter<Digit>;
fn into_iter(self) -> Self::IntoIter {
self.digits.into_iter()
}
}
#[cfg(feature = "ternary-string")]
mod operations;
mod conversions;
#[cfg(feature = "ternary-store")]
mod store;
#[cfg(feature = "ternary-store")]
pub use crate::store::{Ter40, DataTernary, TritsChunk};
#[cfg(feature = "tryte")]
mod tryte;
#[cfg(feature = "tryte")]
pub use crate::tryte::Tryte;
#[cfg(test)]
#[cfg(feature = "ternary-string")]
#[test]
fn test_ternary() {
use crate::*;
let repr5 = Ternary::new(vec![Pos, Neg, Neg]);
assert_eq!(repr5.to_dec(), 5);
let repr5 = Ternary::from_dec(5);
assert_eq!(repr5.to_dec(), 5);
let repr13 = Ternary::new(vec![Pos, Pos, Pos]);
assert_eq!(repr13.to_dec(), 13);
let repr14 = Ternary::parse("+---");
let repr15 = Ternary::parse("+--0");
assert_eq!(repr14.to_dec(), 14);
assert_eq!(repr15.to_dec(), 15);
assert_eq!(repr14.to_string(), "+---");
assert_eq!(repr15.to_string(), "+--0");
let repr120 = Ternary::from_dec(120);
assert_eq!(repr120.to_dec(), 120);
assert_eq!(repr120.to_string(), "++++0");
let repr121 = Ternary::from_dec(121);
assert_eq!(repr121.to_dec(), 121);
assert_eq!(repr121.to_string(), "+++++");
let repr_neg_5 = Ternary::parse("-++");
assert_eq!(repr_neg_5.to_dec(), -5);
assert_eq!(repr_neg_5.to_string(), "-++");
let repr_neg_5 = Ternary::from_dec(-5);
assert_eq!(repr_neg_5.to_dec(), -5);
assert_eq!(repr_neg_5.to_string(), "-++");
let repr_neg_121 = Ternary::from_dec(-121);
assert_eq!(repr_neg_121.to_dec(), -121);
assert_eq!(repr_neg_121.to_string(), "-----");
let test = Ternary::from_dec(18887455);
assert_eq!(test.to_dec(), 18887455);
assert_eq!(test.to_string(), "++00--0--+-0++0+");
let unbalanced = Ternary::from_unbalanced("12");
assert_eq!(unbalanced.to_dec(), 5);
assert_eq!(unbalanced.to_string(), "+--");
let unbalanced = Ternary::from_unbalanced("-12");
assert_eq!(unbalanced.to_dec(), -5);
assert_eq!(unbalanced.to_string(), "-++");
let unbalanced = Ternary::from_dec(121);
assert_eq!(unbalanced.to_unbalanced(), "11111");
assert_eq!(unbalanced.to_string(), "+++++");
}
#[cfg(test)]
#[cfg(feature = "ternary-string")]
#[test]
fn test_each() {
use crate::*;
let ternary = Ternary::parse("+0-");
assert_eq!(ternary.each(Digit::possibly).to_string(), "++-");
}
#[cfg(test)]
#[cfg(feature = "ternary-string")]
#[test]
fn test_operations() {
fn test_ternary_eq(a: Ternary, b: &str) {
let repr = Ternary::parse(b);
assert_eq!(a.to_string(), repr.to_string());
}
fn test_binary_op(a: &Ternary, f: impl Fn(Digit, Digit) -> Digit, b: &Ternary, c: &str) {
test_ternary_eq(a.each_zip(f, b.clone()), c);
}
use core::ops::{BitAnd, BitOr, BitXor, Mul, Not};
let short = Ternary::parse("-0+");
let long = Ternary::parse("---000+++");
let other = Ternary::parse("-0+-0+-0+");
test_ternary_eq(short.each(Digit::not), "+0-");
test_binary_op(&long, Digit::bitand, &other, "----00-0+");
test_binary_op(&long, Digit::bitor, &other, "-0+00++++");
test_binary_op(&long, Digit::bitxor, &other, "-0+000+0-");
test_binary_op(&long, Digit::k3_equiv, &other, "+0-000-0+");
test_binary_op(&long, Digit::k3_imply, &other, "+++00+-0+");
test_ternary_eq(short.each(Digit::ht_not), "+--");
test_binary_op(&long, Digit::ht_imply, &other, "+++-++-0+");
test_binary_op(&long, Digit::bi3_and, &other, "-0-000-0+");
test_binary_op(&long, Digit::bi3_or, &other, "-0+000+0+");
test_binary_op(&long, Digit::bi3_imply, &other, "+0+000-0+");
test_ternary_eq(short.each(Digit::possibly), "-++");
test_ternary_eq(short.each(Digit::necessary), "--+");
test_ternary_eq(short.each(Digit::contingently), "-+-");
test_binary_op(&long, Digit::l3_imply, &other, "+++0++-0+");
test_binary_op(&long, Digit::rm3_imply, &other, "+++-0+--+");
test_binary_op(&long, Digit::para_imply, &other, "+++-0+-0+");
test_ternary_eq(short.each(Digit::post), "0+-");
test_ternary_eq(short.each(Digit::pre), "+-0");
test_ternary_eq(short.each(Digit::absolute_positive), "+0+");
test_ternary_eq(short.each(Digit::positive), "00+");
test_ternary_eq(short.each(Digit::not_negative), "0++");
test_ternary_eq(short.each(Digit::not_positive), "--0");
test_ternary_eq(short.each(Digit::negative), "-00");
test_ternary_eq(short.each(Digit::absolute_negative), "-0-");
test_binary_op(&long, Digit::mul, &other, "+0-000-0+");
}
#[cfg(test)]
#[cfg(feature = "ternary-string")]
#[test]
fn test_from_str() {
use core::str::FromStr;
let ternary = Ternary::from_str("+-0").unwrap();
assert_eq!(ternary.to_string(), "+-0");
assert!(Ternary::from_str("+-x").is_err());
#[cfg(feature = "tryte")]
{
let tryte = <crate::Tryte>::from_str("+-0").unwrap();
assert_eq!(tryte.to_string(), "000+-0");
assert!(<crate::Tryte>::from_str("+-x").is_err());
}
}
#[cfg(test)]
#[cfg(feature = "ternary-string")]
#[test]
fn test_ordering() {
use crate::ter;
assert!(ter("-+") < ter("0"));
assert!(ter("0") < ter("++"));
}
#[cfg(test)]
#[cfg(feature = "ternary-string")]
#[test]
fn test_ordering_additional() {
use crate::ter;
assert!(ter("--") < ter("-0"));
assert!(ter("-0") < ter("-"));
assert!(ter("+") < ter("+-"));
assert!(ter("+-") < ter("++"));
let mut values = vec![ter("+"), ter("--"), ter("+-"), ter("-"), ter("0"), ter("-0"), ter("++")];
values.sort();
let expected = vec![ter("--"), ter("-0"), ter("-"), ter("0"), ter("+"), ter("+-"), ter("++")];
assert_eq!(values, expected);
}
#[cfg(test)]
#[cfg(feature = "ternary-string")]
#[test]
fn test_iterators() {
use crate::*;
let ternary = Ternary::parse("+0-");
let expected = vec![Pos, Zero, Neg];
assert_eq!(ternary.iter().cloned().collect::<Vec<_>>(), expected);
let collected: Vec<Digit> = Ternary::parse("+0-").into_iter().collect();
assert_eq!(collected, expected);
}