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