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.significand.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
104        // then print the digits to a buffer, without the sign
105        let mut signif_str = String::new();
106        write!(&mut signif_str, "{}", signif.in_radix(B as _))?;
107        let signif_str = if negative {
108            &signif_str[1..]
109        } else {
110            signif_str.as_str()
111        };
112
113        // calculate padding if necessary
114        let (left_pad, right_pad) = if let Some(min_width) = f.width() {
115            let mut signif_digits = signif_str.len();
116            // the leading zeros needs to be printed (when the exponent of the number is very small).
117            let leading_zeros = -(exp + signif_str.len() as isize - 1).min(0) as usize;
118            // the trailing zeros needs to be printed (when the exponent of the number is very large)
119            let mut trailing_zeros = exp.max(0) as usize;
120
121            // if the precision option is set, there might be extra trailing zeros
122            if let Some(prec) = f.precision() {
123                let diff = prec as isize + exp.min(0);
124                if diff > 0 {
125                    trailing_zeros += diff as usize;
126                }
127            }
128            if leading_zeros == 0 {
129                // there is at least one digit to print (0)
130                signif_digits = signif_digits.max(1);
131            }
132
133            let has_sign = (negative || f.sign_plus()) as usize;
134            let has_radix_point = if exp > 0 {
135                // if there's no fractional part, the result has the floating point
136                // only if the precision is set to be non-zero
137                f.precision().unwrap_or(0) > 0
138            } else {
139                // if there is fractional part, the result has the floating point
140                // if the precision is not set, or set to be non-zero
141                f.precision() != Some(0) // non-zero or none
142            } as usize;
143
144            let width = signif_digits + has_sign + has_radix_point + leading_zeros + trailing_zeros;
145
146            // check alignment and calculate padding
147            if width >= min_width {
148                (0, 0)
149            } else if f.sign_aware_zero_pad() {
150                (min_width - width, 0)
151            } else {
152                match f.align() {
153                    Some(Alignment::Left) => (0, min_width - width),
154                    Some(Alignment::Right) | None => (min_width - width, 0),
155                    Some(Alignment::Center) => {
156                        let diff = min_width - width;
157                        (diff / 2, diff - diff / 2)
158                    }
159                }
160            }
161        } else {
162            (0, 0)
163        };
164
165        // print sign and left padding
166        if !f.sign_aware_zero_pad() {
167            for _ in 0..left_pad {
168                f.write_char(f.fill())?;
169            }
170        }
171        if negative {
172            f.write_char('-')?;
173        } else if f.sign_plus() {
174            f.write_char('+')?;
175        }
176        if f.sign_aware_zero_pad() {
177            for _ in 0..left_pad {
178                f.write_char('0')?;
179            }
180        }
181
182        // print the actual digits
183        if exp < 0 {
184            // If the exponent is negative, then the float number has fractional part
185            let exp = -exp as usize;
186            let (int, fract) = signif_str.split_at(signif_str.len().saturating_sub(exp));
187
188            let frac_digits = fract.len();
189            debug_assert!(frac_digits <= exp);
190
191            // print the integral part, at least print a zero.
192            if int.is_empty() {
193                f.write_char('0')?;
194            } else {
195                f.write_str(int)?;
196            }
197
198            // print the fractional part, it has exactly `exp` digits (with left zero padding)
199            if let Some(prec) = f.precision() {
200                // don't print any fractional part if precision is zero
201                if prec != 0 {
202                    f.write_char('.')?;
203                    if exp >= prec {
204                        // the fractional part should be already rounded at the beginning
205                        debug_assert!(exp == prec);
206
207                        // print padding zeros
208                        if prec > frac_digits {
209                            for _ in 0..prec - frac_digits {
210                                f.write_char('0')?;
211                            }
212                        }
213                        if frac_digits > 0 {
214                            f.write_str(fract)?;
215                        }
216                    } else {
217                        // append zeros if the required precision is larger
218                        for _ in 0..exp - frac_digits {
219                            f.write_char('0')?;
220                        }
221                        f.write_str(fract)?;
222                        for _ in 0..prec - exp {
223                            f.write_char('0')?;
224                        }
225                    }
226                }
227            } else if frac_digits > 0 {
228                f.write_char('.')?;
229                for _ in 0..(exp - frac_digits) {
230                    f.write_char('0')?;
231                }
232                f.write_str(fract)?;
233            }
234        } else {
235            // In this case, the number is actually an integer and it can be trivially formatted.
236            // However, when the precision option is set, we need to append zeros.
237
238            // print the significand and append zeros if needed
239            if signif_str.is_empty() {
240                // this branch can happend when a negative float is rounded to zero.
241                f.write_char('0')?;
242            } else {
243                f.write_str(signif_str)?;
244            }
245            for _ in 0..exp {
246                f.write_char('0')?;
247            }
248
249            // print trailing zeros after the float point if the precision is set to be nonzero
250            if let Some(prec) = f.precision() {
251                if prec > 0 {
252                    f.write_char('.')?;
253                    for _ in 0..prec {
254                        f.write_char('0')?;
255                    }
256                }
257            }
258        };
259
260        // print right padding
261        for _ in 0..right_pad {
262            f.write_char(f.fill())?;
263        }
264
265        Ok(())
266    }
267
268    /// Print the float number in scientific notation with given rounding mode.
269    /// The rounding may happen if the precision option of the formatter is set.
270    ///
271    /// When `use_hexadecimal` is True and base B is 2, the output will be represented
272    /// in the hexadecimal format 0xaaa.bbbpcc.
273    fn fmt_round_scientific<R: Round>(
274        &self,
275        f: &mut Formatter<'_>,
276        upper: bool,
277        use_hexadecimal: bool,
278        exp_marker: Option<char>,
279    ) -> fmt::Result {
280        assert!(!(B != 2 && use_hexadecimal), "hexadecimal is only relevant for base 2");
281
282        if let Some(result) = self.write_if_infinite(f) {
283            return result;
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}