snafu_numbers/
lib.rs

1//! # Special Numeral-Analogue Fuel Units
2//!
3//! A Rust crate and utility to deal with conversions of SNAFU values
4//! from Advent of Code 2022, Day 25 ([here](https://adventofcode.com/2022/day/25)).
5//!
6//! Conversion is implemented via the [`FromSnafu`]/[`TryFromSnafu`] and [`IntoSnafu`] traits.
7//!
8//! ## SNAFU numbers
9//!
10//! SNAFU numbers are a power-of-5 centric base-10 system written right to left.
11//! The zero-th (i.e., right-most) place represents a multiple of 5<sup>0</sup> = 0, the first
12//! represents a multiple 5<sup>1</sup> = 5, the second place 5<sup>2</sup> = 25,
13//! the third place 5<sup>3</sup> = 625, etc.
14//!
15//! Five different digits are used. Here is a list alongside their decimal integer representation:
16//!
17//! | SNAFU digit | Name         | Decimal / ℤ |
18//! |-------------|--------------|-------------|
19//! | `2`         | two          | `2`         |
20//! | `1`         | one          | `1`         |
21//! | `0`         | zero         | `0`         |
22//! | `-`         | minus        | `-1`        |
23//! | `=`         | double-minus | `-2`        |
24//!
25//! As a result, the individual values in each position `n` is 2×5<sup>n-1</sup>, so
26//!
27//! | Position | Base                   | `=`     | `-`     | `0` | `1`    | `2`    |
28//! |----------|------------------------|---------|---------|-----|--------|--------|
29//! | 0        | 5<sup>0</sup> = `1`    | `-2`    | `1`     | `0` | `1`    | `2`    |
30//! | 1        | 5<sup>1</sup> = `5`    | `-10`   | `-5`    | `0` | `5`    | `10`   |
31//! | 2        | 5<sup>2</sup> = `25`   | `-50`   | `-25`   | `0` | `25`   | `50`   |
32//! | 3        | 5<sup>3</sup> = `125`  | `-250`  | `-125`  | `0` | `125`  | `250`  |
33//! | 4        | 5<sup>4</sup> = `625`  | `-1250` | `-625`  | `0` | `625`  | `1250` |
34//! | 5        | 5<sup>5</sup> = `3125` | `-6250` | `-3125` | `0` | `3125` | `6250` |
35//!
36//! etc.
37//!
38//! To quote the rules:
39//!
40//! > Say you have the SNAFU number `2=-01`. That's `2` in
41//! > the 625s place, `=` (double-minus) in the 125s place, `-` (minus) in the 25s place,
42//! > `0` in the 5s place, and 1 in the `1`s place.
43//! > (2 times 625) plus (-2 times 125) plus (-1 times 25) plus (0 times 5) plus (1 times 1).
44//! > That's 1250 plus -250 plus -25 plus 0 plus 1. **976**!"
45//!
46//! ### Example conversion from decimal to SNAFU
47//!
48//! ```
49//! use snafu_numbers::IntoSnafu;
50//! assert_eq!(1747_u32.into_snafu(), "1=-0-2");
51//! assert_eq!(1257_u32.into_snafu(), "20012");
52//! assert_eq!(3_u32.into_snafu(), "1=");
53//! ```
54//!
55//! | Decimal     | SNAFU           |
56//! |-------------|-----------------|
57//! | `1`         | `1`             |
58//! | `2`         | `2`             |
59//! | `3`         | `1=`            |
60//! | `4`         | `1-`            |
61//! | `5`         | `10`            |
62//! | `6`         | `11`            |
63//! | `7`         | `12`            |
64//! | `8`         | `2=`            |
65//! | `9`         | `2-`            |
66//! | `10`        | `20`            |
67//! | `15`        | `1=0`           |
68//! | `20`        | `1-0`           |
69//! | `2022`      | `1=11-2`        |
70//! | `12345`     | `1-0---0`       |
71//! | `314159265` | `1121-1110-1=0` |
72//!
73//! ### Example conversion from SNAFU to decimal
74//!
75//! ```
76//! use snafu_numbers::FromSnafu;
77//! assert_eq!(u32::from_snafu("1=-0-2"), 1747);
78//! assert_eq!(u32::from_snafu("20012"), 1257);
79//! assert_eq!(u32::from_snafu("1="), 3);
80//! ```
81//!
82//! | SNAFU    | Decimal |
83//! |----------|---------|
84//! | `1=-0-2` | `1747`  |
85//! | `12111`  | `906`   |
86//! | `2=0=`   | `198`   |
87//! | `21`     | `11`    |
88//! | `2=01`   | `201`   |
89//! | `111`    | `31`    |
90//! | `20012`  | `1257`  |
91//! | `112`    | `32`    |
92//! | `1=-1=`  | `353`   |
93//! | `1-12`   | `107`   |
94//! | `12`     | `7`     |
95//! | `1=`     | `3`     |
96//! | `122`    | `37`    |
97
98use std::error::Error;
99use std::fmt::{Display, Formatter};
100use std::ops::Div;
101
102/// Provides fallible conversion from SNAFU numbers.
103pub trait TryFromSnafu: Sized {
104    /// Converts a string from SNAFU numbers into decimal.
105    /// Returns an error if the conversion failed.
106    ///
107    /// ## Arguments
108    /// * `value` - The string value to convert from.
109    ///
110    /// ## Example
111    /// ```
112    /// use snafu_numbers::TryFromSnafu;
113    /// assert_eq!(u32::try_from_snafu("1=-0-2"), Ok(1747));
114    /// ```
115    fn try_from_snafu(value: &str) -> Result<Self, ConversionError>;
116}
117
118/// Provides conversion from SNAFU numbers.
119pub trait FromSnafu {
120    /// Converts a string from SNAFU numbers into decimal.
121    ///
122    /// ## Arguments
123    /// * `value` - The string value to convert from.
124    ///
125    /// ## Panics
126    /// Panics if the conversion failed. If you need to fail gracefully,
127    /// use [`TryFromSnafu`] instead.
128    ///
129    /// ## Example
130    /// ```
131    /// use snafu_numbers::FromSnafu;
132    /// assert_eq!(u32::from_snafu("1=-0-2"), 1747);
133    /// ```
134    fn from_snafu(value: &str) -> Self;
135}
136
137/// Provides conversion into SNAFU numbers.
138pub trait IntoSnafu {
139    /// Converts a number from decimal into SNAFU.
140    ///
141    /// ## Arguments
142    /// * `self` - The value to convert.
143    ///
144    /// ## Returns
145    /// A string representing the SNAFU value.
146    ///
147    /// ## Example
148    /// ```
149    /// use snafu_numbers::IntoSnafu;
150    /// assert_eq!(1747_u32.into_snafu(), "1=-0-2");
151    /// ```
152    fn into_snafu(self) -> String;
153}
154
155#[derive(Debug, Copy, Clone, Eq, PartialEq)]
156pub enum ConversionError {
157    /// An invalid digit was provided.
158    InvalidDigit,
159    /// A calculation overflowed.
160    Overflow,
161    /// The value provided is too large or too small to be
162    /// represented by the target type.
163    OutOfBounds,
164}
165
166impl Display for ConversionError {
167    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
168        match self {
169            ConversionError::InvalidDigit => write!(f, "An invalid digit was specified"),
170            ConversionError::Overflow => write!(f, "The calculation overflowed"),
171            ConversionError::OutOfBounds => {
172                write!(f, "The input was out of bounds for the target type")
173            }
174        }
175    }
176}
177
178impl Error for ConversionError {}
179
180/// Implements [`TryFromSnafu`] for the given target type.
181///
182/// ## Macro Arguments
183/// * `max_len` - The maximum allowed string length before an overflow occurs.
184/// * `target` - The type for which to implement [`TryFromSnafu`].
185/// * `lift` - The type to which to lift the calculation in order to allow for negative
186///            values in the calculations. Should be a signed type.
187macro_rules! impl_try_from {
188    ($max_len: literal, $target:ty, $lift:ty) => {
189        impl TryFromSnafu for $target {
190            fn try_from_snafu(value: &str) -> Result<Self, ConversionError> {
191                if value.len() > $max_len {
192                    return Err(ConversionError::OutOfBounds);
193                }
194
195                let (sum, _) =
196                    value
197                        .chars()
198                        .rev()
199                        .try_fold((0 as $lift, 1 as $lift), |(sum, pow), c| {
200                            let digit = map_digit(c)?;
201                            let value = digit as $lift * pow;
202                            Ok((sum + value, pow * 5))
203                        })?;
204
205                if sum > (<$target>::MAX as $lift) {
206                    return Err(ConversionError::OutOfBounds);
207                }
208
209                Ok(sum as $target)
210            }
211        }
212    };
213}
214
215impl_try_from!(55, i128, i128);
216impl_try_from!(28, i64, i128);
217impl_try_from!(28, u64, i128);
218impl_try_from!(14, i32, i64);
219impl_try_from!(14, u32, i64);
220impl_try_from!(7, i16, i32);
221impl_try_from!(7, u16, i32);
222impl_try_from!(4, i8, i16);
223impl_try_from!(4, u8, i16);
224
225impl<T> FromSnafu for T
226where
227    T: TryFromSnafu,
228{
229    fn from_snafu(value: &str) -> Self {
230        match T::try_from_snafu(value) {
231            Ok(value) => value,
232            Err(e) => panic!("Unable to convert to SNAFU: {e}"),
233        }
234    }
235}
236
237/// The symbol table used for converting a decimal to SNAFU.
238static SYMBOLS: &[char] = &['=', '-', '0', '1', '2'];
239
240/// Implements [`IntoSnafu`] for the given target type.
241///
242/// ## Macro Arguments
243/// * `target` - The type for which to implement [`IntoSnafu`].
244macro_rules! impl_into {
245    ($target:ty) => {
246        impl IntoSnafu for $target {
247            fn into_snafu(mut self) -> String {
248                // TODO: Determine capacity
249                let mut digits = Vec::default();
250
251                loop {
252                    self += 2;
253                    let selector = self % 5;
254                    let digit = SYMBOLS[selector as usize];
255                    digits.push(digit);
256
257                    if self < 5 {
258                        break;
259                    }
260                    self = self.div(5);
261                }
262
263                String::from_iter(digits.into_iter().rev())
264            }
265        }
266    };
267}
268
269impl_into!(usize);
270impl_into!(isize);
271impl_into!(u128);
272impl_into!(i128);
273impl_into!(u64);
274impl_into!(i64);
275impl_into!(u32);
276impl_into!(i32);
277impl_into!(u16);
278impl_into!(i16);
279impl_into!(u8);
280impl_into!(i8);
281
282/// Maps a SNAFU digit to a decimal digit.
283#[inline(always)]
284const fn map_digit(digit: char) -> Result<i8, ConversionError> {
285    match digit {
286        '2' => Ok(2),
287        '1' => Ok(1),
288        '0' => Ok(0),
289        '-' => Ok(-1),
290        '=' => Ok(-2),
291        _ => Err(ConversionError::InvalidDigit),
292    }
293}
294
295/// Calculates the number of bits required to store SNAFU
296/// value of the specified length.
297#[cfg(test)]
298fn num_bits_for_len(len: usize) -> u32 {
299    debug_assert_ne!(len, 0, "value must be positive");
300    num_bits_for_pos(len - 1)
301}
302
303/// Calculates the number of bits required to store SNAFU
304/// given the specified highest zero-based digit position.
305///
306/// ## Value ranges and storage requirements
307///
308/// | Position | Highest     | num bits (unsigned) | delta (bits) |
309/// |----------|-------------|---------------------|--------------|
310/// | 0        | `2`         | 2                   | -            |
311/// | 1        | `12`        | 4                   | 2            |
312/// | 2        | `62`        | 6                   | 2            |
313/// | 3        | `312`       | 9                   | 3            |
314/// | 4        | `1562`      | 11                  | 2            |
315/// | 5        | `7812`      | 13                  | 2            |
316/// | 6        | `39062`     | 16                  | 3            |
317/// | 7        | `195312`    | 18                  | 2            |
318/// | 8        | `976562`    | 20                  | 2            |
319/// | 9        | `4882812`   | 23                  | 3            |
320/// | 10       | `24414062`  | 25                  | 2            |
321/// | 11       | `122070312` | 27                  | 2            |
322/// | 12       | `610351562` | 30                  | 3            |
323#[cfg(test)]
324fn num_bits_for_pos(pos: usize) -> u32 {
325    // Beatty sequence for log_2(5)
326    // https://oeis.org/A061785
327    ((pos + 1) as f32 * 5.0f32.log2()).floor() as u32
328}
329
330#[cfg(test)]
331mod tests {
332    use super::*;
333
334    #[test]
335    fn u8_from_snafu_works() {
336        assert_eq!(u8::from_snafu("2=0="), 198);
337        assert_eq!(u8::from_snafu("21"), 11);
338        assert_eq!(u8::from_snafu("2=01"), 201);
339        assert_eq!(u8::from_snafu("111"), 31);
340        assert_eq!(u8::from_snafu("112"), 32);
341        assert_eq!(u8::from_snafu("1-12"), 107);
342        assert_eq!(u8::from_snafu("12"), 7);
343        assert_eq!(u8::from_snafu("1="), 3);
344        assert_eq!(u8::from_snafu("122"), 37);
345    }
346
347    #[test]
348    fn u16_from_snafu_works() {
349        assert_eq!(u16::from_snafu("1=-0-2"), 1747);
350        assert_eq!(u16::from_snafu("12111"), 906);
351        assert_eq!(u16::from_snafu("2=0="), 198);
352        assert_eq!(u16::from_snafu("21"), 11);
353        assert_eq!(u16::from_snafu("2=01"), 201);
354        assert_eq!(u16::from_snafu("111"), 31);
355        assert_eq!(u16::from_snafu("20012"), 1257);
356        assert_eq!(u16::from_snafu("112"), 32);
357        assert_eq!(u16::from_snafu("1=-1="), 353);
358        assert_eq!(u16::from_snafu("1-12"), 107);
359        assert_eq!(u16::from_snafu("12"), 7);
360        assert_eq!(u16::from_snafu("1="), 3);
361        assert_eq!(u16::from_snafu("122"), 37);
362        assert_eq!(u16::from_snafu("2=-01"), 976);
363    }
364
365    #[test]
366    fn u32_from_snafu_works() {
367        assert_eq!(u32::from_snafu("1=-0-2"), 1747);
368        assert_eq!(u32::from_snafu("12111"), 906);
369        assert_eq!(u32::from_snafu("2=0="), 198);
370        assert_eq!(u32::from_snafu("21"), 11);
371        assert_eq!(u32::from_snafu("2=01"), 201);
372        assert_eq!(u32::from_snafu("111"), 31);
373        assert_eq!(u32::from_snafu("20012"), 1257);
374        assert_eq!(u32::from_snafu("112"), 32);
375        assert_eq!(u32::from_snafu("1=-1="), 353);
376        assert_eq!(u32::from_snafu("1-12"), 107);
377        assert_eq!(u32::from_snafu("12"), 7);
378        assert_eq!(u32::from_snafu("1="), 3);
379        assert_eq!(u32::from_snafu("122"), 37);
380        assert_eq!(u32::from_snafu("2=-01"), 976);
381    }
382
383    #[test]
384    fn u64_from_snafu_works() {
385        assert_eq!(u64::from_snafu("1=-0-2"), 1747);
386        assert_eq!(u64::from_snafu("12111"), 906);
387        assert_eq!(u64::from_snafu("2=0="), 198);
388        assert_eq!(u64::from_snafu("21"), 11);
389        assert_eq!(u64::from_snafu("2=01"), 201);
390        assert_eq!(u64::from_snafu("111"), 31);
391        assert_eq!(u64::from_snafu("20012"), 1257);
392        assert_eq!(u64::from_snafu("112"), 32);
393        assert_eq!(u64::from_snafu("1=-1="), 353);
394        assert_eq!(u64::from_snafu("1-12"), 107);
395        assert_eq!(u64::from_snafu("12"), 7);
396        assert_eq!(u64::from_snafu("1="), 3);
397        assert_eq!(u64::from_snafu("122"), 37);
398        assert_eq!(u64::from_snafu("2=-01"), 976);
399    }
400
401    #[test]
402    fn i128_from_snafu_works() {
403        assert_eq!(i128::from_snafu("1=-0-2"), 1747);
404        assert_eq!(i128::from_snafu("12111"), 906);
405        assert_eq!(i128::from_snafu("2=0="), 198);
406        assert_eq!(i128::from_snafu("21"), 11);
407        assert_eq!(i128::from_snafu("2=01"), 201);
408        assert_eq!(i128::from_snafu("111"), 31);
409        assert_eq!(i128::from_snafu("20012"), 1257);
410        assert_eq!(i128::from_snafu("112"), 32);
411        assert_eq!(i128::from_snafu("1=-1="), 353);
412        assert_eq!(i128::from_snafu("1-12"), 107);
413        assert_eq!(i128::from_snafu("12"), 7);
414        assert_eq!(i128::from_snafu("1="), 3);
415        assert_eq!(i128::from_snafu("122"), 37);
416        assert_eq!(i128::from_snafu("2=-01"), 976);
417    }
418
419    #[test]
420    fn highest_number() {
421        assert_eq!(naive_num_bits(0), 1); // need 1 bit to store the number 0
422
423        assert_eq!(max_for_length(1), 2); // 2×1
424        assert_eq!(naive_num_bits(2), 2);
425        assert_eq!(num_bits_for_pos(0), 2);
426        assert_eq!(num_bits_for_len(1), 2);
427
428        assert_eq!(max_for_length(2), 12); // 2×1 + 2×5
429        assert_eq!(naive_num_bits(12), 4); // +2
430        assert_eq!(num_bits_for_pos(1), 4);
431        assert_eq!(num_bits_for_len(2), 4);
432
433        assert_eq!(max_for_length(3), 62); // 2×1 + 2×5 + 2×25
434        assert_eq!(naive_num_bits(62), 6); // +2
435        assert_eq!(num_bits_for_pos(2), 6);
436
437        // boundary of i8 / u8
438
439        assert_eq!(num_bits_for_len(3), 6);
440        assert_eq!(naive_num_bits(i8::MAX as u128), 7);
441        assert_eq!(naive_num_bits(u8::MAX as u128), 8);
442
443        // i8 / u8 overflow here.
444
445        assert_eq!(max_for_length(4), 312); // 2×1 + 2×5 + 2×25 + 2×125
446        assert_eq!(naive_num_bits(312), 9); // +3
447        assert_eq!(num_bits_for_pos(3), 9);
448
449        assert_eq!(max_for_length(5), 1562);
450        assert_eq!(naive_num_bits(1562), 11); // +2
451        assert_eq!(num_bits_for_pos(4), 11);
452
453        assert_eq!(max_for_length(6), 7812);
454        assert_eq!(naive_num_bits(7812), 13); // +2
455        assert_eq!(num_bits_for_pos(5), 13);
456
457        assert_eq!(max_for_length(7), 39062);
458        assert_eq!(naive_num_bits(39062), 16); // +3
459        assert_eq!(num_bits_for_pos(6), 16);
460
461        // boundary of i16 / u16
462
463        assert_eq!(num_bits_for_len(7), 16);
464        assert_eq!(naive_num_bits(i16::MAX as u128), 15);
465        assert_eq!(naive_num_bits(u16::MAX as u128), 16);
466
467        // i16 / u16 overflow here.
468
469        assert_eq!(max_for_length(8), 195312);
470        assert_eq!(naive_num_bits(195312), 18); // +2
471        assert_eq!(num_bits_for_pos(7), 18);
472
473        assert_eq!(max_for_length(9), 976562);
474        assert_eq!(naive_num_bits(976562), 20); // +2
475        assert_eq!(num_bits_for_pos(8), 20);
476
477        assert_eq!(max_for_length(10), 4882812);
478        assert_eq!(naive_num_bits(4882812), 23); // +3
479        assert_eq!(num_bits_for_pos(9), 23);
480
481        assert_eq!(max_for_length(11), 24414062);
482        assert_eq!(naive_num_bits(24414062), 25); // +2
483        assert_eq!(num_bits_for_pos(10), 25);
484
485        assert_eq!(max_for_length(12), 122070312);
486        assert_eq!(naive_num_bits(122070312), 27); // +2
487        assert_eq!(num_bits_for_pos(11), 27);
488
489        assert_eq!(max_for_length(13), 610351562);
490        assert_eq!(naive_num_bits(610351562), 30); // +3
491        assert_eq!(num_bits_for_pos(12), 30);
492
493        assert_eq!(max_for_length(14), 3051757812);
494        assert_eq!(naive_num_bits(3051757812), 32); // +2
495        assert_eq!(num_bits_for_pos(13), 32);
496
497        // boundary of i32 / u32
498
499        assert_eq!(num_bits_for_len(14), 32);
500        assert_eq!(naive_num_bits(i32::MAX as u128), 31);
501        assert_eq!(naive_num_bits(u32::MAX as u128), 32);
502
503        // i32 / u32 overflow here.
504
505        assert_eq!(max_for_length(15), 15258789062);
506        assert_eq!(naive_num_bits(15258789062), 34); // +2
507        assert_eq!(num_bits_for_pos(14), 34);
508
509        assert_eq!(max_for_length(16), 76293945312);
510        assert_eq!(naive_num_bits(76293945312), 37); // +3
511        assert_eq!(num_bits_for_pos(15), 37);
512
513        assert_eq!(max_for_length(17), 381469726562);
514        assert_eq!(naive_num_bits(381469726562), 39); // +2
515        assert_eq!(num_bits_for_pos(16), 39);
516
517        assert_eq!(max_for_length(18), 1907348632812);
518        assert_eq!(naive_num_bits(1907348632812), 41); // +2
519        assert_eq!(num_bits_for_pos(17), 41);
520
521        assert_eq!(max_for_length(19), 9536743164062);
522        assert_eq!(naive_num_bits(9536743164062), 44); // +3
523        assert_eq!(num_bits_for_pos(18), 44);
524
525        assert_eq!(max_for_length(20), 47683715820312);
526        assert_eq!(naive_num_bits(47683715820312), 46); // +2
527        assert_eq!(num_bits_for_pos(19), 46);
528
529        assert_eq!(max_for_length(21), 238418579101562);
530        assert_eq!(naive_num_bits(238418579101562), 48); // +2
531        assert_eq!(num_bits_for_pos(20), 48);
532
533        assert_eq!(max_for_length(22), 1192092895507812);
534        assert_eq!(naive_num_bits(1192092895507812), 51); // +3
535        assert_eq!(num_bits_for_pos(21), 51);
536
537        assert_eq!(max_for_length(23), 5960464477539062);
538        assert_eq!(naive_num_bits(5960464477539062), 53); // +2
539        assert_eq!(num_bits_for_pos(22), 53);
540
541        assert_eq!(max_for_length(24), 29802322387695312);
542        assert_eq!(naive_num_bits(29802322387695312), 55); // +2
543        assert_eq!(num_bits_for_pos(23), 55);
544
545        assert_eq!(max_for_length(25), 149011611938476562);
546        assert_eq!(naive_num_bits(149011611938476562), 58); // +3
547        assert_eq!(num_bits_for_pos(24), 58);
548
549        assert_eq!(max_for_length(26), 745058059692382812);
550        assert_eq!(naive_num_bits(745058059692382812), 60); // +2
551        assert_eq!(num_bits_for_pos(25), 60);
552
553        assert_eq!(max_for_length(27), 3725290298461914062);
554        assert_eq!(naive_num_bits(3725290298461914062), 62); // +2
555        assert_eq!(num_bits_for_pos(26), 62);
556
557        // boundary of i64 / u64
558
559        assert_eq!(num_bits_for_len(27), 62);
560        assert_eq!(naive_num_bits(i64::MAX as u128), 63);
561        assert_eq!(naive_num_bits(u64::MAX as u128), 64);
562
563        // i64 / u64 overflow here.
564
565        assert_eq!(max_for_length(28), 18626451492309570312);
566        assert_eq!(naive_num_bits(18626451492309570312), 65); // +3
567        assert_eq!(num_bits_for_pos(27), 65);
568
569        assert_eq!(max_for_length(29), 93132257461547851562);
570        assert_eq!(naive_num_bits(93132257461547851562), 67); // +2
571        assert_eq!(num_bits_for_pos(28), 67);
572
573        assert_eq!(max_for_length(30), 465661287307739257812);
574        assert_eq!(naive_num_bits(465661287307739257812), 69); // +2
575        assert_eq!(num_bits_for_pos(29), 69);
576
577        assert_eq!(max_for_length(31), 2328306436538696289062);
578        assert_eq!(naive_num_bits(2328306436538696289062), 71); // +2 !! (not +3)
579        assert_eq!(num_bits_for_pos(30), 71);
580
581        assert_eq!(max_for_length(32), 11641532182693481445312);
582        assert_eq!(naive_num_bits(11641532182693481445312), 74); // +3
583        assert_eq!(num_bits_for_pos(31), 74);
584
585        assert_eq!(max_for_length(33), 58207660913467407226562);
586        assert_eq!(naive_num_bits(58207660913467407226562), 76); // +2
587        assert_eq!(num_bits_for_pos(32), 76);
588
589        assert_eq!(max_for_length(34), 291038304567337036132812);
590        assert_eq!(naive_num_bits(291038304567337036132812), 78); // +2
591        assert_eq!(num_bits_for_pos(33), 78);
592
593        assert_eq!(max_for_length(35), 1455191522836685180664062);
594        assert_eq!(naive_num_bits(1455191522836685180664062), 81); // +3
595        assert_eq!(num_bits_for_pos(34), 81);
596
597        assert_eq!(max_for_length(36), 7275957614183425903320312);
598        assert_eq!(naive_num_bits(7275957614183425903320312), 83); // +2
599        assert_eq!(num_bits_for_pos(35), 83);
600
601        // boundary of i128 / u128
602
603        assert_eq!(max_for_length(55), 138777878078144567552953958511352539062);
604        assert_eq!(naive_num_bits(138777878078144567552953958511352539062), 127);
605        assert_eq!(num_bits_for_pos(54), 127);
606
607        assert_eq!(num_bits_for_len(55), 127);
608        assert_eq!(naive_num_bits(i128::MAX as u128), 127);
609        assert_eq!(naive_num_bits(u128::MAX), 128);
610
611        // i128 / u128 overflow here.
612
613        assert_eq!(num_bits_for_pos(55), 130);
614    }
615
616    /// Calculates the highest decimal value for a SNAFU number
617    /// of the specified length.
618    fn max_for_length(len: u32) -> u128 {
619        let mut sum: u128 = 0;
620        for n in 0..len {
621            sum += 2 * 5_u128.pow(n);
622        }
623        sum
624    }
625
626    /// Gets the number of bits required to store the specified number.
627    fn naive_num_bits(value: u128) -> u32 {
628        1 + if value == 0 { 0 } else { value.ilog2() }
629    }
630
631    #[test]
632    fn u128_into_snafu() {
633        assert_eq!(1_u128.into_snafu(), "1");
634        assert_eq!(2_u128.into_snafu(), "2");
635        assert_eq!(3_u128.into_snafu(), "1=");
636        assert_eq!(4_u128.into_snafu(), "1-");
637        assert_eq!(5_u128.into_snafu(), "10");
638        assert_eq!(6_u128.into_snafu(), "11");
639        assert_eq!(7_u128.into_snafu(), "12");
640        assert_eq!(8_u128.into_snafu(), "2=");
641        assert_eq!(9_u128.into_snafu(), "2-");
642        assert_eq!(10_u128.into_snafu(), "20");
643        assert_eq!(11_u128.into_snafu(), "21");
644        assert_eq!(12_u128.into_snafu(), "22");
645        assert_eq!(15_u128.into_snafu(), "1=0");
646        assert_eq!(20_u128.into_snafu(), "1-0");
647        assert_eq!(976_u128.into_snafu(), "2=-01");
648        assert_eq!(2022_u128.into_snafu(), "1=11-2");
649        assert_eq!(12345_u128.into_snafu(), "1-0---0");
650        assert_eq!(314159265_u128.into_snafu(), "1121-1110-1=0");
651    }
652
653    #[test]
654    fn i32_into_snafu() {
655        assert_eq!(1_i32.into_snafu(), "1");
656        assert_eq!(2_i32.into_snafu(), "2");
657        assert_eq!(3_i32.into_snafu(), "1=");
658        assert_eq!(4_i32.into_snafu(), "1-");
659        assert_eq!(5_i32.into_snafu(), "10");
660        assert_eq!(6_i32.into_snafu(), "11");
661        assert_eq!(7_i32.into_snafu(), "12");
662        assert_eq!(8_i32.into_snafu(), "2=");
663        assert_eq!(9_i32.into_snafu(), "2-");
664        assert_eq!(10_i32.into_snafu(), "20");
665        assert_eq!(11_i32.into_snafu(), "21");
666        assert_eq!(12_i32.into_snafu(), "22");
667        assert_eq!(15_i32.into_snafu(), "1=0");
668        assert_eq!(20_i32.into_snafu(), "1-0");
669        assert_eq!(976_i32.into_snafu(), "2=-01");
670        assert_eq!(2022_i32.into_snafu(), "1=11-2");
671        assert_eq!(12345_i32.into_snafu(), "1-0---0");
672        assert_eq!(314159265_i32.into_snafu(), "1121-1110-1=0");
673    }
674
675    #[test]
676    fn i8_into_snafu() {
677        assert_eq!(1_i8.into_snafu(), "1");
678        assert_eq!(2_i8.into_snafu(), "2");
679        assert_eq!(3_i8.into_snafu(), "1=");
680        assert_eq!(4_i8.into_snafu(), "1-");
681        assert_eq!(5_i8.into_snafu(), "10");
682        assert_eq!(6_i8.into_snafu(), "11");
683        assert_eq!(7_i8.into_snafu(), "12");
684        assert_eq!(8_i8.into_snafu(), "2=");
685        assert_eq!(9_i8.into_snafu(), "2-");
686        assert_eq!(10_i8.into_snafu(), "20");
687        assert_eq!(11_i8.into_snafu(), "21");
688        assert_eq!(12_i8.into_snafu(), "22");
689        assert_eq!(15_i8.into_snafu(), "1=0");
690        assert_eq!(20_i8.into_snafu(), "1-0");
691    }
692
693    #[test]
694    fn into_snafu_reference_works() {
695        assert_eq!(into_snafu_reference(1), "1");
696        assert_eq!(into_snafu_reference(2), "2");
697        assert_eq!(into_snafu_reference(3), "1=");
698        assert_eq!(into_snafu_reference(4), "1-");
699        assert_eq!(into_snafu_reference(5), "10");
700        assert_eq!(into_snafu_reference(6), "11");
701        assert_eq!(into_snafu_reference(7), "12");
702        assert_eq!(into_snafu_reference(8), "2=");
703        assert_eq!(into_snafu_reference(9), "2-");
704        assert_eq!(into_snafu_reference(10), "20");
705        assert_eq!(into_snafu_reference(11), "21");
706        assert_eq!(into_snafu_reference(12), "22");
707        assert_eq!(into_snafu_reference(15), "1=0");
708        assert_eq!(into_snafu_reference(20), "1-0");
709        assert_eq!(into_snafu_reference(976), "2=-01");
710        assert_eq!(into_snafu_reference(2022), "1=11-2");
711        assert_eq!(into_snafu_reference(2023), "1=110=");
712        assert_eq!(into_snafu_reference(12345), "1-0---0");
713        assert_eq!(into_snafu_reference(314159265), "1121-1110-1=0");
714    }
715
716    fn into_snafu_reference(mut value: u128) -> String {
717        let mut digits = Vec::default();
718
719        loop {
720            value += 2;
721            let selector = value % 5;
722            let digit = SYMBOLS[selector as usize];
723            digits.push(digit);
724
725            if value < 5 {
726                break;
727            }
728            value = value.div(5);
729        }
730
731        String::from_iter(digits.into_iter().rev())
732    }
733}