dashu_int/fmt/
mod.rs

1//! # Integer formatting.
2//!
3//! Both [UBig] and [IBig] support rust formatter traits ([Display], [LowerHex], etc.). The sign,
4//! width, filling and padding options of the formatter are supported for all formatter traits except
5//! [Debug]. Different from other formatters, [Debug] will display the least and the most significant
6//! digits of the integer, but omitting the middle digits when it's too large. This helps to improve the
7//! readability of debug info, and the printing speed. The digit length and bit length will also be displayed
8//! when the alternate flag of the formatter is set. (pretty printing)
9//!
10//! The struct [InRadix] can be used to print the integer in a given radix, which also supporting
11//! the common formatter options. But the [Debug] trait is not implemented for [InRadix] yet.
12//!
13
14use crate::{
15    error::panic_invalid_radix,
16    ibig::IBig,
17    radix::{self, Digit, DigitCase},
18    repr::TypedReprRef,
19    ubig::UBig,
20    Sign::{self, *},
21};
22use core::fmt::{
23    self, Alignment, Binary, Debug, Display, Formatter, LowerHex, Octal, UpperHex, Write,
24};
25use digit_writer::DigitWriter;
26
27mod digit_writer;
28mod non_power_two;
29mod power_two;
30
31pub use radix::{MAX_RADIX, MIN_RADIX};
32
33impl Display for UBig {
34    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
35        InRadixWriter {
36            sign: Positive,
37            magnitude: self.repr(),
38            radix: 10,
39            prefix: "",
40            digit_case: DigitCase::NoLetters,
41        }
42        .fmt(f)
43    }
44}
45
46impl Debug for UBig {
47    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
48        DoubleEnd {
49            sign: Positive,
50            magnitude: self.repr(),
51            verbose: f.alternate(),
52        }
53        .fmt(f)
54    }
55}
56
57impl Binary for UBig {
58    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
59        InRadixWriter {
60            sign: Positive,
61            magnitude: self.repr(),
62            radix: 2,
63            prefix: if f.alternate() { "0b" } else { "" },
64            digit_case: DigitCase::NoLetters,
65        }
66        .fmt(f)
67    }
68}
69
70impl Octal for UBig {
71    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
72        InRadixWriter {
73            sign: Positive,
74            magnitude: self.repr(),
75            radix: 8,
76            prefix: if f.alternate() { "0o" } else { "" },
77            digit_case: DigitCase::NoLetters,
78        }
79        .fmt(f)
80    }
81}
82
83impl LowerHex for UBig {
84    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
85        InRadixWriter {
86            sign: Positive,
87            magnitude: self.repr(),
88            radix: 16,
89            prefix: if f.alternate() { "0x" } else { "" },
90            digit_case: DigitCase::Lower,
91        }
92        .fmt(f)
93    }
94}
95
96impl UpperHex for UBig {
97    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
98        InRadixWriter {
99            sign: Positive,
100            magnitude: self.repr(),
101            radix: 16,
102            prefix: if f.alternate() { "0x" } else { "" },
103            digit_case: DigitCase::Upper,
104        }
105        .fmt(f)
106    }
107}
108
109impl Display for IBig {
110    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
111        let (sign, magnitude) = self.as_sign_repr();
112        InRadixWriter {
113            sign,
114            magnitude,
115            radix: 10,
116            prefix: "",
117            digit_case: DigitCase::NoLetters,
118        }
119        .fmt(f)
120    }
121}
122
123impl Debug for IBig {
124    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
125        let (sign, magnitude) = self.as_sign_repr();
126        DoubleEnd {
127            sign,
128            magnitude,
129            verbose: f.alternate(),
130        }
131        .fmt(f)
132    }
133}
134
135impl Binary for IBig {
136    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
137        let (sign, magnitude) = self.as_sign_repr();
138        InRadixWriter {
139            sign,
140            magnitude,
141            radix: 2,
142            prefix: if f.alternate() { "0b" } else { "" },
143            digit_case: DigitCase::NoLetters,
144        }
145        .fmt(f)
146    }
147}
148
149impl Octal for IBig {
150    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
151        let (sign, magnitude) = self.as_sign_repr();
152        InRadixWriter {
153            sign,
154            magnitude,
155            radix: 8,
156            prefix: if f.alternate() { "0o" } else { "" },
157            digit_case: DigitCase::NoLetters,
158        }
159        .fmt(f)
160    }
161}
162
163impl LowerHex for IBig {
164    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
165        let (sign, magnitude) = self.as_sign_repr();
166        InRadixWriter {
167            sign,
168            magnitude,
169            radix: 16,
170            prefix: if f.alternate() { "0x" } else { "" },
171            digit_case: DigitCase::Lower,
172        }
173        .fmt(f)
174    }
175}
176
177impl UpperHex for IBig {
178    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
179        let (sign, magnitude) = self.as_sign_repr();
180        InRadixWriter {
181            sign,
182            magnitude,
183            radix: 16,
184            prefix: if f.alternate() { "0x" } else { "" },
185            digit_case: DigitCase::Upper,
186        }
187        .fmt(f)
188    }
189}
190
191impl UBig {
192    /// Representation in a given radix.
193    ///
194    /// # Panics
195    ///
196    /// Panics if `radix` is not between 2 and 36 inclusive.
197    ///
198    /// # Examples
199    ///
200    /// ```
201    /// # use dashu_int::UBig;
202    /// assert_eq!(format!("{}", UBig::from(83u8).in_radix(3)), "10002");
203    /// assert_eq!(format!("{:+010}", UBig::from(35u8).in_radix(36)), "+00000000z");
204    /// ```
205    #[inline]
206    pub fn in_radix(&self, radix: u32) -> InRadix {
207        if !radix::is_radix_valid(radix) {
208            panic_invalid_radix(radix);
209        }
210
211        InRadix {
212            sign: Positive,
213            magnitude: self.repr(),
214            radix,
215        }
216    }
217}
218
219impl IBig {
220    /// Representation in a given radix.
221    ///
222    /// # Panics
223    ///
224    /// Panics if `radix` is not between 2 and 36 inclusive.
225    ///
226    /// # Examples
227    ///
228    /// ```
229    /// # use dashu_int::IBig;
230    /// assert_eq!(format!("{}", IBig::from(-83).in_radix(3)), "-10002");
231    /// assert_eq!(format!("{:010}", IBig::from(-35).in_radix(36)), "-00000000z");
232    /// ```
233    #[inline]
234    pub fn in_radix(&self, radix: u32) -> InRadix {
235        if !radix::is_radix_valid(radix) {
236            panic_invalid_radix(radix);
237        }
238
239        let (sign, magnitude) = self.as_sign_repr();
240        InRadix {
241            sign,
242            magnitude,
243            radix,
244        }
245    }
246}
247
248/// Representation of a [UBig] or [IBig] in any radix between [MIN_RADIX] and [MAX_RADIX] inclusive.
249///
250/// This can be used to format a number in a non-standard radix, by calling [UBig::in_radix] or [IBig::in_radix].
251///
252/// The default format uses lower-case letters a-z for digits 10-35.
253/// The "alternative" format (`{:#}`) uses upper-case letters.
254///
255/// # Examples
256///
257/// ```
258/// # use dashu_int::{IBig, UBig};
259/// assert_eq!(format!("{}", UBig::from(83u8).in_radix(3)), "10002");
260/// assert_eq!(format!("{:+010}", UBig::from(35u8).in_radix(36)), "+00000000z");
261/// // For bases 2, 8, 10, 16 we don't have to use `InRadix`:
262/// assert_eq!(format!("{:x}", UBig::from(3000u32)), "bb8");
263/// assert_eq!(format!("{:#X}", IBig::from(-3000)), "-0xBB8");
264/// ```
265pub struct InRadix<'a> {
266    sign: Sign,
267    magnitude: TypedReprRef<'a>,
268    radix: Digit,
269}
270
271impl Display for InRadix<'_> {
272    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
273        let digit_case = if self.radix <= 10 {
274            DigitCase::NoLetters
275        } else if f.alternate() {
276            DigitCase::Upper
277        } else {
278            DigitCase::Lower
279        };
280
281        InRadixWriter {
282            sign: self.sign,
283            magnitude: self.magnitude,
284            radix: self.radix,
285            prefix: "",
286            digit_case,
287        }
288        .fmt(f)
289    }
290}
291
292/// Representation in a given radix with a prefix and digit case.
293struct InRadixWriter<'a> {
294    sign: Sign,
295    magnitude: TypedReprRef<'a>,
296    radix: Digit,
297    prefix: &'static str,
298    digit_case: DigitCase,
299}
300
301/// Representation for printing only head and tail of the number, only decimal is supported
302struct DoubleEnd<'a> {
303    sign: Sign,
304    magnitude: TypedReprRef<'a>,
305    verbose: bool,
306}
307
308impl InRadixWriter<'_> {
309    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
310        if self.radix.is_power_of_two() {
311            self.fmt_power_two(f)
312        } else {
313            self.fmt_non_power_two(f)
314        }
315    }
316
317    /// Format using a `PreparedForFormatting`.
318    fn format_prepared(
319        &self,
320        f: &mut Formatter,
321        prepared: &mut dyn PreparedForFormatting,
322    ) -> fmt::Result {
323        let mut width = prepared.width();
324
325        // Adding sign and prefix to width will not overflow, because Buffer::MAX_CAPACITY leaves
326        // (WORD_BITS - 1) spare bits before we would hit overflow.
327        let sign = if self.sign == Negative {
328            "-"
329        } else if f.sign_plus() {
330            "+"
331        } else {
332            ""
333        };
334        // In bytes, but it's OK because everything is ASCII.
335        width += sign.len() + self.prefix.len();
336
337        let mut write_digits = |f| {
338            let mut digit_writer = DigitWriter::new(f, self.digit_case);
339            prepared.write(&mut digit_writer)?;
340            digit_writer.flush()
341        };
342
343        match f.width() {
344            None => {
345                f.write_str(sign)?;
346                f.write_str(self.prefix)?;
347                write_digits(f)?
348            }
349            Some(min_width) => {
350                if width >= min_width {
351                    f.write_str(sign)?;
352                    f.write_str(self.prefix)?;
353                    write_digits(f)?;
354                } else if f.sign_aware_zero_pad() {
355                    f.write_str(sign)?;
356                    f.write_str(self.prefix)?;
357                    for _ in 0..min_width - width {
358                        f.write_char('0')?;
359                    }
360                    write_digits(f)?;
361                } else {
362                    let left_pad = match f.align() {
363                        Some(Alignment::Left) => 0,
364                        Some(Alignment::Right) | None => min_width - width,
365                        Some(Alignment::Center) => (min_width - width) / 2,
366                    };
367                    let fill = f.fill();
368                    for _ in 0..left_pad {
369                        f.write_char(fill)?;
370                    }
371                    f.write_str(sign)?;
372                    f.write_str(self.prefix)?;
373                    write_digits(f)?;
374                    for _ in left_pad..min_width - width {
375                        f.write_char(fill)?;
376                    }
377                }
378            }
379        }
380
381        Ok(())
382    }
383}
384
385impl DoubleEnd<'_> {
386    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
387        self.fmt_non_power_two(f)
388    }
389
390    /// Format using a `PreparedForFormatting`.
391    fn format_prepared(
392        &self,
393        f: &mut Formatter,
394        digits: usize,
395        prepared_high: &mut dyn PreparedForFormatting,
396        prepared_low: Option<&mut dyn PreparedForFormatting>,
397    ) -> fmt::Result {
398        let sign = if self.sign == Negative {
399            "-"
400        } else if f.sign_plus() {
401            "+"
402        } else {
403            ""
404        };
405        f.write_str(sign)?;
406
407        let mut digit_writer = DigitWriter::new(f, DigitCase::NoLetters);
408        prepared_high.write(&mut digit_writer)?;
409        digit_writer.flush()?;
410
411        if let Some(low) = prepared_low {
412            f.write_str("..")?;
413
414            let mut digit_writer = DigitWriter::new(f, DigitCase::NoLetters);
415            low.write(&mut digit_writer)?;
416            digit_writer.flush()?;
417        }
418
419        if self.verbose {
420            f.write_str(" (")?;
421            non_power_two::write_usize_decimals(f, digits)?;
422            f.write_str(" digits, ")?;
423            non_power_two::write_usize_decimals(f, self.magnitude.bit_len())?;
424            f.write_str(" bits)")?;
425        }
426
427        Ok(())
428    }
429}
430
431/// Trait for state of a partially-formatted [UBig].
432///
433/// The state must be such the width (number of digits) is already known.
434trait PreparedForFormatting {
435    /// Returns the number of characters that will be written.
436    fn width(&self) -> usize;
437
438    /// Write to a stream.
439    fn write(&mut self, digit_writer: &mut DigitWriter) -> fmt::Result;
440}