1 //! Adaptation of the V8 ftoa algorithm with a custom radix.
2 //!
3 //! This algorithm is adapted from the V8 codebase,
4 //! and may be found [here](https://github.com/v8/v8).
5 
6 use crate::itoa;
7 use crate::util::*;
8 
9 // FTOA BASEN
10 // ----------
11 
12 // Export a character to digit.
13 macro_rules! to_digit {
14     ($c:expr, $radix:ident) => (($c as char).to_digit($radix));
15 }
16 
17 // Calculate the naive exponent from a minimal value.
18 //
19 // Don't export this for float, since it's specialized for radix.
20 perftools_inline!{
21 pub(crate) fn naive_exponent(d: f64, radix: u32) -> i32
22 {
23     // floor returns the minimal value, which is our
24     // desired exponent
25     // ln(1.1e-5) -> -4.95 -> -5
26     // ln(1.1e5) -> -5.04 -> 5
27     (d.ln() / (radix as f64).ln()).floor() as i32
28 }}
29 
30 /// Naive algorithm for converting a floating point to a custom radix.
31 ///
32 /// `d` must be non-special (NaN or infinite), non-negative,
33 /// and non-zero.
34 ///
35 /// Adapted from the V8 implementation.
ftoa_naive<'a>(value: f64, radix: u32, bytes: &'a mut [u8]) -> usize36 fn ftoa_naive<'a>(value: f64, radix: u32, bytes: &'a mut [u8])
37     -> usize
38 {
39     debug_assert_radix!(radix);
40 
41     // Assert no special cases remain, no non-zero values,
42     // and no negative numbers.
43     debug_assert!(!value.is_special());
44     debug_assert!(value != 0.0);
45     debug_assert!(value > 0.0);
46 
47     // Store the first digit and up to `BUFFER_SIZE - 20` digits
48     // that occur from left-to-right in the decimal representation.
49     // For example, for the number 123.45, store the first digit `1`
50     // and `2345` as the remaining values. Then, decide on-the-fly
51     // if we need scientific or regular formatting.
52     //
53     //   BUFFER_SIZE
54     // - 1      # first digit
55     // - 1      # period
56     // - 1      # +/- sign
57     // - 2      # e and +/- sign
58     // - 9      # max exp is 308, in radix2 is 9
59     // - 1      # null terminator
60     // = 15 characters of formatting required
61     // Just pad it a bit, we don't want memory corruption.
62     const MAX_NONDIGIT_LENGTH: usize = 25;
63     const MAX_DIGIT_LENGTH: usize = BUFFER_SIZE - MAX_NONDIGIT_LENGTH;
64 
65     // Temporary buffer for the result. We start with the decimal point in the
66     // middle and write to the left for the integer part and to the right for the
67     // fractional part. 1024 characters for the exponent and 52 for the mantissa
68     // either way, with additional space for sign, decimal point and string
69     // termination should be sufficient.
70     const SIZE: usize = 2200;
71     let mut buffer: [u8; SIZE] = [b'\0'; SIZE];
72     //let buffer = buffer.as_mut_ptr();
73     let initial_position: usize = SIZE / 2;
74     let mut integer_cursor = initial_position;
75     let mut fraction_cursor = initial_position;
76     let base = radix as f64;
77 
78     // Split the value into an integer part and a fractional part.
79     let mut integer = value.floor();
80     let mut fraction = value - integer;
81 
82     // We only compute fractional digits up to the input double's precision.
83     let mut delta = 0.5 * (value.next_positive() - value);
84     delta = 0.0.next_positive().max_finite(delta);
85     debug_assert!(delta > 0.0);
86 
87     // Don't remove bounds checks, for a few reasons.
88     //  1. Difficult to determine statically.
89     //  2. Algorithm is fairly slow, in general, so performance isn't a major deal.
90     if fraction > delta {
91         loop {
92             // Shift up by one digit.
93             fraction *= base;
94             delta *= base;
95             // Write digit.
96             let digit = fraction as i32;
97             buffer[fraction_cursor] = digit_to_char(digit);
98             fraction_cursor += 1;
99             // Calculate remainder.
100             fraction -= digit as f64;
101             // Round to even.
102             if fraction > 0.5 || (fraction == 0.5 && (digit & 1) != 0) {
103                 if fraction + delta > 1.0 {
104                     // We need to back trace already written digits in case of carry-over.
105                     loop {
106                         fraction_cursor -= 1;
107                         if fraction_cursor == initial_position-1 {
108                             // Carry over to the integer part.
109                             integer += 1.0;
110                             break;
111                         }
112                         // Reconstruct digit.
113                         let c = buffer[fraction_cursor];
114                         if let Some(digit) = to_digit!(c, radix) {
115                             let idx = (digit + 1) as usize;
116                             buffer[fraction_cursor] = digit_to_char(idx);
117                             fraction_cursor += 1;
118                             break;
119                         }
120                     }
121                     break;
122                 }
123             }
124 
125             if delta >= fraction {
126                 break;
127             }
128         }
129     }
130 
131     // Compute integer digits. Fill unrepresented digits with zero.
132     while (integer / base).exponent() > 0 {
133         integer /= base;
134         integer_cursor -= 1;
135         buffer[integer_cursor] = b'0';
136     }
137 
138     loop {
139         let remainder = integer % base;
140         integer_cursor -= 1;
141         let idx = remainder as usize;
142         buffer[integer_cursor] = digit_to_char(idx);
143         integer = (integer - remainder) / base;
144 
145         if integer <= 0.0 {
146             break;
147         }
148     };
149 
150     if value <= 1e-5 || value >= 1e9 {
151         // write scientific notation with negative exponent
152         let exponent = naive_exponent(value, radix);
153 
154         // Non-exponent portion.
155         // 1.   Get as many digits as possible, up to `MAX_DIGIT_LENGTH+1`
156         //      (since we are ignoring the digit for the first digit),
157         //      or the number of written digits
158         let start: usize;
159         let end: usize;
160         if value <= 1e-5 {
161             start = ((initial_position as i32) - exponent - 1) as usize;
162             end = fraction_cursor.min(start + MAX_DIGIT_LENGTH + 1);
163         } else {
164             start = integer_cursor;
165             end = fraction_cursor.min(start + MAX_DIGIT_LENGTH + 1);
166         }
167         let buffer = &buffer[start..end];
168 
169         // 2.   Remove any trailing 0s in the selected range.
170         let buffer = rtrim_char_slice(buffer, b'0').0;
171 
172         // 3.   Write the fraction component
173         bytes[0] = buffer[0];
174         bytes[1] = b'.';
175         let count = copy_to_dst(&mut bytes[2..], &buffer[1..]);
176         let bytes = &mut bytes[count+2..];
177 
178         // write the exponent component
179         bytes[0] = exponent_notation_char(radix);
180         // Handle negative exponents.
181         let exp: u32;
182         if exponent < 0 {
183             bytes[1] = b'-';
184             exp = exponent.wrapping_neg() as u32;
185             itoa::itoa_positive(exp, radix, &mut bytes[2..]) + count + 4
186         } else {
187             exp = exponent as u32;
188             itoa::itoa_positive(exp, radix, &mut bytes[1..]) + count + 3
189         }
190     } else {
191         // get component lengths
192         let integer_length = initial_position - integer_cursor;
193         let fraction_length = (fraction_cursor - initial_position).min(MAX_DIGIT_LENGTH - integer_length);
194 
195         // write integer component
196         let start = integer_cursor;
197         let end = integer_cursor + integer_length;
198         let count = copy_to_dst(bytes, &buffer[start..end]);
199         let bytes = &mut bytes[count..];
200 
201         // write fraction component
202         if fraction_length > 0 {
203             // fraction exists, write it
204             bytes[0] = b'.';
205             let start = initial_position;
206             let end = initial_position + fraction_length;
207             copy_to_dst(&mut bytes[1..], &buffer[start..end]);
208             integer_length + fraction_length + 1
209         } else {
210             // no fraction, write decimal place
211             copy_to_dst(bytes, ".0");
212             integer_length + 2
213         }
214     }
215 }
216 
217 // F32
218 
219 // Forward to double_radix.
220 //
221 // `f` must be non-special (NaN or infinite), non-negative,
222 // and non-zero.
223 perftools_inline!{
224 pub(crate) fn float_radix<'a>(f: f32, radix: u32, bytes: &'a mut [u8])
225     -> usize
226 {
227     double_radix(f as f64, radix, bytes)
228 }}
229 
230 // F64
231 
232 // Algorithm for non-decimal string representations.
233 //
234 // `d` must be non-special (NaN or infinite), non-negative,
235 // and non-zero.
236 perftools_inline!{
237 pub(crate) fn double_radix<'a>(value: f64, radix: u32, bytes: &'a mut [u8])
238     -> usize
239 {
240     ftoa_naive(value, radix, bytes)
241 }}
242