fraction/fraction/
display.rs

1//! Implementation of fmt::Display for [GenericFraction] and [Sign] structures
2//!
3//! Complete support of the [std::fmt] formatting syntax with exceptions for alternate
4//! integer types (hexadecimal, octal, binary)
5//!
6//! There are a couple of extensions on top of std::fmt, which are
7//!   - Alternate flag (`#`) is used to enable `trailing zeroes` output
8//!   - Negative sign (`-`) is used to suppress sign output (even negative)
9//!
10//! Otherwise, the standard formatting is followed as is.
11//!
12//! # Examples
13//!
14//! ```
15//! use fraction::prelude::Fraction;
16//!
17//! let fraction = Fraction::from(1.75);
18//!
19//! assert_eq!("7/4", format!("{}", fraction));
20//! assert_eq!("1.75", format!("{:.4}", fraction));
21//! assert_eq!("1.7500", format!("{:#.4}", fraction));
22//! assert_eq!("-1.75", format!("{:.2}", -fraction));
23//! assert_eq!("1.75", format!("{:-.2}", -fraction));
24//! ```
25
26use division;
27use fraction::{GenericFraction, Sign};
28use generic::GenericInteger;
29
30use std::fmt;
31
32/// Same as fmt::Formatter, but allowing to change flags
33/// so we can reuse it in recursive format implementations
34pub struct Format {
35    flags: u8,
36    fill: char,
37    width: Option<usize>,
38    precision: Option<usize>,
39    align: Option<fmt::Alignment>,
40}
41
42const SIGN_PLUS: u8 = 1;
43const SIGN_MINUS: u8 = 2;
44const ALTERNATE: u8 = 4;
45const SIGAZEPAD: u8 = 8;
46
47impl Format {
48    pub const fn empty() -> Self {
49        Format {
50            fill: ' ',
51            align: None,
52            width: None,
53            precision: None,
54            flags: 0,
55        }
56    }
57
58    pub fn new(formatter: &fmt::Formatter) -> Self {
59        let sign_plus = if formatter.sign_plus() { SIGN_PLUS } else { 0 };
60        let sign_minus = if formatter.sign_minus() {
61            SIGN_MINUS
62        } else {
63            0
64        };
65        let alternate = if formatter.alternate() { ALTERNATE } else { 0 };
66        let sigazepad = if formatter.sign_aware_zero_pad() {
67            SIGAZEPAD
68        } else {
69            0
70        };
71
72        let flags = sign_plus | sign_minus | alternate | sigazepad;
73
74        Format {
75            fill: formatter.fill(),
76            align: formatter.align(),
77            width: formatter.width(),
78            precision: formatter.precision(),
79            flags,
80        }
81    }
82
83    pub const fn fill(&self) -> char {
84        self.fill
85    }
86
87    pub const fn cloned_align(&self) -> Option<fmt::Alignment> {
88        match self.align {
89            Some(ref align) => match *align {
90                fmt::Alignment::Left => Some(fmt::Alignment::Left),
91                fmt::Alignment::Right => Some(fmt::Alignment::Right),
92                fmt::Alignment::Center => Some(fmt::Alignment::Center),
93            },
94            None => None,
95        }
96    }
97
98    pub const fn align(&self) -> &Option<fmt::Alignment> {
99        &self.align
100    }
101
102    pub const fn set_align(mut self, align: Option<fmt::Alignment>) -> Self {
103        self.align = align;
104        self
105    }
106
107    pub const fn width(&self) -> &Option<usize> {
108        &self.width
109    }
110
111    pub const fn set_width(mut self, width: Option<usize>) -> Self {
112        self.width = width;
113        self
114    }
115
116    pub const fn precision(&self) -> &Option<usize> {
117        &self.precision
118    }
119
120    pub const fn set_precision(mut self, precision: Option<usize>) -> Self {
121        self.precision = precision;
122        self
123    }
124
125    pub const fn sign_plus(&self) -> bool {
126        self.flags & SIGN_PLUS > 0
127    }
128
129    pub const fn set_sign_plus(mut self, flag: bool) -> Self {
130        if flag {
131            self.flags |= SIGN_PLUS;
132        } else {
133            self.flags &= !SIGN_PLUS;
134        }
135
136        self
137    }
138
139    pub const fn sign_minus(&self) -> bool {
140        self.flags & SIGN_MINUS > 0
141    }
142
143    pub const fn set_sign_minus(mut self, flag: bool) -> Self {
144        if flag {
145            self.flags |= SIGN_MINUS;
146        } else {
147            self.flags &= !SIGN_MINUS;
148        }
149
150        self
151    }
152
153    pub const fn alternate(&self) -> bool {
154        self.flags & ALTERNATE > 0
155    }
156
157    pub const fn set_alternate(mut self, flag: bool) -> Self {
158        if flag {
159            self.flags |= ALTERNATE;
160        } else {
161            self.flags &= !ALTERNATE;
162        }
163
164        self
165    }
166
167    pub const fn sign_aware_zero_pad(&self) -> bool {
168        self.flags & SIGAZEPAD > 0
169    }
170
171    pub const fn set_sign_aware_zero_pad(mut self, flag: bool) -> Self {
172        if flag {
173            self.flags |= SIGAZEPAD;
174        } else {
175            self.flags &= !SIGAZEPAD;
176        }
177
178        self
179    }
180}
181
182impl Clone for Format {
183    fn clone(&self) -> Self {
184        Format {
185            fill: self.fill(),
186            align: self.cloned_align(),
187            width: *self.width(),
188            precision: *self.precision(),
189            flags: self.flags,
190        }
191    }
192}
193
194pub fn format_sign(sign: Sign, buffer: &mut dyn fmt::Write, format: &Format) -> fmt::Result {
195    if format.sign_plus() || (!format.sign_minus() && sign.is_negative()) {
196        if format.sign_aware_zero_pad() {
197            let format = format.clone().set_sign_aware_zero_pad(false);
198            format_value(
199                buffer,
200                &format,
201                None,
202                |_| 1,
203                |b, _| {
204                    b.write_char(sign.into())?;
205                    Ok(1)
206                },
207            )
208        } else {
209            format_value(
210                buffer,
211                format,
212                None,
213                |_| 1,
214                |b, _| {
215                    b.write_char(sign.into())?;
216                    Ok(1)
217                },
218            )
219        }
220    } else {
221        Ok(())
222    }
223}
224
225pub fn format_fraction<T>(
226    fraction: &GenericFraction<T>,
227    buffer: &mut dyn fmt::Write,
228    format: &Format,
229) -> fmt::Result
230where
231    T: Clone + GenericInteger,
232{
233    match *fraction {
234        GenericFraction::NaN => format_value(
235            buffer,
236            format,
237            None,
238            |_| 3,
239            |b, _| {
240                b.write_str("NaN")?;
241                Ok(3)
242            },
243        ),
244        GenericFraction::Infinity(sign) => format_value(
245            buffer,
246            format,
247            Some(sign),
248            |_| 3,
249            |b, _| {
250                b.write_str("inf")?;
251                Ok(3)
252            },
253        ),
254        GenericFraction::Rational(sign, ref ratio) => format_value(
255            buffer,
256            format,
257            Some(sign),
258            |format| {
259                let numer = ratio.numer().clone();
260                let denom = ratio.denom().clone();
261
262                let mut length = 0;
263
264                if let Some(precision) = format.precision() {
265                    division::divide_to_callback(
266                        numer,
267                        denom,
268                        *precision,
269                        format.alternate(),
270                        |_| {
271                            length += 1;
272                            Ok(true)
273                        },
274                    )
275                    .ok();
276                } else {
277                    division::divide_to_callback(numer, T::one(), 0, false, |_| {
278                        length += 1;
279                        Ok(true)
280                    })
281                    .ok();
282
283                    if !ratio.numer().is_zero() && !ratio.denom().is_one() {
284                        length += 1;
285                        division::divide_to_callback(denom, T::one(), 0, false, |_| {
286                            length += 1;
287                            Ok(true)
288                        })
289                        .ok();
290                    }
291                }
292
293                length
294            },
295            |buffer, format| {
296                let numer = ratio.numer().clone();
297                let denom = ratio.denom().clone();
298
299                let mut length = 0;
300
301                if let Some(precision) = format.precision() {
302                    let callback = |digit| {
303                        length += 1;
304                        division::write_digit(buffer, digit)
305                    };
306
307                    if division::divide_to_callback(
308                        numer,
309                        denom,
310                        *precision,
311                        format.alternate(),
312                        callback,
313                    )
314                    .is_err()
315                    {
316                        return Err(fmt::Error);
317                    }
318                } else {
319                    let callback = |digit| {
320                        length += 1;
321                        division::write_digit(buffer, digit)
322                    };
323
324                    if division::divide_to_callback(numer, T::one(), 0, false, callback).is_err() {
325                        return Err(fmt::Error);
326                    }
327
328                    if !ratio.numer().is_zero() && !ratio.denom().is_one() {
329                        length += 1;
330                        buffer.write_char('/')?;
331
332                        let callback = |digit| {
333                            length += 1;
334                            division::write_digit(buffer, digit)
335                        };
336
337                        if division::divide_to_callback(denom, T::one(), 0, false, callback)
338                            .is_err()
339                        {
340                            return Err(fmt::Error);
341                        };
342                    }
343                }
344
345                Ok(length)
346            },
347        ),
348    }
349}
350
351fn format_value<L, V>(
352    buffer: &mut dyn fmt::Write,
353    format: &Format,
354    sign: Option<Sign>,
355    value_length: L,
356    value: V,
357) -> fmt::Result
358where
359    L: Fn(&Format) -> usize,
360    V: FnOnce(&mut dyn fmt::Write, &Format) -> Result<usize, fmt::Error>,
361{
362    if let Some(width) = format.width() {
363        let width = *width;
364        let sign_len = if let Some(sign) = sign {
365            usize::from(format.sign_plus() || (!format.sign_minus() && sign.is_negative()))
366        } else {
367            0
368        };
369
370        if format.sign_aware_zero_pad() {
371            let value_len = value_length(format);
372
373            if sign_len > 0 {
374                buffer.write_char(sign.unwrap().into())?;
375            }
376
377            if width > sign_len + value_len {
378                for _ in sign_len + value_len..width {
379                    buffer.write_char('0')?;
380                }
381            }
382            value(buffer, format)?;
383        } else {
384            match format.align() {
385                Some(fmt::Alignment::Center) => {
386                    let value_len = value_length(format) + sign_len;
387                    let mut prefix_len = 0;
388                    let mut excess = 0;
389
390                    if width > value_len {
391                        excess = width - value_len;
392                        prefix_len = excess / 2;
393                        for _ in 0..prefix_len {
394                            buffer.write_char(format.fill())?;
395                        }
396                    };
397
398                    if sign_len > 0 {
399                        buffer.write_char(sign.unwrap().into())?;
400                    }
401                    value(buffer, format)?;
402
403                    if width > value_len {
404                        for _ in 0..excess - prefix_len {
405                            buffer.write_char(format.fill())?;
406                        }
407                    }
408                }
409                None | Some(fmt::Alignment::Right) => {
410                    let value_len = value_length(format);
411
412                    for _ in sign_len + value_len..width {
413                        buffer.write_char(format.fill())?;
414                    }
415
416                    if sign_len > 0 {
417                        buffer.write_char(sign.unwrap().into())?;
418                    }
419                    value(buffer, format)?;
420                }
421                Some(fmt::Alignment::Left) => {
422                    if sign_len > 0 {
423                        buffer.write_char(sign.unwrap().into())?;
424                    }
425                    let value_len = value(buffer, format)?;
426
427                    if width > sign_len + value_len {
428                        for _ in sign_len + value_len..width {
429                            buffer.write_char(format.fill())?;
430                        }
431                    }
432                }
433            }
434        }
435    } else {
436        if let Some(sign) = sign {
437            if format.sign_plus() || (!format.sign_minus() && sign.is_negative()) {
438                buffer.write_char(sign.into())?;
439            }
440        }
441        value(buffer, format)?;
442    }
443
444    Ok(())
445}
446
447#[cfg(test)]
448mod tests {
449    #[cfg(feature = "with-bigint")]
450    use prelude::{BigFraction, BigUint};
451
452    use super::super::super::{One, Zero};
453    use prelude::GenericFraction;
454
455    type F32 = GenericFraction<i32>;
456    type F64 = GenericFraction<u64>;
457
458    #[test]
459    fn display() {
460        assert_eq!("NaN", format!("{}", F32::nan()));
461        assert_eq!(format!("{}", ::std::f64::NAN), format!("{}", F32::nan()));
462
463        assert_eq!("NaN", format!("{:+}", F32::nan()));
464        assert_eq!(
465            format!("{:+}", ::std::f64::NAN),
466            format!("{:+}", F32::nan())
467        );
468        assert_eq!("==NaN==", format!("{:=^7}", F32::nan()));
469        assert_eq!("=NaN==", format!("{:=^6}", F32::nan()));
470        assert_eq!("NaN====", format!("{:=<7}", F32::nan()));
471        assert_eq!("====NaN", format!("{:=>7}", F32::nan()));
472        assert_eq!("0000NaN", format!("{:=>07}", F32::nan()));
473        assert_eq!("0000NaN", format!("{:07}", F32::nan()));
474        assert_eq!("0000NaN", format!("{:+07}", F32::nan()));
475
476        assert_eq!("inf", format!("{}", F32::infinity()));
477        assert_eq!(
478            format!("{}", ::std::f64::INFINITY),
479            format!("{}", F32::infinity())
480        );
481        assert_eq!("==inf==", format!("{:=^7}", F32::infinity()));
482        assert_eq!("inf====", format!("{:=<7}", F32::infinity()));
483        assert_eq!("====inf", format!("{:=>7}", F32::infinity()));
484        assert_eq!("0000inf", format!("{:=<07}", F32::infinity()));
485        assert_eq!("+000inf", format!("{:=<+07}", F32::infinity()));
486
487        assert_eq!("+inf", format!("{:+}", F32::infinity()));
488        assert_eq!(
489            format!("{:+}", ::std::f64::INFINITY),
490            format!("{:+}", F32::infinity())
491        );
492        assert_eq!("=+inf==", format!("{:=^+7}", F32::infinity()));
493
494        assert_eq!("-inf", format!("{}", F32::neg_infinity()));
495        assert_eq!(
496            format!("{}", ::std::f64::NEG_INFINITY),
497            format!("{}", F32::neg_infinity())
498        );
499        assert_eq!("=-inf==", format!("{:=^7}", F32::neg_infinity()));
500
501        assert_eq!("-inf", format!("{:+}", F32::neg_infinity()));
502        assert_eq!(
503            format!("{:+}", ::std::f64::NEG_INFINITY),
504            format!("{:+}", F32::neg_infinity())
505        );
506        assert_eq!("=-inf==", format!("{:=^+7}", F32::neg_infinity()));
507
508        // zero
509        assert_eq!("0", format!("{}", F32::zero()));
510        assert_eq!(format!("{}", 0.0f64), format!("{}", F32::zero()));
511
512        assert_eq!("+0", format!("{:+}", F32::zero()));
513        assert_eq!(format!("{:+}", 0.0f64), format!("{:+}", F32::zero()));
514
515        assert_eq!("0", format!("{:.2}", F32::zero()));
516        assert_eq!("+0", format!("{:+.2}", F32::zero()));
517        assert_eq!("0", format!("{:-.2}", F32::zero()));
518
519        assert_eq!("0.00", format!("{:#.2}", F32::zero()));
520        assert_eq!("+0.00", format!("{:+#.2}", F32::zero()));
521        assert_eq!("0.00", format!("{:-#.2}", F32::zero()));
522
523        // neg zero
524        assert_eq!("-0", format!("{}", F32::neg_zero()));
525        assert_eq!("-0", format!("{:+}", F32::neg_zero()));
526        assert_eq!("0", format!("{:-}", F32::neg_zero()));
527
528        assert_eq!("-0", format!("{:.2}", F32::neg_zero()));
529        assert_eq!("-0", format!("{:+.2}", F32::neg_zero()));
530        assert_eq!("0", format!("{:-.2}", F32::neg_zero()));
531
532        // positive whole
533        assert_eq!("1", format!("{}", F32::one()));
534        assert_eq!("+1", format!("{:+}", F32::one()));
535        assert_eq!("1", format!("{:-}", F32::one()));
536
537        assert_eq!("0001", format!("{:04}", F32::one()));
538        assert_eq!("+001", format!("{:+04}", F32::one()));
539
540        assert_eq!("=1==", format!("{:=^4}", F32::one()));
541        assert_eq!("===1", format!("{:=>4}", F32::one()));
542        assert_eq!("1===", format!("{:=<4}", F32::one()));
543        assert_eq!("=+1=", format!("{:=^+4}", F32::one()));
544
545        // negative whole
546        assert_eq!("-1", format!("{}", -F32::one()));
547        assert_eq!("-1", format!("{:+}", -F32::one()));
548        assert_eq!("1", format!("{:-}", -F32::one()));
549
550        assert_eq!("=-1=", format!("{:=^4}", -F32::one()));
551        assert_eq!("=1==", format!("{:=^-4}", -F32::one()));
552        assert_eq!("=-1=", format!("{:=^+4}", -F32::one()));
553
554        // positive fraction
555        assert_eq!("00.5", format!("{:04.1}", F32::new(1, 2)));
556        assert_eq!("+0.5", format!("{:+04.1}", F32::new(1, 2)));
557        assert_eq!("00.5", format!("{:-04.1}", F32::new(1, 2)));
558
559        assert_eq!("0.5", format!("{:.16}", F32::new(1, 2)));
560        assert_eq!("+0.5", format!("{:+.16}", F32::new(1, 2)));
561        assert_eq!("+1.75", format!("{:=^+.4}", F32::new(7, 4)));
562        assert_eq!("=+1.75==", format!("{:=^+8.4}", F32::new(7, 4)));
563        assert_eq!("   +1.75", format!("{:+8.4}", F32::new(7, 4)));
564        assert_eq!("   +1.75", format!("{:>+8.4}", F32::new(7, 4)));
565        assert_eq!("+1.75   ", format!("{:<+8.4}", F32::new(7, 4)));
566        assert_eq!("+0001.75", format!("{:<+08.4}", F32::new(7, 4)));
567
568        // negative fraction
569        assert_eq!("-00.5", format!("{:05.2}", -F32::new(1, 2)));
570        assert_eq!("-00.5", format!("{:05.2}", -F32::new(1, 2)));
571        assert_eq!("-00.5", format!("{:+05.2}", -F32::new(1, 2)));
572        assert_eq!("000.5", format!("{:-05.2}", -F32::new(1, 2)));
573
574        assert_eq!("-0.5", format!("{:.16}", -F32::new(1, 2)));
575        assert_eq!("0.5", format!("{:-.16}", -F32::new(1, 2)));
576        assert_eq!("-0.5", format!("{:+.16}", -F32::new(1, 2)));
577
578        assert_eq!("-1.75", format!("{:=^+.4}", -F32::new(7, 4)));
579        assert_eq!("=-1.75==", format!("{:=^+8.4}", -F32::new(7, 4)));
580        assert_eq!("   -1.75", format!("{:+8.4}", -F32::new(7, 4)));
581        assert_eq!("   -1.75", format!("{:>+8.4}", -F32::new(7, 4)));
582        assert_eq!("-1.75   ", format!("{:<+8.4}", -F32::new(7, 4)));
583        assert_eq!("-0001.75", format!("{:<+08.4}", -F32::new(7, 4)));
584
585        assert_eq!("-1.75", format!("{:=^.4}", -F32::new(7, 4)));
586        assert_eq!("=-1.75==", format!("{:=^8.4}", -F32::new(7, 4)));
587        assert_eq!("   -1.75", format!("{:8.4}", -F32::new(7, 4)));
588        assert_eq!("   -1.75", format!("{:>8.4}", -F32::new(7, 4)));
589        assert_eq!("-1.75   ", format!("{:<8.4}", -F32::new(7, 4)));
590        assert_eq!("-0001.75", format!("{:<08.4}", -F32::new(7, 4)));
591
592        assert_eq!("-01.7500", format!("{:<#08.4}", -F32::new(7, 4)));
593
594        assert_eq!("1.75", format!("{:=^-.4}", -F32::new(7, 4)));
595        assert_eq!("==1.75==", format!("{:=^-8.4}", -F32::new(7, 4)));
596        assert_eq!("    1.75", format!("{:-8.4}", -F32::new(7, 4)));
597        assert_eq!("    1.75", format!("{:>-8.4}", -F32::new(7, 4)));
598        assert_eq!("1.75    ", format!("{:<-8.4}", -F32::new(7, 4)));
599        assert_eq!("00001.75", format!("{:<-08.4}", -F32::new(7, 4)));
600
601        // ratios
602        assert_eq!("7/4", format!("{}", F32::new(7, 4)));
603        assert_eq!("7/4", format!("{:-}", F32::new(7, 4)));
604        assert_eq!("+7/4", format!("{:+}", F32::new(7, 4)));
605
606        assert_eq!("-7/4", format!("{}", -F32::new(7, 4)));
607        assert_eq!("7/4", format!("{:-}", -F32::new(7, 4)));
608        assert_eq!("-7/4", format!("{:+}", -F32::new(7, 4)));
609
610        assert_eq!("07/4", format!("{:04}", F32::new(7, 4)));
611        assert_eq!("-7/4", format!("{:+04}", -F32::new(7, 4)));
612
613        assert_eq!("==7/4==", format!("{:=^7}", F32::new(7, 4)));
614        assert_eq!("=-7/4==", format!("{:=^7}", -F32::new(7, 4)));
615        assert_eq!("    7/4", format!("{:7}", F32::new(7, 4)));
616        assert_eq!("7/4    ", format!("{:<7}", F32::new(7, 4)));
617        assert_eq!("-7/4   ", format!("{:<7}", -F32::new(7, 4)));
618        assert_eq!("+7/4   ", format!("{:<+7}", F32::new(7, 4)));
619        assert_eq!("+0007/4", format!("{:<+07}", F32::new(7, 4)));
620
621        // former format_as_decimal tests
622        assert_eq!("NaN", format!("{:.64}", F32::from(::std::f32::NAN)));
623        assert_eq!("inf", format!("{:.64}", F32::from(::std::f32::INFINITY)));
624        assert_eq!(
625            "-inf",
626            format!("{:.64}", F32::from(::std::f32::NEG_INFINITY))
627        );
628
629        assert_eq!("0.75", format!("{:.64}", F32::from(0.75)));
630        assert_eq!("-0.75", format!("{:.64}", F32::from(-0.75)));
631        assert_eq!("0.33", format!("{:.64}", F32::from((33, 100))));
632        assert_eq!(
633            "0.0000000456",
634            format!("{:.64}", F64::new(456u64, 10000000000u64))
635        );
636
637        #[cfg(feature = "with-bigint")]
638        {
639            let fra = BigFraction::new(
640                BigUint::from(42u8),
641                BigUint::from(1000000000000000u64)
642                    * BigUint::from(1000000000000000u64)
643                    * BigUint::from(1000000000000000u64),
644            );
645
646            assert_eq!(
647                "0.000000000000000000000000000000000000000000042",
648                format!("{:.64}", fra)
649            );
650        }
651    }
652}