Skip to main content

json/util/
print_dec.rs

1// This is a modified version of the `itoa` crate by David Tolnay:
2// https://github.com/dtolnay/itoa
3//
4// The crate itself borrows code from the stdlib of Rust.
5//
6// The algorithm here was modified from being able to just writing integers,
7// to printing decimal floating points.
8
9use std::{io, mem, ptr, slice};
10
11const DEC_DIGITS_LUT: &'static[u8] =
12    b"0001020304050607080910111213141516171819\
13      2021222324252627282930313233343536373839\
14      4041424344454647484950515253545556575859\
15      6061626364656667686970717273747576777879\
16      8081828384858687888990919293949596979899";
17
18const ZEROFILL: &'static [u8] = &[b'0'; 20];
19
20#[inline(always)]
21unsafe fn write_num(n: &mut u64, curr: &mut isize, buf_ptr: *mut u8, lut_ptr: *const u8) {
22    // eagerly decode 4 digits at a time
23    while *n >= 10000 {
24        let rem = (*n % 10000) as isize;
25        *n /= 10000;
26
27        let d1 = (rem / 100) << 1;
28        let d2 = (rem % 100) << 1;
29        *curr -= 4;
30        ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(*curr), 2);
31        ptr::copy_nonoverlapping(lut_ptr.offset(d2), buf_ptr.offset(*curr + 2), 2);
32    }
33
34    // decode 2 more digits
35    if *n >= 100 {
36        let d1 = ((*n % 100) << 1) as isize;
37        *n /= 100;
38        *curr -= 2;
39        ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(*curr), 2);
40    }
41
42    // decode last 1 or 2 digits
43    if *n < 10 {
44        *curr -= 1;
45        *buf_ptr.offset(*curr) = (*n as u8) + b'0';
46    } else {
47        let d1 = (*n << 1) as isize;
48        *curr -= 2;
49        ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(*curr), 2);
50    }
51}
52
53pub unsafe fn write<W: io::Write>(wr: &mut W, positive: bool, mut n: u64, exponent: i16) -> io::Result<()> {
54    if !positive {
55        wr.write_all(b"-")?;
56    }
57
58    if n == 0 {
59        return wr.write_all(b"0");
60    }
61
62    const BUF_LEN: usize = 30;
63    let mut buf = mem::MaybeUninit::<[u8; BUF_LEN]>::uninit();
64    let mut curr = BUF_LEN as isize;
65    let buf_ptr = buf.as_mut_ptr() as *mut u8;
66    let lut_ptr = DEC_DIGITS_LUT.as_ptr();
67
68    if exponent == 0 {
69        write_num(&mut n, &mut curr, buf_ptr, lut_ptr);
70
71        return wr.write_all(
72            slice::from_raw_parts(
73                buf_ptr.offset(curr),
74                BUF_LEN - curr as usize
75            )
76        );
77    } else if exponent < 0 {
78        let mut e = safe_abs(exponent);
79
80        // Decimal number with a fraction that's fully printable
81        if e < 18 {
82            // eagerly decode 4 digits at a time
83            for _ in 0 .. e >> 2 {
84                let rem = (n % 10000) as isize;
85                n /= 10000;
86
87                let d1 = (rem / 100) << 1;
88                let d2 = (rem % 100) << 1;
89                curr -= 4;
90                ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
91                ptr::copy_nonoverlapping(lut_ptr.offset(d2), buf_ptr.offset(curr + 2), 2);
92            }
93
94            e &= 3;
95
96            // write the remaining 3, 2 or 1 digits
97            if e & 2 == 2 {
98                let d1 = ((n % 100) << 1) as isize;
99                n /= 100;
100                curr -= 2;
101                ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
102            }
103
104            if e & 1 == 1 {
105                curr -= 1;
106                *buf_ptr.offset(curr) = ((n % 10) as u8) + b'0';
107                n /= 10;
108            }
109
110            curr -= 1;
111            *buf_ptr.offset(curr) = b'.';
112
113            write_num(&mut n, &mut curr, buf_ptr, lut_ptr);
114
115            return wr.write_all(
116                slice::from_raw_parts(buf_ptr.offset(curr), BUF_LEN - curr as usize)
117            );
118        }
119
120        // Not easily printable, write down fraction, then full number, then exponent
121
122        // Since we move the decimal point right after the first digit, we have to adjust the
123        // exponent part. If the number is long enough, this may result in the exponent switching
124        // sign from negative to positive - we have to handle this case separately.
125        let mut exponent_positive = false;
126        if n < 10 {
127            // Single digit, no fraction
128            curr -= 1;
129            *buf_ptr.offset(curr) = ((n % 10) as u8) + b'0';
130        } else {
131            // eagerly decode 4 digits at a time
132            while n >= 100000 {
133                let rem = (n % 10000) as isize;
134                n /= 10000;
135
136                let d1 = (rem / 100) << 1;
137                let d2 = (rem % 100) << 1;
138                curr -= 4;
139                ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
140                ptr::copy_nonoverlapping(lut_ptr.offset(d2), buf_ptr.offset(curr + 2), 2);
141            }
142
143            // decode 2 more digits
144            if n >= 1000 {
145                let d1 = ((n % 100) << 1) as isize;
146                n /= 100;
147                curr -= 2;
148                ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
149            }
150
151            // decode last 1 or 2 digits
152            if n < 100 {
153                curr -= 1;
154                *buf_ptr.offset(curr) = ((n % 10) as u8) + b'0';
155                n /= 10;
156            } else {
157                let d1 = ((n % 100) << 1) as isize;
158                n /= 100;
159                curr -= 2;
160                ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
161            }
162
163            let printed_so_far = BUF_LEN as u16 - curr as u16;
164
165
166            if printed_so_far <= e {
167                // Subtract the amount of digits printed in the fraction
168                // from the exponent that we still need to print using
169                // the `e` notation
170                e -= printed_so_far;
171            } else {
172                // Same as e = |e - printed_so_far|.
173                e = printed_so_far - e;
174                exponent_positive = true;
175            }
176
177            curr -= 1;
178            *buf_ptr.offset(curr) = b'.';
179
180            write_num(&mut n, &mut curr, buf_ptr, lut_ptr);
181        }
182
183        // Write out the number with a fraction
184        wr.write_all(
185            slice::from_raw_parts(
186                buf_ptr.offset(curr),
187                BUF_LEN - curr as usize
188            )
189        )?;
190
191        // Omit the 'e' notation for e == 0
192        if e == 0 {
193            return Ok(());
194        }
195        // Write the remaining `e` notation, with proper sign
196        if exponent_positive {
197            wr.write_all(b"e+")?;
198        } else {
199            wr.write_all(b"e-")?;
200        }
201        return write(wr, true, e as u64, 0);
202
203    }
204
205    // Exponent greater than 0
206    write_num(&mut n, &mut curr, buf_ptr, lut_ptr);
207    let printed = BUF_LEN - curr as usize;
208
209    // No need for `e` notation, just print out zeroes
210    if (printed + exponent as usize) <= 20 {
211        wr.write_all(
212            slice::from_raw_parts(
213                buf_ptr.offset(curr),
214                BUF_LEN - curr as usize
215            )
216        )?;
217
218        return wr.write_all(&ZEROFILL[ .. exponent as usize]);
219    }
220
221    let mut e = exponent as u64;
222
223    // More than one digit, turn into a fraction
224    if printed != 1 {
225        *buf_ptr.offset(curr - 1) = *buf_ptr.offset(curr);
226        *buf_ptr.offset(curr) = b'.';
227        curr -= 1;
228        e += (printed as u64) - 1;
229    }
230
231    wr.write_all(
232        slice::from_raw_parts(
233            buf_ptr.offset(curr),
234            BUF_LEN - curr as usize
235        )
236    )?;
237    wr.write_all(b"e")?;
238    write(wr, true, e, 0)
239}
240
241fn safe_abs(x : i16) -> u16 {
242    if let Some(y) = x.checked_abs() {
243        y as u16
244    } else {
245        i16::max_value() as u16 + 1u16
246    }
247}