dtoa_short/
lib.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5#![no_std]
6
7extern crate dtoa;
8
9use core::fmt::Write;
10use core::{fmt, str};
11
12/// Format the given `value` into `dest` and return the notation it uses.
13#[inline]
14pub fn write<W: Write, V: Floating>(dest: &mut W, value: V) -> DtoaResult {
15    Floating::write(value, dest)
16}
17
18/// Form of the formatted floating-point number.
19#[derive(Debug, PartialEq, Eq, Clone, Copy)]
20pub struct Notation {
21    /// Whether it contains a decimal point.
22    pub decimal_point: bool,
23    /// Whether it uses E-notation.
24    pub scientific: bool,
25}
26
27impl Notation {
28    fn integer() -> Self {
29        Notation {
30            decimal_point: false,
31            scientific: false,
32        }
33    }
34}
35
36/// Result of formatting the number.
37pub type DtoaResult = Result<Notation, fmt::Error>;
38
39pub trait Floating : dtoa::Float {
40    fn write<W: Write>(self, dest: &mut W) -> DtoaResult;
41}
42
43impl Floating for f32 {
44    fn write<W: Write>(self, dest: &mut W) -> DtoaResult {
45        write_with_prec(dest, self, 6)
46    }
47}
48
49impl Floating for f64 {
50    fn write<W: Write>(self, dest: &mut W) -> DtoaResult {
51        write_with_prec(dest, self, 15)
52    }
53}
54
55fn write_with_prec<W, V>(dest: &mut W, value: V, prec: usize) -> DtoaResult
56where
57    W: Write,
58    V: dtoa::Float
59{
60    let mut buf = dtoa::Buffer::new();
61    let str = buf.format_finite(value);
62
63    const SCRATCH_LEN: usize = core::mem::size_of::<dtoa::Buffer>() + 1;
64    let mut scratch = [b'\0'; SCRATCH_LEN];
65    debug_assert!(str.len() < SCRATCH_LEN);
66    unsafe {
67        core::ptr::copy_nonoverlapping(str.as_bytes().as_ptr(), scratch.as_mut_ptr().offset(1), str.len());
68    }
69    let (result, notation) = restrict_prec(&mut scratch[0..str.len() + 1], prec);
70    dest.write_str(if cfg!(debug_assertions) {
71        str::from_utf8(result).unwrap()
72    } else {
73        // safety: dtoa only generates ascii.
74        unsafe { str::from_utf8_unchecked(result) }
75    })?;
76    Ok(notation)
77}
78
79fn restrict_prec(buf: &mut [u8], prec: usize) -> (&[u8], Notation) {
80    let len = buf.len();
81    // Put a leading zero to capture any carry.
82    debug_assert!(buf[0] == b'\0', "Caller must prepare an empty byte for us");
83    buf[0] = b'0';
84    // Remove the sign for now. We will put it back at the end.
85    let sign = match buf[1] {
86        s @ b'+' | s @ b'-' => {
87            buf[1] = b'0';
88            Some(s)
89        }
90        _ => None,
91    };
92    // Locate dot, exponent, and the first significant digit.
93    let mut pos_dot = None;
94    let mut pos_exp = None;
95    let mut prec_start = None;
96    for i in 1..len {
97        if buf[i] == b'.' {
98            debug_assert!(pos_dot.is_none());
99            pos_dot = Some(i);
100        } else if buf[i] == b'e' {
101            pos_exp = Some(i);
102            // We don't change exponent part, so stop here.
103            break;
104        } else if prec_start.is_none() && buf[i] != b'0' {
105            debug_assert!(buf[i] >= b'1' && buf[i] <= b'9');
106            prec_start = Some(i);
107        }
108    }
109    let prec_start = match prec_start {
110        Some(i) => i,
111        // If there is no non-zero digit at all, it is just zero.
112        None => return (&buf[0..1], Notation::integer()),
113    };
114    // Coefficient part ends at 'e' or the length.
115    let coeff_end = pos_exp.unwrap_or(len);
116    // Decimal dot is effectively at the end of coefficient part if no
117    // dot presents before that.
118    let pos_dot = pos_dot.unwrap_or(coeff_end);
119    // Find the end position of the number within the given precision.
120    let prec_end = {
121        let end = prec_start + prec;
122        if pos_dot > prec_start && pos_dot <= end {
123            end + 1
124        } else {
125            end
126        }
127    };
128    let mut new_coeff_end = coeff_end;
129    if prec_end < coeff_end {
130        // Round to the given precision.
131        let next_char = buf[prec_end];
132        new_coeff_end = prec_end;
133        if next_char >= b'5' {
134            for i in (0..prec_end).rev() {
135                if buf[i] == b'.' {
136                    continue;
137                }
138                if buf[i] != b'9' {
139                    buf[i] += 1;
140                    new_coeff_end = i + 1;
141                    break;
142                }
143                buf[i] = b'0';
144            }
145        }
146    }
147    if new_coeff_end < pos_dot {
148        // If the precision isn't enough to reach the dot, set all digits
149        // in-between to zero and keep the number until the dot.
150        for i in new_coeff_end..pos_dot {
151            buf[i] = b'0';
152        }
153        new_coeff_end = pos_dot;
154    } else {
155        // Strip any trailing zeros.
156        for i in (0..new_coeff_end).rev() {
157            if buf[i] != b'0' {
158                if buf[i] == b'.' {
159                    new_coeff_end = i;
160                }
161                break;
162            }
163            new_coeff_end = i;
164        }
165    }
166    // Move exponent part if necessary.
167    let real_end = if let Some(pos_exp) = pos_exp {
168        let exp_len = len - pos_exp;
169        if new_coeff_end != pos_exp {
170            for i in 0..exp_len {
171                buf[new_coeff_end + i] = buf[pos_exp + i];
172            }
173        }
174        new_coeff_end + exp_len
175    } else {
176        new_coeff_end
177    };
178    // Add back the sign and strip the leading zero.
179    let result = if let Some(sign) = sign {
180        if buf[1] == b'0' && buf[2] != b'.' {
181            buf[1] = sign;
182            &buf[1..real_end]
183        } else {
184            debug_assert!(buf[0] == b'0');
185            buf[0] = sign;
186            &buf[0..real_end]
187        }
188    } else {
189        if buf[0] == b'0' && buf[1] != b'.' {
190            &buf[1..real_end]
191        } else {
192            &buf[0..real_end]
193        }
194    };
195    // Generate the notation info.
196    let notation = Notation {
197        decimal_point: pos_dot < new_coeff_end,
198        scientific: pos_exp.is_some(),
199    };
200    (result, notation)
201}