cast/
base.rs

1use alloy_primitives::{utils::ParseUnits, Sign, I256, U256};
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') {
388            s.trim_start_matches('0').to_string()
389        } else {
390            s
391        }
392    }
393
394    fn _parse_int(s: &str, base: Base) -> Result<(U256, bool)> {
395        let (s, sign) = get_sign(s);
396        let mut n = Self::_parse_uint(s, base)?;
397
398        let is_neg = matches!(sign, Sign::Negative);
399        if is_neg {
400            n = (!n).overflowing_add(U256::from(1)).0;
401        }
402
403        Ok((n, !is_neg))
404    }
405
406    fn _parse_uint(s: &str, base: Base) -> Result<U256> {
407        let s = match s.get(0..2) {
408            Some("0x" | "0X" | "0o" | "0O" | "0b" | "0B") => &s[2..],
409            _ => s,
410        };
411        U256::from_str_radix(s, base as u64).map_err(Into::into)
412    }
413}
414
415/* ------------------------------------------- ToBase ------------------------------------------- */
416
417/// Facilitates formatting an integer into a [Base].
418pub trait ToBase {
419    type Err;
420
421    /// Formats self into a base, specifying whether to add the base prefix or not.
422    ///
423    /// Tries converting `self` into a [NumberWithBase] and then formats into the provided base by
424    /// using the [Debug] implementation.
425    ///
426    /// # Example
427    ///
428    /// ```
429    /// use alloy_primitives::U256;
430    /// use cast::base::{Base, ToBase};
431    ///
432    /// // Any type that implements ToBase
433    /// let number = U256::from(12345);
434    /// assert_eq!(number.to_base(Base::Decimal, false).unwrap(), "12345");
435    /// assert_eq!(number.to_base(Base::Hexadecimal, false).unwrap(), "3039");
436    /// assert_eq!(number.to_base(Base::Hexadecimal, true).unwrap(), "0x3039");
437    /// assert_eq!(number.to_base(Base::Binary, true).unwrap(), "0b11000000111001");
438    /// assert_eq!(number.to_base(Base::Octal, true).unwrap(), "0o30071");
439    /// ```
440    fn to_base(&self, base: Base, add_prefix: bool) -> Result<String, Self::Err>;
441}
442
443impl ToBase for NumberWithBase {
444    type Err = Infallible;
445
446    fn to_base(&self, base: Base, add_prefix: bool) -> Result<String, Self::Err> {
447        let n = self.with_base(base);
448        if add_prefix {
449            Ok(format!("{n:#?}"))
450        } else {
451            Ok(format!("{n:?}"))
452        }
453    }
454}
455
456impl ToBase for I256 {
457    type Err = Infallible;
458
459    fn to_base(&self, base: Base, add_prefix: bool) -> Result<String, Self::Err> {
460        let n = NumberWithBase::from(*self).with_base(base);
461        if add_prefix {
462            Ok(format!("{n:#?}"))
463        } else {
464            Ok(format!("{n:?}"))
465        }
466    }
467}
468
469impl ToBase for U256 {
470    type Err = Infallible;
471
472    fn to_base(&self, base: Base, add_prefix: bool) -> Result<String, Self::Err> {
473        let n = NumberWithBase::from(*self).with_base(base);
474        if add_prefix {
475            Ok(format!("{n:#?}"))
476        } else {
477            Ok(format!("{n:?}"))
478        }
479    }
480}
481
482impl ToBase for String {
483    type Err = eyre::Report;
484
485    fn to_base(&self, base: Base, add_prefix: bool) -> Result<String, Self::Err> {
486        str::to_base(self, base, add_prefix)
487    }
488}
489
490impl ToBase for str {
491    type Err = eyre::Report;
492
493    fn to_base(&self, base: Base, add_prefix: bool) -> Result<String, Self::Err> {
494        let n = NumberWithBase::from_str(self)?.with_base(base);
495        if add_prefix {
496            Ok(format!("{n:#?}"))
497        } else {
498            Ok(format!("{n:?}"))
499        }
500    }
501}
502
503fn get_sign(s: &str) -> (&str, Sign) {
504    match s.as_bytes().first() {
505        Some(b'+') => (&s[1..], Sign::Positive),
506        Some(b'-') => (&s[1..], Sign::Negative),
507        _ => (s, Sign::Positive),
508    }
509}
510
511#[cfg(test)]
512mod tests {
513    use super::*;
514    use Base::*;
515
516    const POS_NUM: [i128; 44] = [
517        1,
518        2,
519        3,
520        5,
521        7,
522        8,
523        10,
524        11,
525        13,
526        16,
527        17,
528        19,
529        23,
530        29,
531        31,
532        32,
533        37,
534        41,
535        43,
536        47,
537        53,
538        59,
539        61,
540        64,
541        67,
542        71,
543        73,
544        79,
545        83,
546        89,
547        97,
548        100,
549        128,
550        200,
551        333,
552        500,
553        666,
554        1000,
555        6666,
556        10000,
557        i16::MAX as i128,
558        i32::MAX as i128,
559        i64::MAX as i128,
560        i128::MAX,
561    ];
562
563    const NEG_NUM: [i128; 44] = [
564        -1,
565        -2,
566        -3,
567        -5,
568        -7,
569        -8,
570        -10,
571        -11,
572        -13,
573        -16,
574        -17,
575        -19,
576        -23,
577        -29,
578        -31,
579        -32,
580        -37,
581        -41,
582        -43,
583        -47,
584        -53,
585        -59,
586        -61,
587        -64,
588        -67,
589        -71,
590        -73,
591        -79,
592        -83,
593        -89,
594        -97,
595        -100,
596        -128,
597        -200,
598        -333,
599        -500,
600        -666,
601        -1000,
602        -6666,
603        -10000,
604        i16::MIN as i128,
605        i32::MIN as i128,
606        i64::MIN as i128,
607        i128::MIN,
608    ];
609
610    #[test]
611    fn test_defaults() {
612        let def: Base = Default::default();
613        assert!(matches!(def, Decimal));
614
615        let n: NumberWithBase = U256::ZERO.into();
616        assert!(matches!(n.base, Decimal));
617        let n: NumberWithBase = I256::ZERO.into();
618        assert!(matches!(n.base, Decimal));
619    }
620
621    #[test]
622    fn can_parse_base() {
623        assert_eq!("2".parse::<Base>().unwrap(), Binary);
624        assert_eq!("b".parse::<Base>().unwrap(), Binary);
625        assert_eq!("bin".parse::<Base>().unwrap(), Binary);
626        assert_eq!("binary".parse::<Base>().unwrap(), Binary);
627
628        assert_eq!("8".parse::<Base>().unwrap(), Octal);
629        assert_eq!("o".parse::<Base>().unwrap(), Octal);
630        assert_eq!("oct".parse::<Base>().unwrap(), Octal);
631        assert_eq!("octal".parse::<Base>().unwrap(), Octal);
632
633        assert_eq!("10".parse::<Base>().unwrap(), Decimal);
634        assert_eq!("d".parse::<Base>().unwrap(), Decimal);
635        assert_eq!("dec".parse::<Base>().unwrap(), Decimal);
636        assert_eq!("decimal".parse::<Base>().unwrap(), Decimal);
637
638        assert_eq!("16".parse::<Base>().unwrap(), Hexadecimal);
639        assert_eq!("h".parse::<Base>().unwrap(), Hexadecimal);
640        assert_eq!("hex".parse::<Base>().unwrap(), Hexadecimal);
641        assert_eq!("hexadecimal".parse::<Base>().unwrap(), Hexadecimal);
642    }
643
644    #[test]
645    fn can_detect_base() {
646        assert_eq!(Base::detect("0b100").unwrap(), Binary);
647        assert_eq!(Base::detect("0o100").unwrap(), Octal);
648        assert_eq!(Base::detect("100").unwrap(), Decimal);
649        assert_eq!(Base::detect("0x100").unwrap(), Hexadecimal);
650
651        assert_eq!(Base::detect("0123456789abcdef").unwrap(), Hexadecimal);
652
653        let _ = Base::detect("0b234abc").unwrap_err();
654        let _ = Base::detect("0o89cba").unwrap_err();
655        let _ = Base::detect("0123456789abcdefg").unwrap_err();
656        let _ = Base::detect("0x123abclpmk").unwrap_err();
657        let _ = Base::detect("hello world").unwrap_err();
658    }
659
660    #[test]
661    fn test_format_pos() {
662        let expected_2: Vec<_> = POS_NUM.iter().map(|n| format!("{n:b}")).collect();
663        let expected_8: Vec<_> = POS_NUM.iter().map(|n| format!("{n:o}")).collect();
664        let expected_10: Vec<_> = POS_NUM.iter().map(|n| format!("{n:}")).collect();
665        let expected_l16: Vec<_> = POS_NUM.iter().map(|n| format!("{n:x}")).collect();
666        let expected_u16: Vec<_> = POS_NUM.iter().map(|n| format!("{n:X}")).collect();
667
668        for (i, n) in POS_NUM.into_iter().enumerate() {
669            let mut num: NumberWithBase = I256::try_from(n).unwrap().into();
670
671            assert_eq!(num.set_base(Binary).format(), expected_2[i]);
672            assert_eq!(num.set_base(Octal).format(), expected_8[i]);
673            assert_eq!(num.set_base(Decimal).format(), expected_10[i]);
674            assert_eq!(num.set_base(Hexadecimal).format(), expected_l16[i]);
675            assert_eq!(num.set_base(Hexadecimal).format().to_uppercase(), expected_u16[i]);
676        }
677    }
678
679    // TODO: test for octal
680    #[test]
681    fn test_format_neg() {
682        // underlying is 256 bits so we have to pad left manually
683
684        let expected_2: Vec<_> = NEG_NUM.iter().map(|n| format!("{n:1>256b}")).collect();
685        // let expected_8: Vec<_> = NEG_NUM.iter().map(|n| format!("1{:7>85o}", n)).collect();
686        // Sign not included, see NumberWithBase::format
687        let expected_10: Vec<_> =
688            NEG_NUM.iter().map(|n| format!("{n:}").trim_matches('-').to_string()).collect();
689        let expected_l16: Vec<_> = NEG_NUM.iter().map(|n| format!("{n:f>64x}")).collect();
690        let expected_u16: Vec<_> = NEG_NUM.iter().map(|n| format!("{n:F>64X}")).collect();
691
692        for (i, n) in NEG_NUM.into_iter().enumerate() {
693            let mut num: NumberWithBase = I256::try_from(n).unwrap().into();
694
695            assert_eq!(num.set_base(Binary).format(), expected_2[i]);
696            // assert_eq!(num.set_base(Octal).format(), expected_8[i]);
697            assert_eq!(num.set_base(Decimal).format(), expected_10[i]);
698            assert_eq!(num.set_base(Hexadecimal).format(), expected_l16[i]);
699            assert_eq!(num.set_base(Hexadecimal).format().to_uppercase(), expected_u16[i]);
700        }
701    }
702
703    #[test]
704    fn test_fmt_macro() {
705        let nums: Vec<_> =
706            POS_NUM.into_iter().map(|n| NumberWithBase::from(I256::try_from(n).unwrap())).collect();
707
708        let actual_2: Vec<_> = nums.iter().map(|n| format!("{n:b}")).collect();
709        let actual_2_alt: Vec<_> = nums.iter().map(|n| format!("{n:#b}")).collect();
710        let actual_8: Vec<_> = nums.iter().map(|n| format!("{n:o}")).collect();
711        let actual_8_alt: Vec<_> = nums.iter().map(|n| format!("{n:#o}")).collect();
712        let actual_10: Vec<_> = nums.iter().map(|n| format!("{n:}")).collect();
713        let actual_10_alt: Vec<_> = nums.iter().map(|n| format!("{n:#}")).collect();
714        let actual_l16: Vec<_> = nums.iter().map(|n| format!("{n:x}")).collect();
715        let actual_l16_alt: Vec<_> = nums.iter().map(|n| format!("{n:#x}")).collect();
716        let actual_u16: Vec<_> = nums.iter().map(|n| format!("{n:X}")).collect();
717        let actual_u16_alt: Vec<_> = nums.iter().map(|n| format!("{n:#X}")).collect();
718
719        let expected_2: Vec<_> = POS_NUM.iter().map(|n| format!("{n:b}")).collect();
720        let expected_2_alt: Vec<_> = POS_NUM.iter().map(|n| format!("{n:#b}")).collect();
721        let expected_8: Vec<_> = POS_NUM.iter().map(|n| format!("{n:o}")).collect();
722        let expected_8_alt: Vec<_> = POS_NUM.iter().map(|n| format!("{n:#o}")).collect();
723        let expected_10: Vec<_> = POS_NUM.iter().map(|n| format!("{n:}")).collect();
724        let expected_10_alt: Vec<_> = POS_NUM.iter().map(|n| format!("{n:#}")).collect();
725        let expected_l16: Vec<_> = POS_NUM.iter().map(|n| format!("{n:x}")).collect();
726        let expected_l16_alt: Vec<_> = POS_NUM.iter().map(|n| format!("{n:#x}")).collect();
727        let expected_u16: Vec<_> = POS_NUM.iter().map(|n| format!("{n:X}")).collect();
728        let expected_u16_alt: Vec<_> = POS_NUM.iter().map(|n| format!("{n:#X}")).collect();
729
730        for (i, _) in POS_NUM.iter().enumerate() {
731            assert_eq!(actual_2[i], expected_2[i]);
732            assert_eq!(actual_2_alt[i], expected_2_alt[i]);
733
734            assert_eq!(actual_8[i], expected_8[i]);
735            assert_eq!(actual_8_alt[i], expected_8_alt[i]);
736
737            assert_eq!(actual_10[i], expected_10[i]);
738            assert_eq!(actual_10_alt[i], expected_10_alt[i]);
739
740            assert_eq!(actual_l16[i], expected_l16[i]);
741            assert_eq!(actual_l16_alt[i], expected_l16_alt[i]);
742
743            assert_eq!(actual_u16[i], expected_u16[i]);
744            assert_eq!(actual_u16_alt[i], expected_u16_alt[i]);
745        }
746    }
747}