cast/
base.rs

1use alloy_primitives::{I256, Sign, U256, utils::ParseUnits};
2use eyre::Result;
3use std::{
4    convert::Infallible,
5    fmt::{Binary, Debug, Display, Formatter, LowerHex, Octal, Result as FmtResult, UpperHex},
6    num::IntErrorKind,
7    str::FromStr,
8};
9
10/* -------------------------------------------- Base -------------------------------------------- */
11
12/// Represents a number's [radix] or base. Currently it supports the same bases that [std::fmt]
13/// supports.
14///
15/// [radix]: https://en.wikipedia.org/wiki/Radix
16#[repr(u32)]
17#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
18pub enum Base {
19    Binary = 2,
20    Octal = 8,
21    #[default]
22    Decimal = 10,
23    Hexadecimal = 16,
24}
25
26impl Display for Base {
27    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
28        Display::fmt(&(*self as u32), f)
29    }
30}
31
32impl FromStr for Base {
33    type Err = eyre::Report;
34
35    fn from_str(s: &str) -> Result<Self, Self::Err> {
36        match s.to_lowercase().as_str() {
37            "2" | "b" | "bin" | "binary" => Ok(Self::Binary),
38            "8" | "o" | "oct" | "octal" => Ok(Self::Octal),
39            "10" | "d" | "dec" | "decimal" => Ok(Self::Decimal),
40            "16" | "h" | "hex" | "hexadecimal" => Ok(Self::Hexadecimal),
41            s => Err(eyre::eyre!(
42                "\
43Invalid base \"{s}\". Possible values:
44 2, b, bin, binary
45 8, o, oct, octal
4610, d, dec, decimal
4716, h, hex, hexadecimal"
48            )),
49        }
50    }
51}
52
53impl TryFrom<String> for Base {
54    type Error = eyre::Report;
55
56    fn try_from(s: String) -> Result<Self, Self::Error> {
57        Self::from_str(&s)
58    }
59}
60
61impl TryFrom<u32> for Base {
62    type Error = eyre::Report;
63
64    fn try_from(n: u32) -> Result<Self, Self::Error> {
65        match n {
66            2 => Ok(Self::Binary),
67            8 => Ok(Self::Octal),
68            10 => Ok(Self::Decimal),
69            16 => Ok(Self::Hexadecimal),
70            n => Err(eyre::eyre!("Invalid base \"{}\". Possible values: 2, 8, 10, 16", n)),
71        }
72    }
73}
74
75impl TryFrom<I256> for Base {
76    type Error = eyre::Report;
77
78    fn try_from(n: I256) -> Result<Self, Self::Error> {
79        Self::try_from(n.low_u32())
80    }
81}
82
83impl TryFrom<U256> for Base {
84    type Error = eyre::Report;
85
86    fn try_from(n: U256) -> Result<Self, Self::Error> {
87        Self::try_from(n.saturating_to::<u32>())
88    }
89}
90
91impl From<Base> for u32 {
92    fn from(b: Base) -> Self {
93        b as Self
94    }
95}
96
97impl From<Base> for String {
98    fn from(b: Base) -> Self {
99        b.to_string()
100    }
101}
102
103impl Base {
104    pub fn unwrap_or_detect(base: Option<&str>, s: impl AsRef<str>) -> Result<Self> {
105        match base {
106            Some(base) => base.parse(),
107            None => Self::detect(s),
108        }
109    }
110
111    /// Try parsing a number's base from a string.
112    pub fn detect(s: impl AsRef<str>) -> Result<Self> {
113        let s = s.as_ref();
114        match s {
115            // Ignore sign
116            _ if s.starts_with(['+', '-']) => Self::detect(&s[1..]),
117            // Verify binary and octal values with u128::from_str_radix as U256 does not support
118            // them;
119            // assume overflows are within u128::MAX and U256::MAX, we're not using the parsed value
120            // anyway;
121            // strip prefix when using u128::from_str_radix because it does not recognize it as
122            // valid.
123            _ if s.starts_with("0b") => match u64::from_str_radix(&s[2..], 2) {
124                Ok(_) => Ok(Self::Binary),
125                Err(e) => match e.kind() {
126                    IntErrorKind::PosOverflow => Ok(Self::Binary),
127                    _ => Err(eyre::eyre!("could not parse binary value: {}", e)),
128                },
129            },
130            _ if s.starts_with("0o") => match u64::from_str_radix(&s[2..], 8) {
131                Ok(_) => Ok(Self::Octal),
132                Err(e) => match e.kind() {
133                    IntErrorKind::PosOverflow => Ok(Self::Octal),
134                    _ => Err(eyre::eyre!("could not parse octal value: {e}")),
135                },
136            },
137            _ if s.starts_with("0x") => match u64::from_str_radix(&s[2..], 16) {
138                Ok(_) => Ok(Self::Hexadecimal),
139                Err(e) => match e.kind() {
140                    IntErrorKind::PosOverflow => Ok(Self::Hexadecimal),
141                    _ => Err(eyre::eyre!("could not parse hexadecimal value: {e}")),
142                },
143            },
144            // No prefix => first try parsing as decimal
145            _ => match U256::from_str_radix(s, 10) {
146                // Can be both, ambiguous but default to Decimal
147                Ok(_) => Ok(Self::Decimal),
148                Err(_) => match U256::from_str_radix(s, 16) {
149                    Ok(_) => Ok(Self::Hexadecimal),
150                    Err(e) => Err(eyre::eyre!(
151                        "could not autodetect base as neither decimal or hexadecimal: {e}"
152                    )),
153                },
154            },
155        }
156    }
157
158    /// Returns the Rust standard prefix for a base
159    pub const fn prefix(&self) -> &str {
160        match self {
161            Self::Binary => "0b",
162            Self::Octal => "0o",
163            Self::Decimal => "",
164            Self::Hexadecimal => "0x",
165        }
166    }
167}
168
169/* --------------------------------------- NumberWithBase --------------------------------------- */
170
171/// Utility struct for parsing numbers and formatting them into different [bases][Base].
172///
173/// # Example
174///
175/// ```
176/// use cast::base::NumberWithBase;
177/// use alloy_primitives::U256;
178///
179/// let number: NumberWithBase = U256::from(12345).into();
180/// assert_eq!(number.format(), "12345");
181///
182/// // Debug uses number.base() to determine which base to format to, which defaults to Base::Decimal
183/// assert_eq!(format!("{:?}", number), "12345");
184///
185/// // Display uses Base::Decimal
186/// assert_eq!(format!("{}", number), "12345");
187///
188/// // The alternate formatter ("#") prepends the base's prefix
189/// assert_eq!(format!("{:x}", number), "3039");
190/// assert_eq!(format!("{:#x}", number), "0x3039");
191///
192/// assert_eq!(format!("{:b}", number), "11000000111001");
193/// assert_eq!(format!("{:#b}", number), "0b11000000111001");
194///
195/// assert_eq!(format!("{:o}", number), "30071");
196/// assert_eq!(format!("{:#o}", number), "0o30071");
197/// ```
198#[derive(Clone, Copy)]
199pub struct NumberWithBase {
200    /// The number.
201    number: U256,
202    /// Whether the number is positive or zero.
203    is_nonnegative: bool,
204    /// The base to format to.
205    base: Base,
206}
207
208impl std::ops::Deref for NumberWithBase {
209    type Target = U256;
210
211    fn deref(&self) -> &Self::Target {
212        &self.number
213    }
214}
215
216// Format using self.base
217impl Debug for NumberWithBase {
218    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
219        let prefix = self.base.prefix();
220        if self.number.is_zero() {
221            f.pad_integral(true, prefix, "0")
222        } else {
223            // Add sign only for decimal
224            let is_nonnegative = match self.base {
225                Base::Decimal => self.is_nonnegative,
226                _ => true,
227            };
228            f.pad_integral(is_nonnegative, prefix, &self.format())
229        }
230    }
231}
232
233impl Binary for NumberWithBase {
234    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
235        Debug::fmt(&self.with_base(Base::Binary), f)
236    }
237}
238
239impl Octal for NumberWithBase {
240    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
241        Debug::fmt(&self.with_base(Base::Octal), f)
242    }
243}
244
245impl Display for NumberWithBase {
246    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
247        Debug::fmt(&self.with_base(Base::Decimal), f)
248    }
249}
250
251impl LowerHex for NumberWithBase {
252    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
253        Debug::fmt(&self.with_base(Base::Hexadecimal), f)
254    }
255}
256
257impl UpperHex for NumberWithBase {
258    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
259        let n = format!("{self:x}").to_uppercase();
260        f.pad_integral(true, Base::Hexadecimal.prefix(), &n)
261    }
262}
263
264impl FromStr for NumberWithBase {
265    type Err = eyre::Report;
266
267    fn from_str(s: &str) -> Result<Self, Self::Err> {
268        Self::parse_int(s, None)
269    }
270}
271
272impl From<I256> for NumberWithBase {
273    fn from(number: I256) -> Self {
274        // both is_positive and is_negative return false for 0
275        Self::new(number.into_raw(), !number.is_negative(), Base::default())
276    }
277}
278
279impl From<ParseUnits> for NumberWithBase {
280    fn from(value: ParseUnits) -> Self {
281        match value {
282            ParseUnits::U256(val) => val.into(),
283            ParseUnits::I256(val) => val.into(),
284        }
285    }
286}
287
288impl From<U256> for NumberWithBase {
289    fn from(number: U256) -> Self {
290        Self::new(number, true, Base::default())
291    }
292}
293
294impl From<NumberWithBase> for I256 {
295    fn from(n: NumberWithBase) -> Self {
296        Self::from_raw(n.number)
297    }
298}
299
300impl From<NumberWithBase> for U256 {
301    fn from(n: NumberWithBase) -> Self {
302        n.number
303    }
304}
305
306impl From<NumberWithBase> for String {
307    /// Formats the number into the specified base. See [NumberWithBase::format].
308    ///
309    /// [NumberWithBase::format]: NumberWithBase
310    fn from(n: NumberWithBase) -> Self {
311        n.format()
312    }
313}
314
315impl NumberWithBase {
316    pub fn new(number: impl Into<U256>, is_nonnegative: bool, base: Base) -> Self {
317        Self { number: number.into(), is_nonnegative, base }
318    }
319
320    /// Creates a copy of the number with the provided base.
321    pub fn with_base(&self, base: Base) -> Self {
322        Self { number: self.number, is_nonnegative: self.is_nonnegative, base }
323    }
324
325    /// Parses a string slice into a signed integer. If base is None then it tries to determine base
326    /// from the prefix, otherwise defaults to Decimal.
327    pub fn parse_int(s: &str, base: Option<&str>) -> Result<Self> {
328        let base = Base::unwrap_or_detect(base, s)?;
329        let (number, is_nonnegative) = Self::_parse_int(s, base)?;
330        Ok(Self { number, is_nonnegative, base })
331    }
332
333    /// Parses a string slice into an unsigned integer. If base is None then it tries to determine
334    /// base from the prefix, otherwise defaults to Decimal.
335    pub fn parse_uint(s: &str, base: Option<&str>) -> Result<Self> {
336        let base = Base::unwrap_or_detect(base, s)?;
337        let number = Self::_parse_uint(s, base)?;
338        Ok(Self { number, is_nonnegative: true, base })
339    }
340
341    /// Returns a copy of the underlying number as an unsigned integer. If the value is negative
342    /// then the two's complement of its absolute value will be returned.
343    pub fn number(&self) -> U256 {
344        self.number
345    }
346
347    /// Returns whether the underlying number is positive or zero.
348    pub fn is_nonnegative(&self) -> bool {
349        self.is_nonnegative
350    }
351
352    /// Returns the underlying base. Defaults to [Decimal][Base].
353    pub fn base(&self) -> Base {
354        self.base
355    }
356
357    /// Returns the Rust standard prefix for the base.
358    pub const fn prefix(&self) -> &str {
359        self.base.prefix()
360    }
361
362    /// Sets the number's base to format to.
363    pub fn set_base(&mut self, base: Base) -> &mut Self {
364        self.base = base;
365        self
366    }
367
368    /// Formats the number into the specified base.
369    ///
370    /// **Note**: this method only formats the number into the base, without adding any prefixes,
371    /// signs or padding. Refer to the [std::fmt] module documentation on how to format this
372    /// number with the aforementioned properties.
373    pub fn format(&self) -> String {
374        let s = match self.base {
375            Base::Binary => format!("{:b}", self.number),
376            Base::Octal => format!("{:o}", self.number),
377            Base::Decimal => {
378                if self.is_nonnegative {
379                    self.number.to_string()
380                } else {
381                    let s = I256::from_raw(self.number).to_string();
382                    s.strip_prefix('-').unwrap_or(&s).to_string()
383                }
384            }
385            Base::Hexadecimal => format!("{:x}", self.number),
386        };
387        if s.starts_with('0') { s.trim_start_matches('0').to_string() } else { s }
388    }
389
390    fn _parse_int(s: &str, base: Base) -> Result<(U256, bool)> {
391        let (s, sign) = get_sign(s);
392        let mut n = Self::_parse_uint(s, base)?;
393
394        let is_neg = matches!(sign, Sign::Negative);
395        if is_neg {
396            n = (!n).overflowing_add(U256::from(1)).0;
397        }
398
399        Ok((n, !is_neg))
400    }
401
402    fn _parse_uint(s: &str, base: Base) -> Result<U256> {
403        let s = match s.get(0..2) {
404            Some("0x" | "0X" | "0o" | "0O" | "0b" | "0B") => &s[2..],
405            _ => s,
406        };
407        U256::from_str_radix(s, base as u64).map_err(Into::into)
408    }
409}
410
411/* ------------------------------------------- ToBase ------------------------------------------- */
412
413/// Facilitates formatting an integer into a [Base].
414pub trait ToBase {
415    type Err;
416
417    /// Formats self into a base, specifying whether to add the base prefix or not.
418    ///
419    /// Tries converting `self` into a [NumberWithBase] and then formats into the provided base by
420    /// using the [Debug] implementation.
421    ///
422    /// # Example
423    ///
424    /// ```
425    /// use alloy_primitives::U256;
426    /// use cast::base::{Base, ToBase};
427    ///
428    /// // Any type that implements ToBase
429    /// let number = U256::from(12345);
430    /// assert_eq!(number.to_base(Base::Decimal, false).unwrap(), "12345");
431    /// assert_eq!(number.to_base(Base::Hexadecimal, false).unwrap(), "3039");
432    /// assert_eq!(number.to_base(Base::Hexadecimal, true).unwrap(), "0x3039");
433    /// assert_eq!(number.to_base(Base::Binary, true).unwrap(), "0b11000000111001");
434    /// assert_eq!(number.to_base(Base::Octal, true).unwrap(), "0o30071");
435    /// ```
436    fn to_base(&self, base: Base, add_prefix: bool) -> Result<String, Self::Err>;
437}
438
439impl ToBase for NumberWithBase {
440    type Err = Infallible;
441
442    fn to_base(&self, base: Base, add_prefix: bool) -> Result<String, Self::Err> {
443        let n = self.with_base(base);
444        if add_prefix { Ok(format!("{n:#?}")) } else { Ok(format!("{n:?}")) }
445    }
446}
447
448impl ToBase for I256 {
449    type Err = Infallible;
450
451    fn to_base(&self, base: Base, add_prefix: bool) -> Result<String, Self::Err> {
452        let n = NumberWithBase::from(*self).with_base(base);
453        if add_prefix { Ok(format!("{n:#?}")) } else { Ok(format!("{n:?}")) }
454    }
455}
456
457impl ToBase for U256 {
458    type Err = Infallible;
459
460    fn to_base(&self, base: Base, add_prefix: bool) -> Result<String, Self::Err> {
461        let n = NumberWithBase::from(*self).with_base(base);
462        if add_prefix { Ok(format!("{n:#?}")) } else { Ok(format!("{n:?}")) }
463    }
464}
465
466impl ToBase for String {
467    type Err = eyre::Report;
468
469    fn to_base(&self, base: Base, add_prefix: bool) -> Result<String, Self::Err> {
470        str::to_base(self, base, add_prefix)
471    }
472}
473
474impl ToBase for str {
475    type Err = eyre::Report;
476
477    fn to_base(&self, base: Base, add_prefix: bool) -> Result<String, Self::Err> {
478        let n = NumberWithBase::from_str(self)?.with_base(base);
479        if add_prefix { Ok(format!("{n:#?}")) } else { Ok(format!("{n:?}")) }
480    }
481}
482
483fn get_sign(s: &str) -> (&str, Sign) {
484    match s.as_bytes().first() {
485        Some(b'+') => (&s[1..], Sign::Positive),
486        Some(b'-') => (&s[1..], Sign::Negative),
487        _ => (s, Sign::Positive),
488    }
489}
490
491#[cfg(test)]
492mod tests {
493    use super::*;
494    use Base::*;
495
496    const POS_NUM: [i128; 44] = [
497        1,
498        2,
499        3,
500        5,
501        7,
502        8,
503        10,
504        11,
505        13,
506        16,
507        17,
508        19,
509        23,
510        29,
511        31,
512        32,
513        37,
514        41,
515        43,
516        47,
517        53,
518        59,
519        61,
520        64,
521        67,
522        71,
523        73,
524        79,
525        83,
526        89,
527        97,
528        100,
529        128,
530        200,
531        333,
532        500,
533        666,
534        1000,
535        6666,
536        10000,
537        i16::MAX as i128,
538        i32::MAX as i128,
539        i64::MAX as i128,
540        i128::MAX,
541    ];
542
543    const NEG_NUM: [i128; 44] = [
544        -1,
545        -2,
546        -3,
547        -5,
548        -7,
549        -8,
550        -10,
551        -11,
552        -13,
553        -16,
554        -17,
555        -19,
556        -23,
557        -29,
558        -31,
559        -32,
560        -37,
561        -41,
562        -43,
563        -47,
564        -53,
565        -59,
566        -61,
567        -64,
568        -67,
569        -71,
570        -73,
571        -79,
572        -83,
573        -89,
574        -97,
575        -100,
576        -128,
577        -200,
578        -333,
579        -500,
580        -666,
581        -1000,
582        -6666,
583        -10000,
584        i16::MIN as i128,
585        i32::MIN as i128,
586        i64::MIN as i128,
587        i128::MIN,
588    ];
589
590    #[test]
591    fn test_defaults() {
592        let def: Base = Default::default();
593        assert!(matches!(def, Decimal));
594
595        let n: NumberWithBase = U256::ZERO.into();
596        assert!(matches!(n.base, Decimal));
597        let n: NumberWithBase = I256::ZERO.into();
598        assert!(matches!(n.base, Decimal));
599    }
600
601    #[test]
602    fn can_parse_base() {
603        assert_eq!("2".parse::<Base>().unwrap(), Binary);
604        assert_eq!("b".parse::<Base>().unwrap(), Binary);
605        assert_eq!("bin".parse::<Base>().unwrap(), Binary);
606        assert_eq!("binary".parse::<Base>().unwrap(), Binary);
607
608        assert_eq!("8".parse::<Base>().unwrap(), Octal);
609        assert_eq!("o".parse::<Base>().unwrap(), Octal);
610        assert_eq!("oct".parse::<Base>().unwrap(), Octal);
611        assert_eq!("octal".parse::<Base>().unwrap(), Octal);
612
613        assert_eq!("10".parse::<Base>().unwrap(), Decimal);
614        assert_eq!("d".parse::<Base>().unwrap(), Decimal);
615        assert_eq!("dec".parse::<Base>().unwrap(), Decimal);
616        assert_eq!("decimal".parse::<Base>().unwrap(), Decimal);
617
618        assert_eq!("16".parse::<Base>().unwrap(), Hexadecimal);
619        assert_eq!("h".parse::<Base>().unwrap(), Hexadecimal);
620        assert_eq!("hex".parse::<Base>().unwrap(), Hexadecimal);
621        assert_eq!("hexadecimal".parse::<Base>().unwrap(), Hexadecimal);
622    }
623
624    #[test]
625    fn can_detect_base() {
626        assert_eq!(Base::detect("0b100").unwrap(), Binary);
627        assert_eq!(Base::detect("0o100").unwrap(), Octal);
628        assert_eq!(Base::detect("100").unwrap(), Decimal);
629        assert_eq!(Base::detect("0x100").unwrap(), Hexadecimal);
630
631        assert_eq!(Base::detect("0123456789abcdef").unwrap(), Hexadecimal);
632
633        let _ = Base::detect("0b234abc").unwrap_err();
634        let _ = Base::detect("0o89cba").unwrap_err();
635        let _ = Base::detect("0123456789abcdefg").unwrap_err();
636        let _ = Base::detect("0x123abclpmk").unwrap_err();
637        let _ = Base::detect("hello world").unwrap_err();
638    }
639
640    #[test]
641    fn test_format_pos() {
642        let expected_2: Vec<_> = POS_NUM.iter().map(|n| format!("{n:b}")).collect();
643        let expected_8: Vec<_> = POS_NUM.iter().map(|n| format!("{n:o}")).collect();
644        let expected_10: Vec<_> = POS_NUM.iter().map(|n| format!("{n:}")).collect();
645        let expected_l16: Vec<_> = POS_NUM.iter().map(|n| format!("{n:x}")).collect();
646        let expected_u16: Vec<_> = POS_NUM.iter().map(|n| format!("{n:X}")).collect();
647
648        for (i, n) in POS_NUM.into_iter().enumerate() {
649            let mut num: NumberWithBase = I256::try_from(n).unwrap().into();
650
651            assert_eq!(num.set_base(Binary).format(), expected_2[i]);
652            assert_eq!(num.set_base(Octal).format(), expected_8[i]);
653            assert_eq!(num.set_base(Decimal).format(), expected_10[i]);
654            assert_eq!(num.set_base(Hexadecimal).format(), expected_l16[i]);
655            assert_eq!(num.set_base(Hexadecimal).format().to_uppercase(), expected_u16[i]);
656        }
657    }
658
659    #[test]
660    fn test_format_neg() {
661        // underlying is 256 bits so we have to pad left manually
662
663        let expected_2: Vec<_> = NEG_NUM.iter().map(|n| format!("{n:1>256b}")).collect();
664        let expected_8: Vec<_> = NEG_NUM
665            .iter()
666            .map(|n| {
667                let i = I256::try_from(*n).unwrap();
668                let mut u = NumberWithBase::from(i);
669                u.set_base(Octal);
670                u.format()
671            })
672            .collect();
673        // Sign not included, see NumberWithBase::format
674        let expected_10: Vec<_> =
675            NEG_NUM.iter().map(|n| format!("{n:}").trim_matches('-').to_string()).collect();
676        let expected_l16: Vec<_> = NEG_NUM.iter().map(|n| format!("{n:f>64x}")).collect();
677        let expected_u16: Vec<_> = NEG_NUM.iter().map(|n| format!("{n:F>64X}")).collect();
678
679        for (i, n) in NEG_NUM.into_iter().enumerate() {
680            let mut num: NumberWithBase = I256::try_from(n).unwrap().into();
681
682            assert_eq!(num.set_base(Binary).format(), expected_2[i]);
683            assert_eq!(num.set_base(Octal).format(), expected_8[i]);
684            assert_eq!(num.set_base(Decimal).format(), expected_10[i]);
685            assert_eq!(num.set_base(Hexadecimal).format(), expected_l16[i]);
686            assert_eq!(num.set_base(Hexadecimal).format().to_uppercase(), expected_u16[i]);
687        }
688    }
689
690    #[test]
691    fn test_fmt_macro() {
692        let nums: Vec<_> =
693            POS_NUM.into_iter().map(|n| NumberWithBase::from(I256::try_from(n).unwrap())).collect();
694
695        let actual_2: Vec<_> = nums.iter().map(|n| format!("{n:b}")).collect();
696        let actual_2_alt: Vec<_> = nums.iter().map(|n| format!("{n:#b}")).collect();
697        let actual_8: Vec<_> = nums.iter().map(|n| format!("{n:o}")).collect();
698        let actual_8_alt: Vec<_> = nums.iter().map(|n| format!("{n:#o}")).collect();
699        let actual_10: Vec<_> = nums.iter().map(|n| format!("{n:}")).collect();
700        let actual_10_alt: Vec<_> = nums.iter().map(|n| format!("{n:#}")).collect();
701        let actual_l16: Vec<_> = nums.iter().map(|n| format!("{n:x}")).collect();
702        let actual_l16_alt: Vec<_> = nums.iter().map(|n| format!("{n:#x}")).collect();
703        let actual_u16: Vec<_> = nums.iter().map(|n| format!("{n:X}")).collect();
704        let actual_u16_alt: Vec<_> = nums.iter().map(|n| format!("{n:#X}")).collect();
705
706        let expected_2: Vec<_> = POS_NUM.iter().map(|n| format!("{n:b}")).collect();
707        let expected_2_alt: Vec<_> = POS_NUM.iter().map(|n| format!("{n:#b}")).collect();
708        let expected_8: Vec<_> = POS_NUM.iter().map(|n| format!("{n:o}")).collect();
709        let expected_8_alt: Vec<_> = POS_NUM.iter().map(|n| format!("{n:#o}")).collect();
710        let expected_10: Vec<_> = POS_NUM.iter().map(|n| format!("{n:}")).collect();
711        let expected_10_alt: Vec<_> = POS_NUM.iter().map(|n| format!("{n:#}")).collect();
712        let expected_l16: Vec<_> = POS_NUM.iter().map(|n| format!("{n:x}")).collect();
713        let expected_l16_alt: Vec<_> = POS_NUM.iter().map(|n| format!("{n:#x}")).collect();
714        let expected_u16: Vec<_> = POS_NUM.iter().map(|n| format!("{n:X}")).collect();
715        let expected_u16_alt: Vec<_> = POS_NUM.iter().map(|n| format!("{n:#X}")).collect();
716
717        for (i, _) in POS_NUM.iter().enumerate() {
718            assert_eq!(actual_2[i], expected_2[i]);
719            assert_eq!(actual_2_alt[i], expected_2_alt[i]);
720
721            assert_eq!(actual_8[i], expected_8[i]);
722            assert_eq!(actual_8_alt[i], expected_8_alt[i]);
723
724            assert_eq!(actual_10[i], expected_10[i]);
725            assert_eq!(actual_10_alt[i], expected_10_alt[i]);
726
727            assert_eq!(actual_l16[i], expected_l16[i]);
728            assert_eq!(actual_l16_alt[i], expected_l16_alt[i]);
729
730            assert_eq!(actual_u16[i], expected_u16[i]);
731            assert_eq!(actual_u16_alt[i], expected_u16_alt[i]);
732        }
733    }
734}