crypto/encoding/ternary/
convert.rs

1// Copyright 2020-2021 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use core::{cmp::PartialOrd, convert::TryFrom};
5
6use num_traits::{AsPrimitive, CheckedAdd, CheckedSub, FromPrimitive, Num, Signed};
7
8use crate::encoding::ternary::{Btrit, RawEncoding, RawEncodingBuf, Trit, TritBuf, Trits, Utrit};
9
10/// An error that may be produced during numeric conversion.
11#[derive(Debug, Eq, PartialEq)]
12pub enum Error {
13    /// The trit slice didn't contain enough trits to be considered a numeric value.
14    Empty,
15    /// An overflow occurred during a numeric operation.
16    Overflow,
17    /// An underflow occurred during a numeric operation.
18    Underflow,
19}
20
21// TODO: Find a way to implement this without conflicting impls
22// impl<I, T: RawEncodingBuf> From<I> for TritBuf<T>
23// where
24//     T::Slice: RawEncoding<Trit = Btrit>,
25//     I: AsPrimitive<i8> + FromPrimitive + Signed,
26// {
27//     fn from(x: I) -> Self {
28//         signed_int_trits(x).collect()
29//     }
30// }
31
32macro_rules! signed_try_from_trits {
33    ($int:ident) => {
34        impl<'a, T: RawEncoding<Trit = Btrit> + ?Sized> TryFrom<&'a Trits<T>> for $int {
35            type Error = Error;
36            fn try_from(trits: &'a Trits<T>) -> Result<Self, Self::Error> {
37                trits_to_int(trits)
38            }
39        }
40
41        impl<T: RawEncodingBuf> From<$int> for TritBuf<T>
42        where
43            T::Slice: RawEncoding<Trit = Btrit>,
44        {
45            fn from(x: $int) -> Self {
46                signed_int_trits(x).collect()
47            }
48        }
49    };
50}
51
52// We have to implement manually due to Rust's orphan rules :(
53// This macro accepts anything that implements:
54// `Clone + CheckedAdd + Signed + AsPrimitive<i8> + FromPrimitive`
55#[cfg(has_i128)]
56signed_try_from_trits!(i128);
57signed_try_from_trits!(i64);
58signed_try_from_trits!(i32);
59signed_try_from_trits!(i16);
60signed_try_from_trits!(i8);
61
62macro_rules! unsigned_try_from_trits {
63    ($int:ident) => {
64        impl<'a, T: RawEncoding<Trit = Utrit> + ?Sized> TryFrom<&'a Trits<T>> for $int {
65            type Error = Error;
66            fn try_from(trits: &'a Trits<T>) -> Result<Self, Self::Error> {
67                trits_to_int(trits)
68            }
69        }
70
71        impl<T: RawEncodingBuf> From<$int> for TritBuf<T>
72        where
73            T::Slice: RawEncoding<Trit = Utrit>,
74        {
75            fn from(x: $int) -> Self {
76                unsigned_int_trits(x).collect()
77            }
78        }
79    };
80}
81
82// We have to implement manually due to Rust's orphan rules :(
83// This macro accepts anything that implements:
84// `Clone + CheckedAdd + Unsigned + AsPrimitive<u8> + FromPrimitive`
85#[cfg(has_u128)]
86unsigned_try_from_trits!(u128);
87unsigned_try_from_trits!(u64);
88unsigned_try_from_trits!(u32);
89unsigned_try_from_trits!(u16);
90unsigned_try_from_trits!(u8);
91
92/// Attempt to convert the given trit slice into a number. If the numeric representation of the
93/// trit slice is too large or small to fit the numeric type, or does not contain any trits, an
94/// error will be returned.
95pub fn trits_to_int<I, T: RawEncoding + ?Sized>(trits: &Trits<T>) -> Result<I, Error>
96where
97    I: Clone + CheckedAdd + CheckedSub + PartialOrd + Num,
98{
99    if trits.is_empty() {
100        Err(Error::Empty)
101    } else {
102        let mut acc = I::zero();
103
104        for trit in trits.iter().rev() {
105            let old_acc = acc.clone();
106            acc = trit
107                .add_to_num(acc)?
108                .checked_add(&old_acc)
109                .and_then(|acc| acc.checked_add(&old_acc))
110                .ok_or_else(|| {
111                    if old_acc < I::zero() {
112                        Error::Underflow
113                    } else {
114                        Error::Overflow
115                    }
116                })?;
117        }
118
119        Ok(acc)
120    }
121}
122
123/// Produce an iterator over the [`Btrit`]s that make up a given integer.
124pub fn signed_int_trits<I>(x: I) -> impl Iterator<Item = Btrit> + Clone
125where
126    I: Clone + AsPrimitive<i8> + FromPrimitive + Signed,
127{
128    let is_neg = x.is_negative();
129    let mut x = if is_neg { x } else { -x };
130
131    let radix = I::from_i8(3).unwrap();
132
133    core::iter::from_fn(move || {
134        if x.is_zero() {
135            None
136        } else {
137            let modulus = ((x + I::one()).abs() % radix).as_();
138            x = x / radix;
139            if modulus == 1 {
140                x = x + -I::one();
141            }
142            Some(Btrit::try_from(((modulus + 2) % 3 - 1) * if is_neg { -1 } else { 1 }).unwrap())
143        }
144    })
145    // If the integer is exactly 0, add an extra trit
146    .chain(Some(Btrit::Zero).filter(|_| x.is_zero()))
147}
148
149/// Produce an iterator over the [`Utrit`]s that make up a given integer.
150pub fn unsigned_int_trits<I>(mut x: I) -> impl Iterator<Item = Utrit> + Clone
151where
152    I: Clone + AsPrimitive<u8> + FromPrimitive + Num,
153{
154    let radix = I::from_u8(3).unwrap();
155
156    core::iter::from_fn(move || {
157        if x.is_zero() {
158            None
159        } else {
160            let modulus = (x % radix).as_();
161            x = x / radix;
162            Some(Utrit::try_from(modulus).unwrap())
163        }
164    })
165    // If the integer is exactly 0, add an extra trit
166    .chain(Some(Utrit::Zero).filter(|_| x.is_zero()))
167}