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