malachite_base/num/conversion/string/
to_string.rs

1// Copyright © 2025 Mikhail Hogrefe
2//
3// This file is part of Malachite.
4//
5// Malachite is free software: you can redistribute it and/or modify it under the terms of the GNU
6// Lesser General Public License (LGPL) as published by the Free Software Foundation; either version
7// 3 of the License, or (at your option) any later version. See <https://www.gnu.org/licenses/>.
8
9use crate::num::arithmetic::traits::UnsignedAbs;
10use crate::num::basic::traits::Zero;
11use crate::num::conversion::traits::{Digits, ToStringBase, WrappingFrom};
12use crate::vecs::vec_pad_left;
13use alloc::string::String;
14use alloc::string::ToString;
15use core::fmt::{Debug, Display, Formatter, Result, Write};
16
17/// A `struct` that allows for formatting a numeric type and rendering its digits in a specified
18/// base.
19#[derive(Clone, Eq, Hash, PartialEq)]
20pub struct BaseFmtWrapper<T> {
21    pub(crate) x: T,
22    pub(crate) base: u8,
23}
24
25impl<T> BaseFmtWrapper<T> {
26    /// Creates a new `BaseFmtWrapper`.
27    ///
28    /// # Worst-case complexity
29    /// Constant time and additional memory.
30    ///
31    /// # Panics
32    /// Panics if `base` is less than 2 or greater than 36.
33    ///
34    /// # Examples
35    /// ```
36    /// use malachite_base::num::conversion::string::to_string::BaseFmtWrapper;
37    ///
38    /// let x = BaseFmtWrapper::new(1000000000u32, 36);
39    /// assert_eq!(format!("{}", x), "gjdgxs");
40    /// assert_eq!(format!("{:#}", x), "GJDGXS");
41    /// ```
42    pub fn new(x: T, base: u8) -> Self {
43        assert!((2..=36).contains(&base), "base out of range");
44        BaseFmtWrapper { x, base }
45    }
46
47    /// Recovers the value from a `BaseFmtWrapper`.
48    ///
49    /// # Worst-case complexity
50    /// Constant time and additional memory.
51    ///
52    /// # Examples
53    /// ```
54    /// use malachite_base::num::conversion::string::to_string::BaseFmtWrapper;
55    ///
56    /// assert_eq!(BaseFmtWrapper::new(1000000000u32, 36).unwrap(), 1000000000);
57    /// ```
58    #[allow(clippy::missing_const_for_fn)]
59    pub fn unwrap(self) -> T {
60        self.x
61    }
62}
63
64/// Converts a digit to a byte corresponding to a numeric or lowercase alphabetic [`char`] that
65/// represents the digit.
66///
67/// Digits from 0 to 9 become bytes corresponding to [`char`]s from '0' to '9'. Digits from 10 to 35
68/// become bytes representing the lowercase [`char`]s 'a' to 'z'. Passing a digit greater than 35
69/// gives a `None`.
70///
71/// # Worst-case complexity
72/// Constant time and additional memory.
73///
74/// # Examples
75/// ```
76/// use malachite_base::num::conversion::string::to_string::digit_to_display_byte_lower;
77///
78/// assert_eq!(digit_to_display_byte_lower(0), Some(b'0'));
79/// assert_eq!(digit_to_display_byte_lower(9), Some(b'9'));
80/// assert_eq!(digit_to_display_byte_lower(10), Some(b'a'));
81/// assert_eq!(digit_to_display_byte_lower(35), Some(b'z'));
82/// assert_eq!(digit_to_display_byte_lower(100), None);
83/// ```
84pub const fn digit_to_display_byte_lower(b: u8) -> Option<u8> {
85    match b {
86        0..=9 => Some(b + b'0'),
87        10..=35 => Some(b + b'a' - 10),
88        _ => None,
89    }
90}
91
92/// Converts a digit to a byte corresponding to a numeric or uppercase alphabetic [`char`] that
93/// represents the digit.
94///
95/// Digits from 0 to 9 become bytes corresponding to [`char`]s from '0' to '9'. Digits from 10 to 35
96/// become bytes representing the lowercase [`char`]s 'A' to 'Z'. Passing a digit greater than 35
97/// gives a `None`.
98///
99/// # Worst-case complexity
100/// Constant time and additional memory.
101///
102/// # Examples
103/// ```
104/// use malachite_base::num::conversion::string::to_string::digit_to_display_byte_upper;
105///
106/// assert_eq!(digit_to_display_byte_upper(0), Some(b'0'));
107/// assert_eq!(digit_to_display_byte_upper(9), Some(b'9'));
108/// assert_eq!(digit_to_display_byte_upper(10), Some(b'A'));
109/// assert_eq!(digit_to_display_byte_upper(35), Some(b'Z'));
110/// assert_eq!(digit_to_display_byte_upper(100), None);
111/// ```
112pub const fn digit_to_display_byte_upper(b: u8) -> Option<u8> {
113    match b {
114        0..=9 => Some(b + b'0'),
115        10..=35 => Some(b + b'A' - 10),
116        _ => None,
117    }
118}
119
120fn fmt_unsigned<T: Copy + Digits<u8> + Eq + Zero>(
121    w: &BaseFmtWrapper<T>,
122    f: &mut Formatter,
123) -> Result {
124    let mut digits = w.x.to_digits_desc(&u8::wrapping_from(w.base));
125    if f.alternate() {
126        for digit in &mut digits {
127            *digit = digit_to_display_byte_upper(*digit).unwrap();
128        }
129    } else {
130        for digit in &mut digits {
131            *digit = digit_to_display_byte_lower(*digit).unwrap();
132        }
133    }
134    if w.x == T::ZERO {
135        digits.push(b'0');
136    }
137    f.pad_integral(true, "", core::str::from_utf8(&digits).unwrap())
138}
139
140fn to_string_base_unsigned<T: Copy + Digits<u8> + Eq + Zero>(x: &T, base: u8) -> String {
141    assert!((2..=36).contains(&base), "base out of range");
142    if *x == T::ZERO {
143        "0".to_string()
144    } else {
145        let mut digits = x.to_digits_desc(&base);
146        for digit in &mut digits {
147            *digit = digit_to_display_byte_lower(*digit).unwrap();
148        }
149        String::from_utf8(digits).unwrap()
150    }
151}
152
153fn to_string_base_upper_unsigned<T: Copy + Digits<u8> + Eq + Zero>(x: &T, base: u8) -> String {
154    assert!((2..=36).contains(&base), "base out of range");
155    if *x == T::ZERO {
156        "0".to_string()
157    } else {
158        let mut digits = x.to_digits_desc(&base);
159        for digit in &mut digits {
160            *digit = digit_to_display_byte_upper(*digit).unwrap();
161        }
162        String::from_utf8(digits).unwrap()
163    }
164}
165
166macro_rules! impl_to_string_base_unsigned {
167    ($t:ident) => {
168        impl Display for BaseFmtWrapper<$t> {
169            /// Writes a wrapped unsigned number to a string using a specified base.
170            ///
171            /// If the base is greater than 10, lowercase alphabetic letters are used by default.
172            /// Using the `#` flag switches to uppercase letters. Padding with zeros works as usual.
173            ///
174            /// # Worst-case complexity
175            /// $T(n) = O(n)$
176            ///
177            /// $M(n) = O(n)$
178            ///
179            /// where $T$ is time, $M$ is additional memory, and $n$ is `self.significant_bits()`.
180            ///
181            /// # Panics
182            /// Panics if `base` is less than 2 or greater than 36.
183            ///
184            /// # Examples
185            /// See [here](super::to_string).
186            #[inline]
187            fn fmt(&self, f: &mut Formatter) -> Result {
188                fmt_unsigned(self, f)
189            }
190        }
191
192        impl Debug for BaseFmtWrapper<$t> {
193            /// Writes a wrapped unsigned number to a string using a specified base.
194            ///
195            /// If the base is greater than 10, lowercase alphabetic letters are used by default.
196            /// Using the `#` flag switches to uppercase letters. Padding with zeros works as usual.
197            ///
198            /// This is the same as the [`Display::fmt`] implementation.
199            ///
200            /// # Worst-case complexity
201            /// $T(n) = O(n)$
202            ///
203            /// $M(n) = O(n)$
204            ///
205            /// where $T$ is time, $M$ is additional memory, and $n$ is `self.significant_bits()`.
206            ///
207            /// # Panics
208            /// Panics if `base` is less than 2 or greater than 36.
209            ///
210            /// # Examples
211            /// See [here](super::to_string).
212            #[inline]
213            fn fmt(&self, f: &mut Formatter) -> Result {
214                Display::fmt(self, f)
215            }
216        }
217
218        impl ToStringBase for $t {
219            /// Converts an unsigned number to a string using a specified base.
220            ///
221            /// Digits from 0 to 9 become `char`s from '0' to '9'. Digits from 10 to 35 become the
222            /// lowercase [`char`]s 'a' to 'z'.
223            ///
224            /// # Worst-case complexity
225            /// $T(n) = O(n)$
226            ///
227            /// $M(n) = O(n)$
228            ///
229            /// where $T$ is time, $M$ is additional memory, and $n$ is `self.significant_bits()`.
230            ///
231            /// # Panics
232            /// Panics if `base` is less than 2 or greater than 36.
233            ///
234            /// # Examples
235            /// See [here](super::to_string#to_string_base).
236            #[inline]
237            fn to_string_base(&self, base: u8) -> String {
238                to_string_base_unsigned(self, base)
239            }
240
241            /// Converts an unsigned number to a string using a specified base.
242            ///
243            /// Digits from 0 to 9 become `char`s from '0' to '9'. Digits from 10 to 35 become the
244            /// uppercase [`char`]s 'A' to 'Z'.
245            ///
246            /// # Worst-case complexity
247            /// $T(n) = O(n)$
248            ///
249            /// $M(n) = O(n)$
250            ///
251            /// where $T$ is time, $M$ is additional memory, and $n$ is `self.significant_bits()`.
252            ///
253            /// # Panics
254            /// Panics if `base` is less than 2 or greater than 36.
255            ///
256            /// # Examples
257            /// See [here](super::to_string#to_string_base_upper).
258            #[inline]
259            fn to_string_base_upper(&self, base: u8) -> String {
260                to_string_base_upper_unsigned(self, base)
261            }
262        }
263    };
264}
265apply_to_unsigneds!(impl_to_string_base_unsigned);
266
267fn fmt_signed<T: Copy + Ord + UnsignedAbs + Zero>(
268    w: &BaseFmtWrapper<T>,
269    f: &mut Formatter,
270) -> Result
271where
272    BaseFmtWrapper<<T as UnsignedAbs>::Output>: Display,
273{
274    if w.x < T::ZERO {
275        f.write_char('-')?;
276        if let Some(width) = f.width() {
277            return if f.alternate() {
278                write!(
279                    f,
280                    "{:#0width$}",
281                    &BaseFmtWrapper::new(w.x.unsigned_abs(), w.base),
282                    width = width.saturating_sub(1)
283                )
284            } else {
285                write!(
286                    f,
287                    "{:0width$}",
288                    &BaseFmtWrapper::new(w.x.unsigned_abs(), w.base),
289                    width = width.saturating_sub(1)
290                )
291            };
292        }
293    }
294    Display::fmt(&BaseFmtWrapper::new(w.x.unsigned_abs(), w.base), f)
295}
296
297fn to_string_base_signed<U: Digits<u8>, S: Copy + Eq + Ord + UnsignedAbs<Output = U> + Zero>(
298    x: &S,
299    base: u8,
300) -> String {
301    assert!((2..=36).contains(&base), "base out of range");
302    if *x == S::ZERO {
303        "0".to_string()
304    } else {
305        let mut digits = x.unsigned_abs().to_digits_desc(&u8::wrapping_from(base));
306        for digit in &mut digits {
307            *digit = digit_to_display_byte_lower(*digit).unwrap();
308        }
309        if *x < S::ZERO {
310            vec_pad_left(&mut digits, 1, b'-');
311        }
312        String::from_utf8(digits).unwrap()
313    }
314}
315
316fn to_string_base_upper_signed<
317    U: Digits<u8>,
318    S: Copy + Eq + Ord + UnsignedAbs<Output = U> + Zero,
319>(
320    x: &S,
321    base: u8,
322) -> String {
323    assert!((2..=36).contains(&base), "base out of range");
324    if *x == S::ZERO {
325        "0".to_string()
326    } else {
327        let mut digits = x.unsigned_abs().to_digits_desc(&base);
328        for digit in &mut digits {
329            *digit = digit_to_display_byte_upper(*digit).unwrap();
330        }
331        if *x < S::ZERO {
332            vec_pad_left(&mut digits, 1, b'-');
333        }
334        String::from_utf8(digits).unwrap()
335    }
336}
337
338macro_rules! impl_to_string_base_signed {
339    ($u:ident, $s:ident) => {
340        impl Display for BaseFmtWrapper<$s> {
341            /// Writes a wrapped signed number to a string using a specified base.
342            ///
343            /// If the base is greater than 10, lowercase alphabetic letters are used by default.
344            /// Using the `#` flag switches to uppercase letters. Padding with zeros works as usual.
345            ///
346            /// Unlike with the default implementations of [`Binary`](std::fmt::Binary),
347            /// [`Octal`](std::fmt::Octal), [`LowerHex`](std::fmt::LowerHex), and
348            /// [`UpperHex`](std::fmt::UpperHex), negative numbers are represented using a negative
349            /// sign, not two's complement.
350            ///
351            /// # Worst-case complexity
352            /// $T(n) = O(n)$
353            ///
354            /// $M(n) = O(n)$
355            ///
356            /// where $T$ is time, $M$ is additional memory, and $n$ is `self.significant_bits()`.
357            ///
358            /// # Panics
359            /// Panics if `base` is less than 2 or greater than 36.
360            ///
361            /// # Examples
362            /// See [here](super::to_string).
363            #[inline]
364            fn fmt(&self, f: &mut Formatter) -> Result {
365                fmt_signed(self, f)
366            }
367        }
368
369        impl Debug for BaseFmtWrapper<$s> {
370            /// Writes a wrapped signed number to a string using a specified base.
371            ///
372            /// If the base is greater than 10, lowercase alphabetic letters are used by default.
373            /// Using the `#` flag switches to uppercase letters. Padding with zeros works as usual.
374            ///
375            /// Unlike with the default implementations of [`Binary`](std::fmt::Binary),
376            /// [`Octal`](std::fmt::Octal), [`LowerHex`](std::fmt::LowerHex), and
377            /// [`UpperHex`](std::fmt::UpperHex), negative numbers are represented using a negative
378            /// sign, not two's complement.
379            ///
380            /// This is the same as the [`Display::fmt`] implementation.
381            ///
382            /// # Worst-case complexity
383            /// $T(n) = O(n)$
384            ///
385            /// $M(n) = O(n)$
386            ///
387            /// where $T$ is time, $M$ is additional memory, and $n$ is `self.significant_bits()`.
388            ///
389            /// # Panics
390            /// Panics if `base` is less than 2 or greater than 36.
391            ///
392            /// # Examples
393            /// See [here](super::to_string).
394            #[inline]
395            fn fmt(&self, f: &mut Formatter) -> Result {
396                Display::fmt(self, f)
397            }
398        }
399
400        impl ToStringBase for $s {
401            /// Converts a signed number to a string using a specified base.
402            ///
403            /// Digits from 0 to 9 become `char`s from '0' to '9'. Digits from 10 to 35 become the
404            /// lowercase [`char`]s 'a' to 'z'.
405            ///
406            /// # Worst-case complexity
407            /// $T(n) = O(n)$
408            ///
409            /// $M(n) = O(n)$
410            ///
411            /// where $T$ is time, $M$ is additional memory, and $n$ is `self.significant_bits()`.
412            ///
413            /// # Panics
414            /// Panics if `base` is less than 2 or greater than 36.
415            ///
416            /// # Examples
417            /// See [here](super::to_string#to_string_base).
418            #[inline]
419            fn to_string_base(&self, base: u8) -> String {
420                to_string_base_signed::<$u, $s>(self, base)
421            }
422
423            /// Converts a signed number to a string using a specified base.
424            ///
425            /// Digits from 0 to 9 become `char`s from '0' to '9'. Digits from 10 to 35 become the
426            /// uppercase [`char`]s 'A' to 'Z'.
427            ///
428            /// # Worst-case complexity
429            /// $T(n) = O(n)$
430            ///
431            /// $M(n) = O(n)$
432            ///
433            /// where $T$ is time, $M$ is additional memory, and $n$ is `self.significant_bits()`.
434            ///
435            /// # Panics
436            /// Panics if `base` is less than 2 or greater than 36.
437            ///
438            /// # Examples
439            /// See [here](super::to_string#to_string_base_upper).
440            #[inline]
441            fn to_string_base_upper(&self, base: u8) -> String {
442                to_string_base_upper_signed::<$u, $s>(self, base)
443            }
444        }
445    };
446}
447apply_to_unsigned_signed_pairs!(impl_to_string_base_signed);