ruint/
fmt.rs

1#![allow(clippy::missing_inline_in_public_items)] // allow format functions
2
3use crate::Uint;
4use core::{
5    fmt::{self, Write},
6    mem::MaybeUninit,
7};
8
9mod base {
10    pub(super) trait Base {
11        /// The base.
12        const BASE: u64;
13        /// The prefix for the base.
14        const PREFIX: &'static str;
15
16        /// Highest power of the base that fits in a `u64`.
17        const MAX: u64 = crate::utils::max_pow_u64(Self::BASE);
18        /// Number of characters written using `MAX` as the base in
19        /// `to_base_be`.
20        // TODO(MSRV-1.67): = `Self::MAX.ilog(Self::BASE)`
21        const WIDTH: usize;
22    }
23
24    pub(super) struct Binary;
25    impl Base for Binary {
26        const BASE: u64 = 2;
27        const PREFIX: &'static str = "0b";
28        const WIDTH: usize = 63;
29    }
30
31    pub(super) struct Octal;
32    impl Base for Octal {
33        const BASE: u64 = 8;
34        const PREFIX: &'static str = "0o";
35        const WIDTH: usize = 21;
36    }
37
38    pub(super) struct Decimal;
39    impl Base for Decimal {
40        const BASE: u64 = 10;
41        const PREFIX: &'static str = "";
42        const WIDTH: usize = 19;
43    }
44
45    pub(super) struct Hexadecimal;
46    impl Base for Hexadecimal {
47        const BASE: u64 = 16;
48        const PREFIX: &'static str = "0x";
49        const WIDTH: usize = 15;
50    }
51}
52use base::Base;
53
54macro_rules! impl_fmt {
55    ($tr:path; $base:ty, $base_char:literal) => {
56        impl<const BITS: usize, const LIMBS: usize> $tr for Uint<BITS, LIMBS> {
57            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58                if let Ok(small) = u64::try_from(self) {
59                    return <u64 as $tr>::fmt(&small, f);
60                }
61                if let Ok(small) = u128::try_from(self) {
62                    return <u128 as $tr>::fmt(&small, f);
63                }
64
65                // Use `BITS` for all bases since `generic_const_exprs` is not yet stable.
66                let mut s = StackString::<BITS>::new();
67                let mut first = true;
68                for spigot in self.to_base_be_2(<$base>::MAX) {
69                    write!(
70                        s,
71                        concat!("{:0width$", $base_char, "}"),
72                        spigot,
73                        width = if first { 0 } else { <$base>::WIDTH },
74                    )
75                    .unwrap();
76                    first = false;
77                }
78                f.pad_integral(true, <$base>::PREFIX, s.as_str())
79            }
80        }
81    };
82}
83
84impl<const BITS: usize, const LIMBS: usize> fmt::Debug for Uint<BITS, LIMBS> {
85    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86        fmt::Display::fmt(self, f)
87    }
88}
89
90impl_fmt!(fmt::Display; base::Decimal, "");
91impl_fmt!(fmt::Binary; base::Binary, "b");
92impl_fmt!(fmt::Octal; base::Octal, "o");
93impl_fmt!(fmt::LowerHex; base::Hexadecimal, "x");
94impl_fmt!(fmt::UpperHex; base::Hexadecimal, "X");
95
96/// A stack-allocated buffer that implements [`fmt::Write`].
97pub(crate) struct StackString<const SIZE: usize> {
98    len: usize,
99    buf: [MaybeUninit<u8>; SIZE],
100}
101
102impl<const SIZE: usize> StackString<SIZE> {
103    #[inline]
104    pub(crate) const fn new() -> Self {
105        Self {
106            len: 0,
107            buf: unsafe { MaybeUninit::uninit().assume_init() },
108        }
109    }
110
111    #[inline]
112    pub(crate) const fn as_str(&self) -> &str {
113        // SAFETY: `buf` is only written to by the `fmt::Write::write_str`
114        // implementation which writes a valid UTF-8 string to `buf` and
115        // correctly sets `len`.
116        unsafe { core::str::from_utf8_unchecked(self.as_bytes()) }
117    }
118
119    #[inline]
120    const fn as_bytes(&self) -> &[u8] {
121        unsafe { core::slice::from_raw_parts(self.buf.as_ptr().cast(), self.len) }
122    }
123}
124
125impl<const SIZE: usize> fmt::Write for StackString<SIZE> {
126    fn write_str(&mut self, s: &str) -> fmt::Result {
127        if self.len + s.len() > SIZE {
128            return Err(fmt::Error);
129        }
130        unsafe {
131            let dst = self.buf.as_mut_ptr().add(self.len).cast();
132            core::ptr::copy_nonoverlapping(s.as_ptr(), dst, s.len());
133        }
134        self.len += s.len();
135        Ok(())
136    }
137
138    fn write_char(&mut self, c: char) -> fmt::Result {
139        let clen = c.len_utf8();
140        if self.len + clen > SIZE {
141            return Err(fmt::Error);
142        }
143        c.encode_utf8(unsafe {
144            core::slice::from_raw_parts_mut(self.buf.as_mut_ptr().add(self.len).cast(), clen)
145        });
146        self.len += clen;
147        Ok(())
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154    use proptest::{prop_assert_eq, proptest};
155
156    #[allow(unused_imports)]
157    use alloc::string::ToString;
158
159    #[allow(clippy::unreadable_literal)]
160    const N: Uint<256, 4> = Uint::from_limbs([
161        0xa8ec92344438aaf4_u64,
162        0x9819ebdbd1faaab1_u64,
163        0x573b1a7064c19c1a_u64,
164        0xc85ef7d79691fe79_u64,
165    ]);
166
167    #[test]
168    fn test_num() {
169        assert_eq!(
170            N.to_string(),
171            "90630363884335538722706632492458228784305343302099024356772372330524102404852"
172        );
173        assert_eq!(
174            format!("{N:x}"),
175            "c85ef7d79691fe79573b1a7064c19c1a9819ebdbd1faaab1a8ec92344438aaf4"
176        );
177        assert_eq!(
178            format!("{N:b}"),
179            "1100100001011110111101111101011110010110100100011111111001111001010101110011101100011010011100000110010011000001100111000001101010011000000110011110101111011011110100011111101010101010101100011010100011101100100100100011010001000100001110001010101011110100"
180        );
181        assert_eq!(
182            format!("{N:o}"),
183            "14413675753626443771712563543234062301470152300636573364375252543243544443210416125364"
184        );
185    }
186
187    #[test]
188    fn test_fmt() {
189        proptest!(|(value: u128)| {
190            let n: Uint<128, 2> = Uint::from(value);
191
192            prop_assert_eq!(format!("{n:b}"), format!("{value:b}"));
193            prop_assert_eq!(format!("{n:064b}"), format!("{value:064b}"));
194            prop_assert_eq!(format!("{n:#b}"), format!("{value:#b}"));
195
196            prop_assert_eq!(format!("{n:o}"), format!("{value:o}"));
197            prop_assert_eq!(format!("{n:064o}"), format!("{value:064o}"));
198            prop_assert_eq!(format!("{n:#o}"), format!("{value:#o}"));
199
200            prop_assert_eq!(format!("{n:}"), format!("{value:}"));
201            prop_assert_eq!(format!("{n:064}"), format!("{value:064}"));
202            prop_assert_eq!(format!("{n:#}"), format!("{value:#}"));
203            prop_assert_eq!(format!("{n:?}"), format!("{value:?}"));
204            prop_assert_eq!(format!("{n:064}"), format!("{value:064?}"));
205            prop_assert_eq!(format!("{n:#?}"), format!("{value:#?}"));
206
207            prop_assert_eq!(format!("{n:x}"), format!("{value:x}"));
208            prop_assert_eq!(format!("{n:064x}"), format!("{value:064x}"));
209            prop_assert_eq!(format!("{n:#x}"), format!("{value:#x}"));
210
211            prop_assert_eq!(format!("{n:X}"), format!("{value:X}"));
212            prop_assert_eq!(format!("{n:064X}"), format!("{value:064X}"));
213            prop_assert_eq!(format!("{n:#X}"), format!("{value:#X}"));
214        });
215    }
216}