Skip to main content

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