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);