Skip to main content

malachite_base/num/conversion/string/
to_string.rs

1// Copyright © 2026 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, ToString};
14use core::fmt::{Debug, Display, Formatter, Result, Write};
15
16/// A `struct` that allows for formatting a numeric type and rendering its digits in a specified
17/// base.
18#[derive(Clone, Eq, Hash, PartialEq)]
19pub struct BaseFmtWrapper<T> {
20    pub(crate) x: T,
21    pub(crate) base: u8,
22}
23
24impl<T> BaseFmtWrapper<T> {
25    /// Creates a new `BaseFmtWrapper`.
26    ///
27    /// # Worst-case complexity
28    /// Constant time and additional memory.
29    ///
30    /// # Panics
31    /// Panics if `base` is less than 2 or greater than 62.
32    ///
33    /// # Examples
34    /// ```
35    /// use malachite_base::num::conversion::string::to_string::BaseFmtWrapper;
36    ///
37    /// let x = BaseFmtWrapper::new(1000000000u32, 36);
38    /// assert_eq!(format!("{}", x), "gjdgxs");
39    /// assert_eq!(format!("{:#}", x), "GJDGXS");
40    /// ```
41    pub fn new(x: T, base: u8) -> Self {
42        assert!((2..=62).contains(&base), "base out of range");
43        Self { x, base }
44    }
45
46    /// Recovers the value from a `BaseFmtWrapper`.
47    ///
48    /// # Worst-case complexity
49    /// Constant time and additional memory.
50    ///
51    /// # Examples
52    /// ```
53    /// use malachite_base::num::conversion::string::to_string::BaseFmtWrapper;
54    ///
55    /// assert_eq!(BaseFmtWrapper::new(1000000000u32, 36).unwrap(), 1000000000);
56    /// ```
57    #[allow(clippy::missing_const_for_fn)]
58    pub fn unwrap(self) -> T {
59        self.x
60    }
61}
62
63/// Converts a digit to a byte corresponding to a numeric or lowercase alphabetic [`char`] that
64/// represents the digit.
65///
66/// Digits from 0 to 9 become bytes corresponding to [`char`]s from '0' to '9'. Digits from 10 to 35
67/// become bytes representing the lowercase [`char`]s 'a' to 'z'. Passing a digit greater than 35
68/// gives a `None`.
69///
70/// # Worst-case complexity
71/// Constant time and additional memory.
72///
73/// # Examples
74/// ```
75/// use malachite_base::num::conversion::string::to_string::digit_to_display_byte_lower;
76///
77/// assert_eq!(digit_to_display_byte_lower(0), Some(b'0'));
78/// assert_eq!(digit_to_display_byte_lower(9), Some(b'9'));
79/// assert_eq!(digit_to_display_byte_lower(10), Some(b'a'));
80/// assert_eq!(digit_to_display_byte_lower(35), Some(b'z'));
81/// assert_eq!(digit_to_display_byte_lower(100), None);
82/// ```
83pub const fn digit_to_display_byte_lower(b: u8) -> Option<u8> {
84    match b {
85        0..=9 => Some(b + b'0'),
86        10..=35 => Some(b + b'a' - 10),
87        _ => None,
88    }
89}
90
91/// Converts a digit to a byte corresponding to a numeric or uppercase alphabetic [`char`] that
92/// represents the digit.
93///
94/// Digits from 0 to 9 become bytes corresponding to [`char`]s from '0' to '9'. Digits from 10 to 35
95/// become bytes representing the lowercase [`char`]s 'A' to 'Z'. Passing a digit greater than 35
96/// gives a `None`.
97///
98/// # Worst-case complexity
99/// Constant time and additional memory.
100///
101/// # Examples
102/// ```
103/// use malachite_base::num::conversion::string::to_string::digit_to_display_byte_upper;
104///
105/// assert_eq!(digit_to_display_byte_upper(0), Some(b'0'));
106/// assert_eq!(digit_to_display_byte_upper(9), Some(b'9'));
107/// assert_eq!(digit_to_display_byte_upper(10), Some(b'A'));
108/// assert_eq!(digit_to_display_byte_upper(35), Some(b'Z'));
109/// assert_eq!(digit_to_display_byte_upper(100), None);
110/// ```
111pub const fn digit_to_display_byte_upper(b: u8) -> Option<u8> {
112    match b {
113        0..=9 => Some(b + b'0'),
114        10..=35 => Some(b + b'A' - 10),
115        _ => None,
116    }
117}
118
119/// Converts a digit to a byte corresponding to a numeric or alphabetic [`char`] in the large-base
120/// alphabet used for bases from 37 through 62, in which the uppercase and lowercase letters are
121/// distinct digits: `b'0'` through `b'9'` represent 0 through 9, `b'A'` through `b'Z'` represent 10
122/// through 35, and `b'a'` through `b'z'` represent 36 through 61, as in GMP.
123///
124/// Digits from 62 on are not associated with any byte, so [`None`] is returned.
125///
126/// # Worst-case complexity
127/// Constant time and additional memory.
128///
129/// # Examples
130/// ```
131/// use malachite_base::num::conversion::string::to_string::digit_to_display_byte_large;
132///
133/// assert_eq!(digit_to_display_byte_large(0), Some(b'0'));
134/// assert_eq!(digit_to_display_byte_large(10), Some(b'A'));
135/// assert_eq!(digit_to_display_byte_large(35), Some(b'Z'));
136/// assert_eq!(digit_to_display_byte_large(36), Some(b'a'));
137/// assert_eq!(digit_to_display_byte_large(61), Some(b'z'));
138/// assert_eq!(digit_to_display_byte_large(62), None);
139/// ```
140pub const fn digit_to_display_byte_large(b: u8) -> Option<u8> {
141    match b {
142        0..=9 => Some(b + b'0'),
143        10..=35 => Some(b + b'A' - 10),
144        36..=61 => Some(b + b'a' - 36),
145        _ => None,
146    }
147}
148
149fn fmt_unsigned<T: Copy + Digits<u8> + Eq + Zero>(
150    w: &BaseFmtWrapper<T>,
151    f: &mut Formatter,
152) -> Result {
153    let mut digits = w.x.to_digits_desc(&u8::wrapping_from(w.base));
154    // Above base 36 the uppercase and lowercase letters are distinct digits, so there is only one
155    // alphabet and the `#` flag has no effect.
156    if w.base > 36 {
157        for digit in &mut digits {
158            *digit = digit_to_display_byte_large(*digit).unwrap();
159        }
160    } else if f.alternate() {
161        for digit in &mut digits {
162            *digit = digit_to_display_byte_upper(*digit).unwrap();
163        }
164    } else {
165        for digit in &mut digits {
166            *digit = digit_to_display_byte_lower(*digit).unwrap();
167        }
168    }
169    if w.x == T::ZERO {
170        digits.push(b'0');
171    }
172    f.pad_integral(true, "", core::str::from_utf8(&digits).unwrap())
173}
174
175fn to_string_base_unsigned<T: Copy + Digits<u8> + Eq + Zero>(x: &T, base: u8) -> String {
176    assert!((2..=62).contains(&base), "base out of range");
177    if *x == T::ZERO {
178        "0".to_string()
179    } else {
180        let mut digits = x.to_digits_desc(&base);
181        let map = if base > 36 {
182            // above base 36 there is only one alphabet, with both cases as distinct digits
183            digit_to_display_byte_large
184        } else {
185            digit_to_display_byte_lower
186        };
187        for digit in &mut digits {
188            *digit = map(*digit).unwrap();
189        }
190        String::from_utf8(digits).unwrap()
191    }
192}
193
194fn to_string_base_upper_unsigned<T: Copy + Digits<u8> + Eq + Zero>(x: &T, base: u8) -> String {
195    assert!((2..=62).contains(&base), "base out of range");
196    if *x == T::ZERO {
197        "0".to_string()
198    } else {
199        let mut digits = x.to_digits_desc(&base);
200        let map = if base > 36 {
201            // above base 36 there is only one alphabet, with both cases as distinct digits
202            digit_to_display_byte_large
203        } else {
204            digit_to_display_byte_upper
205        };
206        for digit in &mut digits {
207            *digit = map(*digit).unwrap();
208        }
209        String::from_utf8(digits).unwrap()
210    }
211}
212
213macro_rules! impl_to_string_base_unsigned {
214    ($t:ident) => {
215        impl Display for BaseFmtWrapper<$t> {
216            /// Writes a wrapped unsigned number to a string using a specified base.
217            ///
218            /// If the base is greater than 10, lowercase alphabetic letters are used by default.
219            /// Using the `#` flag switches to uppercase letters. Padding with zeros works as usual.
220            /// If the base is greater than 36, the uppercase and lowercase letters are distinct
221            /// digits (see
222            /// [`digit_to_display_byte_large`](super::to_string::digit_to_display_byte_large)), and
223            /// the `#` flag has no effect.
224            ///
225            /// # Worst-case complexity
226            /// $T(n) = O(n)$
227            ///
228            /// $M(n) = O(n)$
229            ///
230            /// where $T$ is time, $M$ is additional memory, and $n$ is `self.significant_bits()`.
231            ///
232            /// # Panics
233            /// Panics if `base` is less than 2 or greater than 62.
234            ///
235            /// # Examples
236            /// See [here](super::to_string).
237            #[inline]
238            fn fmt(&self, f: &mut Formatter) -> Result {
239                fmt_unsigned(self, f)
240            }
241        }
242
243        impl Debug for BaseFmtWrapper<$t> {
244            /// Writes a wrapped unsigned number to a string using a specified base.
245            ///
246            /// If the base is greater than 10, lowercase alphabetic letters are used by default.
247            /// Using the `#` flag switches to uppercase letters. Padding with zeros works as usual.
248            /// If the base is greater than 36, the uppercase and lowercase letters are distinct
249            /// digits (see
250            /// [`digit_to_display_byte_large`](super::to_string::digit_to_display_byte_large)), and
251            /// the `#` flag has no effect.
252            ///
253            /// This is the same as the [`Display::fmt`] implementation.
254            ///
255            /// # Worst-case complexity
256            /// $T(n) = O(n)$
257            ///
258            /// $M(n) = O(n)$
259            ///
260            /// where $T$ is time, $M$ is additional memory, and $n$ is `self.significant_bits()`.
261            ///
262            /// # Panics
263            /// Panics if `base` is less than 2 or greater than 62.
264            ///
265            /// # Examples
266            /// See [here](super::to_string).
267            #[inline]
268            fn fmt(&self, f: &mut Formatter) -> Result {
269                Display::fmt(self, f)
270            }
271        }
272
273        impl ToStringBase for $t {
274            /// Converts an unsigned number to a string using a specified base.
275            ///
276            /// For bases up to 36, digits from 0 to 9 become [`char`]s from '0' to '9' and digits
277            /// from 10 to 35 become the lowercase [`char`]s 'a' to 'z'. For bases from 37 through
278            /// 62 the uppercase and lowercase letters are distinct digits, 'A' through 'Z'
279            /// representing 10 through 35 and 'a' through 'z' representing 36 through 61, as in
280            /// GMP.
281            ///
282            /// # Worst-case complexity
283            /// $T(n) = O(n)$
284            ///
285            /// $M(n) = O(n)$
286            ///
287            /// where $T$ is time, $M$ is additional memory, and $n$ is `self.significant_bits()`.
288            ///
289            /// # Panics
290            /// Panics if `base` is less than 2 or greater than 62.
291            ///
292            /// # Examples
293            /// See [here](super::to_string#to_string_base).
294            #[inline]
295            fn to_string_base(&self, base: u8) -> String {
296                to_string_base_unsigned(self, base)
297            }
298
299            /// Converts an unsigned number to a string using a specified base.
300            ///
301            /// For bases up to 36, digits from 0 to 9 become [`char`]s from '0' to '9' and digits
302            /// from 10 to 35 become the uppercase [`char`]s 'A' to 'Z'. For bases from 37 through
303            /// 62 the uppercase and lowercase letters are distinct digits and there is only one
304            /// alphabet, so the result is the same as
305            /// [`to_string_base`](crate::num::conversion::traits::ToStringBase::to_string_base)'s.
306            ///
307            /// # Worst-case complexity
308            /// $T(n) = O(n)$
309            ///
310            /// $M(n) = O(n)$
311            ///
312            /// where $T$ is time, $M$ is additional memory, and $n$ is `self.significant_bits()`.
313            ///
314            /// # Panics
315            /// Panics if `base` is less than 2 or greater than 62.
316            ///
317            /// # Examples
318            /// See [here](super::to_string#to_string_base_upper).
319            #[inline]
320            fn to_string_base_upper(&self, base: u8) -> String {
321                to_string_base_upper_unsigned(self, base)
322            }
323        }
324    };
325}
326apply_to_unsigneds!(impl_to_string_base_unsigned);
327
328fn fmt_signed<T: Copy + Ord + UnsignedAbs + Zero>(
329    w: &BaseFmtWrapper<T>,
330    f: &mut Formatter,
331) -> Result
332where
333    <T as UnsignedAbs>::Output: Copy + Digits<u8> + Eq + Zero,
334    BaseFmtWrapper<<T as UnsignedAbs>::Output>: Display,
335{
336    if w.x < T::ZERO {
337        if f.width().is_some() || f.sign_plus() {
338            // Let `pad_integral` handle the interaction of the sign with the `+` flag, the
339            // sign-aware zero flag, and fill and alignment.
340            let s = if f.alternate() {
341                to_string_base_upper_unsigned(&w.x.unsigned_abs(), w.base)
342            } else {
343                to_string_base_unsigned(&w.x.unsigned_abs(), w.base)
344            };
345            return f.pad_integral(false, "", &s);
346        }
347        f.write_char('-')?;
348    }
349    Display::fmt(&BaseFmtWrapper::new(w.x.unsigned_abs(), w.base), f)
350}
351
352fn to_string_base_signed<U: Digits<u8>, S: Copy + Eq + Ord + UnsignedAbs<Output = U> + Zero>(
353    x: &S,
354    base: u8,
355) -> String {
356    assert!((2..=62).contains(&base), "base out of range");
357    if *x == S::ZERO {
358        "0".to_string()
359    } else {
360        let mut digits = x.unsigned_abs().to_digits_desc(&u8::wrapping_from(base));
361        let map = if base > 36 {
362            // above base 36 there is only one alphabet, with both cases as distinct digits
363            digit_to_display_byte_large
364        } else {
365            digit_to_display_byte_lower
366        };
367        for digit in &mut digits {
368            *digit = map(*digit).unwrap();
369        }
370        if *x < S::ZERO {
371            vec_pad_left(&mut digits, 1, b'-');
372        }
373        String::from_utf8(digits).unwrap()
374    }
375}
376
377fn to_string_base_upper_signed<
378    U: Digits<u8>,
379    S: Copy + Eq + Ord + UnsignedAbs<Output = U> + Zero,
380>(
381    x: &S,
382    base: u8,
383) -> String {
384    assert!((2..=62).contains(&base), "base out of range");
385    if *x == S::ZERO {
386        "0".to_string()
387    } else {
388        let mut digits = x.unsigned_abs().to_digits_desc(&base);
389        let map = if base > 36 {
390            // above base 36 there is only one alphabet, with both cases as distinct digits
391            digit_to_display_byte_large
392        } else {
393            digit_to_display_byte_upper
394        };
395        for digit in &mut digits {
396            *digit = map(*digit).unwrap();
397        }
398        if *x < S::ZERO {
399            vec_pad_left(&mut digits, 1, b'-');
400        }
401        String::from_utf8(digits).unwrap()
402    }
403}
404
405macro_rules! impl_to_string_base_signed {
406    ($u:ident, $s:ident) => {
407        impl Display for BaseFmtWrapper<$s> {
408            /// Writes a wrapped signed number to a string using a specified base.
409            ///
410            /// If the base is greater than 10, lowercase alphabetic letters are used by default.
411            /// Using the `#` flag switches to uppercase letters. Padding with zeros works as usual.
412            /// If the base is greater than 36, the uppercase and lowercase letters are distinct
413            /// digits (see
414            /// [`digit_to_display_byte_large`](super::to_string::digit_to_display_byte_large)), and
415            /// the `#` flag has no effect.
416            ///
417            /// Unlike with the default implementations of [`Binary`](std::fmt::Binary),
418            /// [`Octal`](std::fmt::Octal), [`LowerHex`](std::fmt::LowerHex), and
419            /// [`UpperHex`](std::fmt::UpperHex), negative numbers are represented using a negative
420            /// sign, not two's complement.
421            ///
422            /// # Worst-case complexity
423            /// $T(n) = O(n)$
424            ///
425            /// $M(n) = O(n)$
426            ///
427            /// where $T$ is time, $M$ is additional memory, and $n$ is `self.significant_bits()`.
428            ///
429            /// # Panics
430            /// Panics if `base` is less than 2 or greater than 62.
431            ///
432            /// # Examples
433            /// See [here](super::to_string).
434            #[inline]
435            fn fmt(&self, f: &mut Formatter) -> Result {
436                fmt_signed(self, f)
437            }
438        }
439
440        impl Debug for BaseFmtWrapper<$s> {
441            /// Writes a wrapped signed number to a string using a specified base.
442            ///
443            /// If the base is greater than 10, lowercase alphabetic letters are used by default.
444            /// Using the `#` flag switches to uppercase letters. Padding with zeros works as usual.
445            /// If the base is greater than 36, the uppercase and lowercase letters are distinct
446            /// digits (see
447            /// [`digit_to_display_byte_large`](super::to_string::digit_to_display_byte_large)), and
448            /// the `#` flag has no effect.
449            ///
450            /// Unlike with the default implementations of [`Binary`](std::fmt::Binary),
451            /// [`Octal`](std::fmt::Octal), [`LowerHex`](std::fmt::LowerHex), and
452            /// [`UpperHex`](std::fmt::UpperHex), negative numbers are represented using a negative
453            /// sign, not two's complement.
454            ///
455            /// This is the same as the [`Display::fmt`] implementation.
456            ///
457            /// # Worst-case complexity
458            /// $T(n) = O(n)$
459            ///
460            /// $M(n) = O(n)$
461            ///
462            /// where $T$ is time, $M$ is additional memory, and $n$ is `self.significant_bits()`.
463            ///
464            /// # Panics
465            /// Panics if `base` is less than 2 or greater than 62.
466            ///
467            /// # Examples
468            /// See [here](super::to_string).
469            #[inline]
470            fn fmt(&self, f: &mut Formatter) -> Result {
471                Display::fmt(self, f)
472            }
473        }
474
475        impl ToStringBase for $s {
476            /// Converts a signed number to a string using a specified base.
477            ///
478            /// For bases up to 36, digits from 0 to 9 become [`char`]s from '0' to '9' and digits
479            /// from 10 to 35 become the lowercase [`char`]s 'a' to 'z'. For bases from 37 through
480            /// 62 the uppercase and lowercase letters are distinct digits, 'A' through 'Z'
481            /// representing 10 through 35 and 'a' through 'z' representing 36 through 61, as in
482            /// GMP.
483            ///
484            /// # Worst-case complexity
485            /// $T(n) = O(n)$
486            ///
487            /// $M(n) = O(n)$
488            ///
489            /// where $T$ is time, $M$ is additional memory, and $n$ is `self.significant_bits()`.
490            ///
491            /// # Panics
492            /// Panics if `base` is less than 2 or greater than 62.
493            ///
494            /// # Examples
495            /// See [here](super::to_string#to_string_base).
496            #[inline]
497            fn to_string_base(&self, base: u8) -> String {
498                to_string_base_signed::<$u, $s>(self, base)
499            }
500
501            /// Converts a signed number to a string using a specified base.
502            ///
503            /// For bases up to 36, digits from 0 to 9 become [`char`]s from '0' to '9' and digits
504            /// from 10 to 35 become the uppercase [`char`]s 'A' to 'Z'. For bases from 37 through
505            /// 62 the uppercase and lowercase letters are distinct digits and there is only one
506            /// alphabet, so the result is the same as
507            /// [`to_string_base`](crate::num::conversion::traits::ToStringBase::to_string_base)'s.
508            ///
509            /// # Worst-case complexity
510            /// $T(n) = O(n)$
511            ///
512            /// $M(n) = O(n)$
513            ///
514            /// where $T$ is time, $M$ is additional memory, and $n$ is `self.significant_bits()`.
515            ///
516            /// # Panics
517            /// Panics if `base` is less than 2 or greater than 62.
518            ///
519            /// # Examples
520            /// See [here](super::to_string#to_string_base_upper).
521            #[inline]
522            fn to_string_base_upper(&self, base: u8) -> String {
523                to_string_base_upper_signed::<$u, $s>(self, base)
524            }
525        }
526    };
527}
528apply_to_unsigned_signed_pairs!(impl_to_string_base_signed);