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 core::fmt::{self, Alignment, Display, Formatter, Write};
10use dashu_base::{Sign, UnsignedAbs};
11use dashu_int::{IBig, Word};
12
13trait DebugStructHelper {
14    /// Print the full debug info for the significand
15    fn field_significand<const B: Word>(&mut self, signif: &IBig) -> &mut Self;
16}
17
18impl<'a, 'b> DebugStructHelper for fmt::DebugStruct<'a, 'b> {
19    fn field_significand<const B: Word>(&mut self, signif: &IBig) -> &mut Self {
20        match B {
21            2 => self.field(
22                "significand",
23                &format_args!("{:?} ({} bits)", signif, digit_len::<B>(signif)),
24            ),
25            10 => self.field("significand", &format_args!("{:#?}", signif)),
26            _ => self.field(
27                "significand",
28                &format_args!("{:?} ({} digits)", signif, digit_len::<B>(signif)),
29            ),
30        }
31    }
32}
33
34impl<const B: Word> fmt::Debug for Repr<B> {
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        // shortcut for infinities
37        if self.is_infinite() {
38            return match self.sign() {
39                Sign::Positive => f.write_str("inf"),
40                Sign::Negative => f.write_str("-inf"),
41            };
42        }
43
44        if f.alternate() {
45            f.debug_struct("Repr")
46                .field_significand::<B>(&self.significand)
47                .field("exponent", &format_args!("{} ^ {}", &B, &self.exponent))
48                .finish()
49        } else {
50            f.write_fmt(format_args!("{:?} * {} ^ {}", &self.significand, &B, &self.exponent))
51        }
52    }
53}
54
55impl<R: Round> fmt::Debug for Context<R> {
56    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
57        let rnd_name = core::any::type_name::<R>();
58        let rnd_name = rnd_name
59            .rfind("::")
60            .map(|pos| &rnd_name[pos + 2..])
61            .unwrap_or(rnd_name);
62        f.debug_struct("Context")
63            .field("precision", &self.precision)
64            .field("rounding", &format_args!("{}", rnd_name))
65            .finish()
66    }
67}
68
69impl<const B: Word> Repr<B> {
70    /// Print the float number with given rounding mode. The rounding may happen if the precision option
71    /// of the formatter is set.
72    fn fmt_round<R: Round>(&self, f: &mut Formatter<'_>) -> fmt::Result {
73        // shortcut for infinities
74        if self.is_infinite() {
75            return match self.sign() {
76                Sign::Positive => f.write_str("inf"),
77                Sign::Negative => f.write_str("-inf"),
78            };
79        }
80
81        // first perform rounding before actual printing if necessary
82        let negative = self.significand.sign() == Sign::Negative;
83        let rounded_signif;
84        let (signif, exp) = if let Some(prec) = f.precision() {
85            let diff = prec as isize + self.exponent;
86            if diff < 0 {
87                let shift = -diff as usize;
88                let (signif, rem) = split_digits_ref::<B>(&self.significand, shift);
89                let adjust = R::round_fract::<B>(&signif, rem, shift);
90                rounded_signif = signif + adjust;
91                (&rounded_signif, self.exponent - diff)
92            } else {
93                (&self.significand, self.exponent)
94            }
95        } else {
96            (&self.significand, self.exponent)
97        };
98
99        // calculate padding if necessary
100        let (left_pad, right_pad) = if let Some(min_width) = f.width() {
101            // first calculate the with of the formatted digits without padding
102
103            let mut signif_digits = digit_len::<B>(signif);
104            // the leading zeros needs to be printed (when the exponent of the number is very small).
105            let leading_zeros = -(exp + signif_digits as isize - 1).min(0) as usize;
106            // the trailing zeros needs to be printed (when the exponent of the number is very large)
107            let mut trailing_zeros = exp.max(0) as usize;
108
109            // if the precision option is set, there might be extra trailing zeros
110            if let Some(prec) = f.precision() {
111                let diff = prec as isize + exp.min(0);
112                if diff > 0 {
113                    trailing_zeros += diff as usize;
114                }
115            }
116            if leading_zeros == 0 {
117                // there is at least one digit to print (0)
118                signif_digits = signif_digits.max(1);
119            }
120
121            let has_sign = (negative || f.sign_plus()) as usize;
122            let has_float_point = if exp > 0 {
123                // if there's no fractional part, the result has the floating point
124                // only if the precision is set to be non-zero
125                f.precision().unwrap_or(0) > 0
126            } else {
127                // if there is fractional part, the result has the floating point
128                // if the precision is not set, or set to be non-zero
129                f.precision() != Some(0) // non-zero or none
130            } as usize;
131
132            let width = signif_digits + has_sign + has_float_point + leading_zeros + trailing_zeros;
133
134            // check alignment and calculate padding
135            if width >= min_width {
136                (0, 0)
137            } else if f.sign_aware_zero_pad() {
138                (min_width - width, 0)
139            } else {
140                match f.align() {
141                    Some(Alignment::Left) => (0, min_width - width),
142                    Some(Alignment::Right) | None => (min_width - width, 0),
143                    Some(Alignment::Center) => {
144                        let diff = min_width - width;
145                        (diff / 2, diff - diff / 2)
146                    }
147                }
148            }
149        } else {
150            (0, 0)
151        };
152
153        // print left padding
154        let fill = if f.sign_aware_zero_pad() {
155            '0'
156        } else {
157            f.fill()
158        };
159        for _ in 0..left_pad {
160            f.write_char(fill)?;
161        }
162
163        // print the actual digits
164        if exp < 0 {
165            // If the exponent is negative, then the float number has fractional part
166            let exp = -exp as usize;
167            let (int, fract) = split_digits_ref::<B>(signif, exp);
168
169            let frac_digits = digit_len::<B>(&fract);
170            debug_assert!(frac_digits <= exp);
171
172            // print the integral part.
173            if !negative && f.sign_plus() {
174                f.write_char('+')?;
175            }
176            if int.is_zero() {
177                if negative {
178                    f.write_char('-')?;
179                }
180                f.write_char('0')?;
181            } else {
182                f.write_fmt(format_args!("{}", int.in_radix(B as u32)))?;
183            }
184
185            // print the fractional part, it has exactly `exp` digits (with left zero padding)
186            let fract = fract.unsigned_abs(); // don't print sign for fractional part
187            if let Some(prec) = f.precision() {
188                // don't print any fractional part if precision is zero
189                if prec != 0 {
190                    f.write_char('.')?;
191                    if exp >= prec {
192                        // the fractional part should be already rounded at the beginning
193                        debug_assert!(exp == prec);
194
195                        // print padding zeros
196                        if prec > frac_digits {
197                            for _ in 0..prec - frac_digits {
198                                f.write_char('0')?;
199                            }
200                        }
201                        if frac_digits > 0 {
202                            f.write_fmt(format_args!("{}", fract.in_radix(B as u32)))?;
203                        }
204                    } else {
205                        // append zeros if the required precision is larger
206                        for _ in 0..exp - frac_digits {
207                            f.write_char('0')?;
208                        }
209                        f.write_fmt(format_args!("{}", fract.in_radix(B as u32)))?;
210                        for _ in 0..prec - exp {
211                            f.write_char('0')?;
212                        }
213                    }
214                }
215            } else if frac_digits > 0 {
216                f.write_char('.')?;
217                for _ in 0..(exp - frac_digits) {
218                    f.write_char('0')?;
219                }
220                f.write_fmt(format_args!("{}", fract.in_radix(B as u32)))?;
221            }
222        } else {
223            // In this case, the number is actually an integer and it can be trivially formatted.
224            // However, when the precision option is set, we need to append zeros.
225
226            // print the significand
227            if !negative && f.sign_plus() {
228                f.write_char('+')?;
229            }
230            if signif.is_zero() {
231                if negative {
232                    f.write_char('-')?;
233                }
234                f.write_char('0')?;
235            } else {
236                f.write_fmt(format_args!("{}", signif.in_radix(B as u32)))?;
237            }
238
239            // append zeros if needed
240            for _ in 0..exp {
241                f.write_char('0')?;
242            }
243
244            // print trailing zeros after the float point if the precision is set to be nonzero
245            if let Some(prec) = f.precision() {
246                if prec > 0 {
247                    f.write_char('.')?;
248                    for _ in 0..prec {
249                        f.write_char('0')?;
250                    }
251                }
252            }
253        };
254
255        // print right padding
256        for _ in 0..right_pad {
257            f.write_char(f.fill())?;
258        }
259
260        Ok(())
261    }
262}
263
264impl<const B: Word> Display for Repr<B> {
265    #[inline]
266    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
267        self.fmt_round::<Zero>(f)
268    }
269}
270
271impl<R: Round, const B: Word> fmt::Debug for FBig<R, B> {
272    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
273        // shortcut for infinities
274        if self.repr.is_infinite() {
275            return match self.repr.sign() {
276                Sign::Positive => f.write_str("inf"),
277                Sign::Negative => f.write_str("-inf"),
278            };
279        }
280
281        let rnd_name = core::any::type_name::<R>();
282        let rnd_name = rnd_name
283            .rfind("::")
284            .map(|pos| &rnd_name[pos + 2..])
285            .unwrap_or(rnd_name);
286
287        if f.alternate() {
288            f.debug_struct("FBig")
289                .field_significand::<B>(&self.repr.significand)
290                .field("exponent", &format_args!("{} ^ {}", &B, &self.repr.exponent))
291                .field("precision", &self.context.precision)
292                .field("rounding", &format_args!("{}", rnd_name))
293                .finish()
294        } else {
295            f.write_fmt(format_args!("{:?} (prec: {})", &self.repr, &self.context.precision))
296        }
297    }
298}
299
300impl<R: Round, const B: Word> Display for FBig<R, B> {
301    #[inline]
302    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
303        self.repr.fmt_round::<R>(f)
304    }
305}