Skip to main content

lean_decimal/
display.rs

1use core::fmt;
2use core::mem::MaybeUninit;
3
4use crate::{Decimal, UnderlyingInt};
5
6/// Display the decimal.
7///
8/// It supports some [formatting options](https://doc.rust-lang.org/std/fmt/index.html#formatting-parameters):
9/// width, fill, alignment, precision, sign and 0-fill.
10///
11/// Examples:
12///
13/// ```
14/// use lean_decimal::Dec128;
15/// let d = Dec128::from_parts(12_3470, 4);
16///
17/// assert_eq!(format!("{}", d), "12.3470");
18/// assert_eq!(format!("{:.6}", d), "12.347000"); // set precision: pad 0
19/// assert_eq!(format!("{:.2}", d), "12.35"); // set smaller precision: round the number
20/// assert_eq!(format!("{:x>10}", d), "xxx12.3470"); // set width, fill, alignment
21/// assert_eq!(format!("{:+}", d), "+12.3470"); // set sign
22
23impl<I: UnderlyingInt> fmt::Display for Decimal<I> {
24    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25        let (sign, scale, man) = self.unpack();
26
27        let mut buf: [MaybeUninit<u8>; 80] = [MaybeUninit::uninit(); 80];
28        assert!(scale <= 36);
29
30        let offset = display_num(man, scale, f.precision(), &mut buf);
31
32        // SAFETY: offset is updated along with buf
33        let buf = unsafe { buf[offset..].assume_init_ref() };
34
35        // SAFETY: all data is valid charactor
36        let s = unsafe { str::from_utf8_unchecked(buf) };
37
38        f.pad_integral(sign == 0 || man == I::ZERO, "", s)
39    }
40}
41
42fn display_num<I: UnderlyingInt>(
43    uns: I,
44    scale: u32,
45    precision: Option<usize>,
46    buf: &mut [MaybeUninit<u8>],
47) -> usize {
48    let precision = precision.unwrap_or(scale as usize);
49
50    if scale == 0 {
51        let mut offset = buf.len();
52        if precision > 0 {
53            // pad zeros and set point
54            offset = pad_zeros(precision, buf);
55            offset -= 1;
56            buf[offset].write(b'.');
57        }
58        return dump_single(uns, &mut buf[..offset]);
59    }
60
61    let scale = scale as usize;
62
63    if precision >= scale {
64        let (int, frac) = uns.div_rem_exp(scale as u32);
65        let offset = pad_zeros(precision.min(I::MAX_SCALE as usize) - scale, buf);
66        dump_decimal(int, frac, scale, &mut buf[..offset])
67    } else {
68        let uns = uns.div_exp((scale - precision) as u32);
69        if precision == 0 {
70            dump_single(uns, buf)
71        } else {
72            let (int, frac) = uns.div_rem_exp(precision as u32);
73            dump_decimal(int, frac, precision, buf)
74        }
75    }
76}
77
78// dump: "int . frac"
79fn dump_decimal<I: UnderlyingInt>(
80    int: I,
81    frac: I,
82    scale: usize,
83    buf: &mut [MaybeUninit<u8>],
84) -> usize {
85    let mut offset = dump_single(frac, buf);
86
87    offset = pad_zeros(scale - (buf.len() - offset), &mut buf[..offset]);
88
89    offset -= 1;
90    buf[offset].write(b'.');
91
92    dump_single(int, &mut buf[..offset])
93}
94
95// dump a single integer number
96// This is much faster than using integers' Display.
97fn dump_single<I: UnderlyingInt>(n: I, buf: &mut [MaybeUninit<u8>]) -> usize {
98    static DECIMAL_PAIRS: &[u8; 200] = b"\
99        0001020304050607080910111213141516171819\
100        2021222324252627282930313233343536373839\
101        4041424344454647484950515253545556575859\
102        6061626364656667686970717273747576777879\
103        8081828384858687888990919293949596979899";
104
105    let mut offset = buf.len();
106    let mut remain = n;
107
108    // Format per two digits from the lookup table.
109    while remain >= I::TEN {
110        offset -= 2;
111
112        let pair: usize = (remain % I::HUNDRED).as_u32() as usize;
113        remain = remain / I::HUNDRED;
114        buf[offset + 0].write(DECIMAL_PAIRS[pair * 2 + 0]);
115        buf[offset + 1].write(DECIMAL_PAIRS[pair * 2 + 1]);
116    }
117
118    // Format the last remaining digit, if any.
119    if remain != I::ZERO || n == I::ZERO {
120        offset -= 1;
121        let remain: u8 = remain.as_u32() as u8;
122        buf[offset].write(b'0' + remain);
123    }
124
125    offset
126}
127
128fn pad_zeros(n: usize, buf: &mut [MaybeUninit<u8>]) -> usize {
129    let mut offset = buf.len();
130    for _ in 0..n {
131        offset -= 1;
132        buf[offset].write(b'0');
133    }
134    offset
135}