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