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