Skip to main content

dashu_float/
fmt.rs

1//! Implementation of formatters
2
3use crate::{
4    fbig::FBig,
5    repr::{Context, Repr},
6    round::{mode::Zero, Round},
7    utils::{digit_len, split_digits_ref},
8};
9use alloc::string::String;
10use core::fmt::{self, Alignment, Display, Formatter, Write};
11use dashu_base::Sign;
12use dashu_int::{IBig, Word};
13
14trait DebugStructHelper {
15    /// Print the full debug info for the significand
16    fn field_significand<const B: Word>(&mut self, signif: &IBig) -> &mut Self;
17}
18
19impl<'a, 'b> DebugStructHelper for fmt::DebugStruct<'a, 'b> {
20    fn field_significand<const B: Word>(&mut self, signif: &IBig) -> &mut Self {
21        match B {
22            2 => self.field(
23                "significand",
24                &format_args!("{:?} ({} bits)", signif, digit_len::<B>(signif)),
25            ),
26            10 => self.field("significand", &format_args!("{:#?}", signif)),
27            _ => self.field(
28                "significand",
29                &format_args!("{:?} ({} digits)", signif, digit_len::<B>(signif)),
30            ),
31        }
32    }
33}
34
35impl<const B: Word> fmt::Debug for Repr<B> {
36    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37        if let Some(result) = self.write_if_infinite(f) {
38            return result;
39        }
40
41        if f.alternate() {
42            f.debug_struct("Repr")
43                .field_significand::<B>(&self.significand)
44                .field("exponent", &format_args!("{} ^ {}", B, self.exponent))
45                .finish()
46        } else {
47            f.write_fmt(format_args!("{:?} * {} ^ {}", self.significand, B, self.exponent))
48        }
49    }
50}
51
52impl<R: Round> fmt::Debug for Context<R> {
53    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
54        let rnd_name = core::any::type_name::<R>();
55        let rnd_name = rnd_name
56            .rfind("::")
57            .map(|pos| &rnd_name[pos + 2..])
58            .unwrap_or(rnd_name);
59        f.debug_struct("Context")
60            .field("precision", &self.precision)
61            .field("rounding", &format_args!("{}", rnd_name))
62            .finish()
63    }
64}
65
66impl<const B: Word> Repr<B> {
67    /// If this value is infinite, write "inf" / "-inf" and return `Some(Ok(()))`.
68    fn write_if_infinite(&self, f: &mut Formatter<'_>) -> Option<fmt::Result> {
69        if self.is_infinite() {
70            Some(match self.sign() {
71                Sign::Positive => f.write_str("inf"),
72                Sign::Negative => f.write_str("-inf"),
73            })
74        } else {
75            None
76        }
77    }
78
79    /// Print the float number with given rounding mode.
80    /// The rounding may happen if the precision option of the formatter is set.
81    fn fmt_round<R: Round>(&self, f: &mut Formatter<'_>) -> fmt::Result {
82        if let Some(result) = self.write_if_infinite(f) {
83            return result;
84        }
85
86        // first perform rounding before actual printing if necessary
87        let negative = self.sign() == Sign::Negative;
88        let rounded_signif;
89        let (signif, exp) = if let Some(prec) = f.precision() {
90            let diff = prec as isize + self.exponent;
91            if diff < 0 {
92                let shift = -diff as usize;
93                let (signif, rem) = split_digits_ref::<B>(&self.significand, shift);
94                let adjust = R::round_fract::<B>(&signif, rem, shift);
95                rounded_signif = signif + adjust;
96                (&rounded_signif, self.exponent - diff)
97            } else {
98                (&self.significand, self.exponent)
99            }
100        } else {
101            (&self.significand, self.exponent)
102        };
103        // zero's stored exponent encodes the ±0 sign (sentinel -1 / 0), not magnitude, so it must
104        // not leak into the rendered digits/exponent — render `0`/`0e0` canonically.
105        let is_zero = signif.is_zero();
106        let exp = if is_zero { 0 } else { exp };
107
108        // then print the digits to a buffer, without the sign
109        let mut signif_str = String::new();
110        write!(&mut signif_str, "{}", signif.in_radix(B as _))?;
111        // strip the leading '-' from a negative significand (e.g. "-123"); for `-0` the
112        // significand renders as "0" with no sign, so leave it — the '-' is emitted below
113        // from `negative` (which reflects the signed zero, not the bare significand).
114        let signif_str = signif_str.strip_prefix('-').unwrap_or(&signif_str);
115
116        // calculate padding if necessary
117        let (left_pad, right_pad) = if let Some(min_width) = f.width() {
118            let mut signif_digits = signif_str.len();
119            // the leading zeros needs to be printed (when the exponent of the number is very small).
120            let leading_zeros = -(exp + signif_str.len() as isize - 1).min(0) as usize;
121            // the trailing zeros needs to be printed (when the exponent of the number is very large)
122            let mut trailing_zeros = exp.max(0) as usize;
123
124            // if the precision option is set, there might be extra trailing zeros
125            if let Some(prec) = f.precision() {
126                let diff = prec as isize + exp.min(0);
127                if diff > 0 {
128                    trailing_zeros += diff as usize;
129                }
130            }
131            if leading_zeros == 0 {
132                // there is at least one digit to print (0)
133                signif_digits = signif_digits.max(1);
134            }
135
136            let has_sign = ((negative && !is_zero) || f.sign_plus()) as usize;
137            let has_radix_point = if exp > 0 {
138                // if there's no fractional part, the result has the floating point
139                // only if the precision is set to be non-zero
140                f.precision().unwrap_or(0) > 0
141            } else {
142                // if there is fractional part, the result has the floating point
143                // if the precision is not set, or set to be non-zero
144                f.precision() != Some(0) // non-zero or none
145            } as usize;
146
147            let width = signif_digits + has_sign + has_radix_point + leading_zeros + trailing_zeros;
148
149            // check alignment and calculate padding
150            if width >= min_width {
151                (0, 0)
152            } else if f.sign_aware_zero_pad() {
153                (min_width - width, 0)
154            } else {
155                match f.align() {
156                    Some(Alignment::Left) => (0, min_width - width),
157                    Some(Alignment::Right) | None => (min_width - width, 0),
158                    Some(Alignment::Center) => {
159                        let diff = min_width - width;
160                        (diff / 2, diff - diff / 2)
161                    }
162                }
163            }
164        } else {
165            (0, 0)
166        };
167
168        // print sign and left padding
169        if !f.sign_aware_zero_pad() {
170            for _ in 0..left_pad {
171                f.write_char(f.fill())?;
172            }
173        }
174        // Emit the sign: a nonzero negative always carries '-'; `-0` carries '-' only when the
175        // formatter's `+` flag is set (otherwise `-0` and `+0` both render as "0"). Under `+`,
176        // `+0` renders as "+0" via the else-if branch.
177        if negative && (!is_zero || f.sign_plus()) {
178            f.write_char('-')?;
179        } else if f.sign_plus() {
180            f.write_char('+')?;
181        }
182        if f.sign_aware_zero_pad() {
183            for _ in 0..left_pad {
184                f.write_char('0')?;
185            }
186        }
187
188        // print the actual digits
189        if exp < 0 {
190            // If the exponent is negative, then the float number has fractional part
191            let exp = -exp as usize;
192            let (int, fract) = signif_str.split_at(signif_str.len().saturating_sub(exp));
193
194            let frac_digits = fract.len();
195            debug_assert!(frac_digits <= exp);
196
197            // print the integral part, at least print a zero.
198            if int.is_empty() {
199                f.write_char('0')?;
200            } else {
201                f.write_str(int)?;
202            }
203
204            // print the fractional part, it has exactly `exp` digits (with left zero padding)
205            if let Some(prec) = f.precision() {
206                // don't print any fractional part if precision is zero
207                if prec != 0 {
208                    f.write_char('.')?;
209                    if exp >= prec {
210                        // the fractional part should be already rounded at the beginning
211                        debug_assert!(exp == prec);
212
213                        // print padding zeros
214                        if prec > frac_digits {
215                            for _ in 0..prec - frac_digits {
216                                f.write_char('0')?;
217                            }
218                        }
219                        if frac_digits > 0 {
220                            f.write_str(fract)?;
221                        }
222                    } else {
223                        // append zeros if the required precision is larger
224                        for _ in 0..exp - frac_digits {
225                            f.write_char('0')?;
226                        }
227                        f.write_str(fract)?;
228                        for _ in 0..prec - exp {
229                            f.write_char('0')?;
230                        }
231                    }
232                }
233            } else if frac_digits > 0 {
234                f.write_char('.')?;
235                for _ in 0..(exp - frac_digits) {
236                    f.write_char('0')?;
237                }
238                f.write_str(fract)?;
239            }
240        } else {
241            // In this case, the number is actually an integer and it can be trivially formatted.
242            // However, when the precision option is set, we need to append zeros.
243
244            // print the significand and append zeros if needed
245            if signif_str.is_empty() {
246                // this branch can happend when a negative float is rounded to zero.
247                f.write_char('0')?;
248            } else {
249                f.write_str(signif_str)?;
250            }
251            for _ in 0..exp {
252                f.write_char('0')?;
253            }
254
255            // print trailing zeros after the float point if the precision is set to be nonzero
256            if let Some(prec) = f.precision() {
257                if prec > 0 {
258                    f.write_char('.')?;
259                    for _ in 0..prec {
260                        f.write_char('0')?;
261                    }
262                }
263            }
264        };
265
266        // print right padding
267        for _ in 0..right_pad {
268            f.write_char(f.fill())?;
269        }
270
271        Ok(())
272    }
273
274    /// Print the float number in scientific notation with given rounding mode.
275    /// The rounding may happen if the precision option of the formatter is set.
276    ///
277    /// When `use_hexadecimal` is True and base B is 2, the output will be represented
278    /// in the hexadecimal format 0xaaa.bbbpcc.
279    fn fmt_round_scientific<R: Round>(
280        &self,
281        f: &mut Formatter<'_>,
282        upper: bool,
283        use_hexadecimal: bool,
284        exp_marker: Option<char>,
285    ) -> fmt::Result {
286        assert!(!(B != 2 && use_hexadecimal), "hexadecimal is only relevant for base 2");
287
288        if let Some(result) = self.write_if_infinite(f) {
289            return result;
290        }
291
292        // first perform rounding before actual printing if necessary
293        let negative = self.sign() == Sign::Negative;
294        let rounded_signif;
295        let (signif, exp) = if let Some(prec) = f.precision() {
296            // add one because always have one extra digit before the radix point
297            let prec = if use_hexadecimal {
298                (prec * 4 + 4) as isize
299            } else {
300                (prec + 1) as isize
301            };
302            let diff = prec - self.digits() as isize;
303            if diff < 0 {
304                let shift = -diff as usize;
305                let (signif, rem) = split_digits_ref::<B>(&self.significand, shift);
306                let adjust = R::round_fract::<B>(&signif, rem, shift);
307                rounded_signif = signif + adjust;
308                (&rounded_signif, self.exponent - diff)
309            } else {
310                (&self.significand, self.exponent)
311            }
312        } else {
313            (&self.significand, self.exponent)
314        };
315        // zero's stored exponent encodes the ±0 sign (sentinel -1 / 0), not magnitude, so it must
316        // not leak into the rendered digits/exponent — render `0`/`0e0` canonically.
317        let is_zero = signif.is_zero();
318        let exp = if is_zero { 0 } else { exp };
319
320        // then print the digits to a buffer, without the prefix or sign
321        let (mut signif_str, mut exp_str) = (String::new(), String::new());
322        match (upper, use_hexadecimal) {
323            (false, false) => write!(&mut signif_str, "{}", signif.in_radix(B as _)),
324            (true, false) => write!(&mut signif_str, "{:#}", signif.in_radix(B as _)),
325            (false, true) => write!(&mut signif_str, "{:}", signif.in_radix(16)),
326            (true, true) => write!(&mut signif_str, "{:#}", signif.in_radix(16)),
327        }?;
328        // strip the leading '-' from a negative significand (e.g. "-123"); for `-0` the
329        // significand renders as "0" with no sign, so leave it — the '-' is emitted below
330        // from `negative` (which reflects the signed zero, not the bare significand).
331        let signif_str = signif_str.strip_prefix('-').unwrap_or(&signif_str);
332        // adjust exp because the radix point is put after the first digit
333        let exp_adjust = if use_hexadecimal {
334            exp + (signif_str.len() as isize - 1) * 4
335        } else {
336            exp + signif_str.len() as isize - 1
337        };
338        write!(&mut exp_str, "{}", exp_adjust)?;
339        let exp_str = exp_str.as_str();
340
341        // calculate padding if necessary
342        let (left_pad, right_pad) = if let Some(min_width) = f.width() {
343            let prec = f.precision().unwrap_or(0);
344            let has_point = signif_str.len() > 1 || prec > 0; // whether print the radix point
345            let has_sign = (negative && !is_zero) || f.sign_plus();
346
347            // if the precision option is set, there might be extra trailing zeros
348            let trailing_zeros = if prec > signif_str.len() - 1 {
349                prec - (signif_str.len() - 1)
350            } else {
351                0
352            };
353
354            let width = signif_str.len() + exp_str.len()
355                + /* exponent marker */ 1
356                + has_sign as usize
357                + has_point as usize
358                + use_hexadecimal as usize * 2
359                + trailing_zeros;
360
361            if width >= min_width {
362                (0, 0)
363            } else {
364                match f.align() {
365                    Some(Alignment::Left) => (0, min_width - width),
366                    Some(Alignment::Right) | None => (min_width - width, 0),
367                    Some(Alignment::Center) => {
368                        let diff = min_width - width;
369                        (diff / 2, diff - diff / 2)
370                    }
371                }
372            }
373        } else {
374            (0, 0)
375        };
376
377        // print sign and left padding
378        if !f.sign_aware_zero_pad() {
379            for _ in 0..left_pad {
380                f.write_char(f.fill())?;
381            }
382        }
383        // Emit the sign: a nonzero negative always carries '-'; `-0` carries '-' only when the
384        // formatter's `+` flag is set (otherwise `-0` and `+0` both render as "0"). Under `+`,
385        // `+0` renders as "+0" via the else-if branch.
386        if negative && (!is_zero || f.sign_plus()) {
387            f.write_char('-')?;
388        } else if f.sign_plus() {
389            f.write_char('+')?;
390        }
391        if use_hexadecimal {
392            f.write_str("0x")?;
393        }
394        if f.sign_aware_zero_pad() {
395            for _ in 0..left_pad {
396                f.write_char('0')?;
397            }
398        }
399
400        // print the body
401        let (int, fract) = signif_str.split_at(1);
402        f.write_str(int)?;
403        if !fract.is_empty() {
404            f.write_char('.')?;
405            f.write_str(fract)?;
406        }
407        let prec = f.precision().unwrap_or(0);
408        if prec > 0 {
409            if fract.is_empty() {
410                f.write_char('.')?
411            }
412            for _ in fract.len()..prec {
413                f.write_char('0')?;
414            }
415        }
416
417        f.write_char(exp_marker.unwrap_or('@'))?;
418        f.write_str(exp_str)?;
419
420        // print right padding
421        for _ in 0..right_pad {
422            f.write_char(f.fill())?;
423        }
424
425        Ok(())
426    }
427}
428
429impl<const B: Word> Display for Repr<B> {
430    #[inline]
431    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
432        self.fmt_round::<Zero>(f)
433    }
434}
435
436impl<R: Round, const B: Word> fmt::Debug for FBig<R, B> {
437    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
438        // shortcut for infinities
439        if self.repr.is_infinite() {
440            return match self.repr.sign() {
441                Sign::Positive => f.write_str("inf"),
442                Sign::Negative => f.write_str("-inf"),
443            };
444        }
445
446        let rnd_name = core::any::type_name::<R>();
447        let rnd_name = rnd_name
448            .rfind("::")
449            .map(|pos| &rnd_name[pos + 2..])
450            .unwrap_or(rnd_name);
451
452        if f.alternate() {
453            f.debug_struct("FBig")
454                .field_significand::<B>(&self.repr.significand)
455                .field("exponent", &format_args!("{} ^ {}", B, self.repr.exponent))
456                .field("precision", &self.context.precision)
457                .field("rounding", &format_args!("{}", rnd_name))
458                .finish()
459        } else {
460            f.write_fmt(format_args!("{:?} (prec: {})", self.repr, self.context.precision))
461        }
462    }
463}
464
465impl<R: Round, const B: Word> Display for FBig<R, B> {
466    #[inline]
467    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
468        self.repr.fmt_round::<R>(f)
469    }
470}
471
472macro_rules! impl_fmt_with_base {
473    ($base:literal, $trait:ident, $upper: literal, $hex:literal, $marker:literal) => {
474        impl fmt::$trait for Repr<$base> {
475            #[inline]
476            fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
477                self.fmt_round_scientific::<Zero>(f, $upper, $hex, Some($marker))
478            }
479        }
480
481        impl<R: Round> fmt::$trait for FBig<R, $base> {
482            #[inline]
483            fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
484                self.repr
485                    .fmt_round_scientific::<R>(f, $upper, $hex, Some($marker))
486            }
487        }
488    };
489}
490
491// TODO(v1.0): Alternate flags can be used to print upper separator, for example 'p' -> 'P'.
492//             In case of base ten, it can be used to switch between '@' and 'e'/'E'.
493//             Need to investigate what is the best way to utilize the alternate flag before implementing.
494impl_fmt_with_base!(2, LowerHex, false, true, 'p');
495impl_fmt_with_base!(2, UpperHex, true, true, 'p');
496impl_fmt_with_base!(2, Binary, false, false, 'b');
497impl_fmt_with_base!(8, Octal, false, false, 'o');
498impl_fmt_with_base!(16, LowerHex, false, false, 'h');
499impl_fmt_with_base!(16, UpperHex, true, false, 'h');
500
501impl<const B: Word> fmt::LowerExp for Repr<B> {
502    #[inline]
503    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
504        let marker = match B {
505            10 => Some('e'),
506            _ => None,
507        };
508        self.fmt_round_scientific::<Zero>(f, false, false, marker)
509    }
510}
511impl<const B: Word> fmt::UpperExp for Repr<B> {
512    #[inline]
513    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
514        let marker = match B {
515            10 => Some('E'),
516            _ => None,
517        };
518        self.fmt_round_scientific::<Zero>(f, true, false, marker)
519    }
520}
521impl<R: Round, const B: Word> fmt::LowerExp for FBig<R, B> {
522    #[inline]
523    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
524        let marker = match B {
525            10 => Some('e'),
526            _ => None,
527        };
528        self.repr.fmt_round_scientific::<R>(f, false, false, marker)
529    }
530}
531impl<R: Round, const B: Word> fmt::UpperExp for FBig<R, B> {
532    #[inline]
533    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
534        let marker = match B {
535            10 => Some('E'),
536            _ => None,
537        };
538        self.repr.fmt_round_scientific::<R>(f, true, false, marker)
539    }
540}