1 //! Low-level API generator.
2 //!
3 //! Uses either the internal "Grisu2", or the external "Grisu3" or "Ryu"
4 //! algorithms provided by `https://github.com/dtolnay`.
5 
6 //  The following benchmarks were run on an "Intel(R) Core(TM) i7-6560U
7 //  CPU @ 2.20GHz" CPU, on Fedora 28, Linux kernel version 4.18.16-200
8 //  (x86-64), using the lexical formatter or `x.parse()`,
9 //  avoiding any inefficiencies in Rust string parsing. The code was
10 //  compiled with LTO and at an optimization level of 3.
11 //
12 //  The benchmarks with `std` were compiled using "rustc 1.29.2 (17a9dc751
13 //  2018-10-05", and the `no_std` benchmarks were compiled using "rustc
14 //  1.31.0-nightly (46880f41b 2018-10-15)".
15 //
16 //  The benchmark code may be found `benches/atof.rs`.
17 //
18 //  # Benchmarks
19 //
20 //  | Type  |  lexical (ns/iter) | libcore (ns/iter)     | Relative Increase |
21 //  |:-----:|:------------------:|:---------------------:|:-----------------:|
22 //  | f32   | 465,584            | 1,884,646             | 4.04x             |
23 //  | f64   | 539,904            | 2,276,839             | 4.22x             |
24 //
25 //  # Raw Benchmarks
26 //
27 //  ```text
28 //  test ftoa_f32_dtoa    ... bench:     917,561 ns/iter (+/- 45,458)
29 //  test ftoa_f32_lexical ... bench:     465,584 ns/iter (+/- 76,158)
30 //  test ftoa_f32_std     ... bench:   1,884,646 ns/iter (+/- 130,721)
31 //  test ftoa_f64_dtoa    ... bench:   1,092,687 ns/iter (+/- 125,136)
32 //  test ftoa_f64_lexical ... bench:     539,904 ns/iter (+/- 29,626)
33 //  test ftoa_f64_std     ... bench:   2,276,839 ns/iter (+/- 64,515)
34 //  ```
35 
36 // Code the generate the benchmark plot:
37 //  import numpy as np
38 //  import pandas as pd
39 //  import matplotlib.pyplot as plt
40 //  plt.style.use('ggplot')
41 //  lexical = np.array([465584, 539904]) / 1e6
42 //  rustcore = np.array([1884646, 2276839]) / 1e6
43 //  dtoa = np.array([917561, 1092687]) / 1e6
44 //  ryu = np.array([432878, 522515]) / 1e6
45 //  index = ["f32", "f64"]
46 //  df = pd.DataFrame({'lexical': lexical, 'rustcore': rustcore, 'dtoa': dtoa, 'ryu': ryu}, index = index, columns=['lexical', 'dtoa', 'ryu', 'rustcore'])
47 //  ax = df.plot.bar(rot=0, figsize=(16, 8), fontsize=14)
48 //  ax.set_ylabel("ms/iter")
49 //  ax.figure.tight_layout()
50 //  ax.legend(loc=2, prop={'size': 14})
51 //  plt.show()
52 
53 use crate::util::*;
54 
55 #[cfg(feature = "radix")]
56 use super::radix::{double_radix, float_radix};
57 
58 // Select the back-end
59 cfg_if! {
60 if #[cfg(feature = "grisu3")] {
61     use super::grisu3::{double_decimal, float_decimal};
62 } else if #[cfg(feature = "ryu")] {
63     use super::ryu::{double_decimal, float_decimal};
64 } else {
65     use super::grisu2::{double_decimal, float_decimal};
66 }}  //cfg_if
67 
68 // TRAITS
69 
70 /// Trait to define serialization of a float to string.
71 pub(crate) trait FloatToString: Float {
72     /// Export float to decimal string with optimized algorithm.
decimal<'a>(self, bytes: &'a mut [u8]) -> usize73     fn decimal<'a>(self, bytes: &'a mut [u8]) -> usize;
74 
75     /// Export float to radix string with slow algorithm.
76     #[cfg(feature = "radix")]
radix<'a>(self, radix: u32, bytes: &'a mut [u8]) -> usize77     fn radix<'a>(self, radix: u32, bytes: &'a mut [u8]) -> usize;
78 }
79 
80 impl FloatToString for f32 {
81     perftools_inline!{
82     fn decimal<'a>(self, bytes: &'a mut [u8]) -> usize {
83         float_decimal(self, bytes)
84     }}
85 
86     perftools_inline!{
87     #[cfg(feature = "radix")]
88     fn radix<'a>(self, radix: u32, bytes: &'a mut [u8]) -> usize {
89         float_radix(self, radix, bytes)
90     }}
91 }
92 
93 impl FloatToString for f64 {
94     perftools_inline!{
95     fn decimal<'a>(self, bytes: &'a mut [u8]) -> usize {
96         double_decimal(self, bytes)
97     }}
98 
99     perftools_inline!{
100     #[cfg(feature = "radix")]
101     fn radix<'a>(self, radix: u32, bytes: &'a mut [u8]) -> usize {
102         double_radix(self, radix, bytes)
103     }}
104 }
105 
106 // FTOA
107 
108 // Forward the correct arguments the ideal encoder.
109 perftools_inline!{
110 fn forward<'a, F: FloatToString>(value: F, radix: u32, bytes: &'a mut [u8])
111     -> usize
112 {
113     debug_assert_radix!(radix);
114 
115     #[cfg(not(feature = "radix"))] {
116         value.decimal(bytes)
117     }
118 
119     #[cfg(feature = "radix")] {
120         match radix {
121             10 => value.decimal(bytes),
122             _  => value.radix(radix, bytes),
123         }
124     }
125 }}
126 
127 // Convert float-to-string and handle special (positive) floats.
128 perftools_inline!{
129 fn filter_special<'a, F: FloatToString>(value: F, radix: u32, bytes: &'a mut [u8])
130     -> usize
131 {
132     // Logic errors, disable in release builds.
133     debug_assert!(value.is_sign_positive(), "Value cannot be negative.");
134     debug_assert_radix!(radix);
135 
136     // We already check for 0 in `filter_sign` if value.is_zero().
137     #[cfg(not(feature = "trim_floats"))] {
138         if value.is_zero() {
139             // This is safe, because we confirmed the buffer is >= 4
140             // in total (since we also handled the sign by here).
141             return copy_to_dst(bytes, b"0.0");
142         }
143     }
144 
145     if value.is_nan() {
146         // This is safe, because we confirmed the buffer is >= F::FORMATTED_SIZE.
147         // We have up to `F::FORMATTED_SIZE - 1` bytes from `get_nan_string()`,
148         // and up to 1 byte from the sign.
149         copy_to_dst(bytes, get_nan_string())
150     } else if value.is_special() {
151         // This is safe, because we confirmed the buffer is >= F::FORMATTED_SIZE.
152         // We have up to `F::FORMATTED_SIZE - 1` bytes from `get_inf_string()`,
153         // and up to 1 byte from the sign.
154         copy_to_dst(bytes, get_inf_string())
155     } else {
156         forward(value, radix, bytes)
157     }
158 }}
159 
160 // Handle +/- values.
161 perftools_inline!{
162 fn filter_sign<'a, F: FloatToString>(value: F, radix: u32, bytes: &'a mut [u8])
163     -> usize
164 {
165     debug_assert_radix!(radix);
166 
167     // Export "-0.0" and "0.0" as "0" with trimmed floats.
168     #[cfg(feature = "trim_floats")] {
169         if value.is_zero() {
170             // We know this is safe, because we confirmed the buffer is >= 1.
171             index_mut!(bytes[0] = b'0');
172             return 1;
173         }
174     }
175 
176     // If the sign bit is set, invert it and just set the first
177     // value to "-".
178     if value.is_sign_negative() {
179         let value = -value;
180         // We know this is safe, because we confirmed the buffer is >= 1.
181         index_mut!(bytes[0] = b'-');
182         let bytes = &mut index_mut!(bytes[1..]);
183         filter_special(value, radix, bytes) + 1
184     } else {
185         filter_special(value, radix, bytes)
186     }
187 }}
188 
189 // Write float to string..
190 perftools_inline!{
191 fn ftoa<F: FloatToString>(value: F, radix: u32, bytes: &mut [u8])
192     -> usize
193 {
194     let len = filter_sign(value, radix, bytes);
195     let bytes = &mut index_mut!(bytes[..len]);
196     trim(bytes)
197 }}
198 
199 // Trim a trailing ".0" from a float.
200 perftools_inline!{
201 fn trim<'a>(bytes: &'a mut [u8])
202     -> usize
203 {
204     // Trim a trailing ".0" from a float.
205     if cfg!(feature = "trim_floats") && ends_with_slice(bytes, b".0") {
206         bytes.len() - 2
207     } else {
208         bytes.len()
209     }
210 }}
211 
212 // TO LEXICAL
213 
214 to_lexical!(ftoa, f32);
215 to_lexical!(ftoa, f64);
216 
217 // TESTS
218 // -----
219 
220 #[cfg(test)]
221 mod tests {
222     use crate::util::*;
223     use crate::util::test::*;
224 
225     #[cfg(all(feature = "correct", feature = "property_tests"))]
226     use quickcheck::quickcheck;
227 
228     #[cfg(all(feature = "correct", feature = "std", feature = "property_tests"))]
229     use proptest::{proptest, prop_assert_eq};
230     use approx::assert_relative_eq;
231 
232     // Test data for roundtrips.
233     const F32_DATA : [f32; 31] = [0., 0.1, 1., 1.1, 12., 12.1, 123., 123.1, 1234., 1234.1, 12345., 12345.1, 123456., 123456.1, 1234567., 1234567.1, 12345678., 12345678.1, 123456789., 123456789.1, 123456789.12, 123456789.123, 123456789.1234, 123456789.12345, 1.2345678912345e8, 1.2345e+8, 1.2345e+11, 1.2345e+38, 1.2345e-8, 1.2345e-11, 1.2345e-38];
234     const F64_DATA: [f64; 33] = [0., 0.1, 1., 1.1, 12., 12.1, 123., 123.1, 1234., 1234.1, 12345., 12345.1, 123456., 123456.1, 1234567., 1234567.1, 12345678., 12345678.1, 123456789., 123456789.1, 123456789.12, 123456789.123, 123456789.1234, 123456789.12345, 1.2345678912345e8, 1.2345e+8, 1.2345e+11, 1.2345e+38, 1.2345e+308, 1.2345e-8, 1.2345e-11, 1.2345e-38, 1.2345e-299];
235 
236     #[cfg(feature = "radix")]
237     #[test]
f32_binary_test()238     fn f32_binary_test() {
239         let mut buffer = new_buffer();
240         // positive
241         #[cfg(feature = "trim_floats")] {
242             assert_eq!(as_slice(b"0"), 0.0f32.to_lexical_radix(2, &mut buffer));
243             assert_eq!(as_slice(b"0"), (-0.0f32).to_lexical_radix(2, &mut buffer));
244             assert_eq!(as_slice(b"1"), 1.0f32.to_lexical_radix(2, &mut buffer));
245             assert_eq!(as_slice(b"10"), 2.0f32.to_lexical_radix(2, &mut buffer));
246         }
247 
248         #[cfg(not(feature = "trim_floats"))] {
249             assert_eq!(as_slice(b"0.0"), 0.0f32.to_lexical_radix(2, &mut buffer));
250             assert_eq!(as_slice(b"-0.0"), (-0.0f32).to_lexical_radix(2, &mut buffer));
251             assert_eq!(as_slice(b"1.0"), 1.0f32.to_lexical_radix(2, &mut buffer));
252             assert_eq!(as_slice(b"10.0"), 2.0f32.to_lexical_radix(2, &mut buffer));
253         }
254 
255         assert_eq!(as_slice(b"1.1"), 1.5f32.to_lexical_radix(2, &mut buffer));
256         assert_eq!(as_slice(b"1.01"), 1.25f32.to_lexical_radix(2, &mut buffer));
257         assert_eq!(b"1.001111000000110010", &1.2345678901234567890e0f32.to_lexical_radix(2, &mut buffer)[..20]);
258         assert_eq!(b"1100.010110000111111", &1.2345678901234567890e1f32.to_lexical_radix(2, &mut buffer)[..20]);
259         assert_eq!(b"1111011.011101001111", &1.2345678901234567890e2f32.to_lexical_radix(2, &mut buffer)[..20]);
260         assert_eq!(b"10011010010.10010001", &1.2345678901234567890e3f32.to_lexical_radix(2, &mut buffer)[..20]);
261 
262         // negative
263         assert_eq!(b"-1.001111000000110010", &(-1.2345678901234567890e0f32).to_lexical_radix(2, &mut buffer)[..21]);
264         assert_eq!(b"-1100.010110000111111", &(-1.2345678901234567890e1f32).to_lexical_radix(2, &mut buffer)[..21]);
265         assert_eq!(b"-1111011.011101001111", &(-1.2345678901234567890e2f32).to_lexical_radix(2, &mut buffer)[..21]);
266         assert_eq!(b"-10011010010.10010001", &(-1.2345678901234567890e3f32).to_lexical_radix(2, &mut buffer)[..21]);
267 
268         // special
269         assert_eq!(as_slice(b"NaN"), f32::NAN.to_lexical_radix(2, &mut buffer));
270         assert_eq!(as_slice(b"inf"), f32::INFINITY.to_lexical_radix(2, &mut buffer));
271 
272         // bugfixes
273         assert_eq!(as_slice(b"1.1010100000101011110001e-11011"), 0.000000012345f32.to_lexical_radix(2, &mut buffer));
274     }
275 
276     #[test]
f32_decimal_test()277     fn f32_decimal_test() {
278         let mut buffer = new_buffer();
279         // positive
280         #[cfg(feature = "trim_floats")] {
281             assert_eq!(as_slice(b"0"), 0.0f32.to_lexical(&mut buffer));
282             assert_eq!(as_slice(b"0"), (-0.0f32).to_lexical(&mut buffer));
283             assert_eq!(as_slice(b"1"), 1.0f32.to_lexical(&mut buffer));
284             assert_eq!(as_slice(b"10"), 10.0f32.to_lexical(&mut buffer));
285         }
286 
287         #[cfg(not(feature = "trim_floats"))] {
288             assert_eq!(as_slice(b"0.0"), 0.0f32.to_lexical(&mut buffer));
289             assert_eq!(as_slice(b"-0.0"), (-0.0f32).to_lexical(&mut buffer));
290             assert_eq!(as_slice(b"1.0"), 1.0f32.to_lexical(&mut buffer));
291             assert_eq!(as_slice(b"10.0"), 10.0f32.to_lexical(&mut buffer));
292         }
293 
294         assert_eq!(as_slice(b"1.234567"), &1.2345678901234567890e0f32.to_lexical(&mut buffer)[..8]);
295         assert_eq!(as_slice(b"12.34567"), &1.2345678901234567890e1f32.to_lexical(&mut buffer)[..8]);
296         assert_eq!(as_slice(b"123.4567"), &1.2345678901234567890e2f32.to_lexical(&mut buffer)[..8]);
297         assert_eq!(as_slice(b"1234.567"), &1.2345678901234567890e3f32.to_lexical(&mut buffer)[..8]);
298 
299         // negative
300         assert_eq!(as_slice(b"-1.234567"), &(-1.2345678901234567890e0f32).to_lexical(&mut buffer)[..9]);
301         assert_eq!(as_slice(b"-12.34567"), &(-1.2345678901234567890e1f32).to_lexical(&mut buffer)[..9]);
302         assert_eq!(as_slice(b"-123.4567"), &(-1.2345678901234567890e2f32).to_lexical(&mut buffer)[..9]);
303         assert_eq!(as_slice(b"-1234.567"), &(-1.2345678901234567890e3f32).to_lexical(&mut buffer)[..9]);
304 
305         // special
306         assert_eq!(as_slice(b"NaN"), f32::NAN.to_lexical(&mut buffer));
307         assert_eq!(as_slice(b"inf"), f32::INFINITY.to_lexical(&mut buffer));
308     }
309 
310     #[test]
f32_decimal_roundtrip_test()311     fn f32_decimal_roundtrip_test() {
312         let mut buffer = new_buffer();
313         for &f in F32_DATA.iter() {
314             let s = f.to_lexical(&mut buffer);
315             assert_relative_eq!(f32::from_lexical(s).unwrap(), f, epsilon=1e-6, max_relative=1e-6);
316         }
317     }
318 
319     #[cfg(feature = "radix")]
320     #[test]
f32_radix_roundtrip_test()321     fn f32_radix_roundtrip_test() {
322         let mut buffer = new_buffer();
323         for &f in F32_DATA.iter() {
324             for radix in 2..37 {
325                 // The lower accuracy is due to slight rounding errors of
326                 // ftoa for the Grisu method with non-10 bases.
327                 let s = f.to_lexical_radix(radix, &mut buffer);
328                 assert_relative_eq!(f32::from_lexical_radix(s, radix).unwrap(), f, max_relative=2e-5);
329             }
330         }
331     }
332 
333     #[cfg(feature = "radix")]
334     #[test]
f64_binary_test()335     fn f64_binary_test() {
336         let mut buffer = new_buffer();
337         // positive
338         #[cfg(feature = "trim_floats")] {
339             assert_eq!(as_slice(b"0"), 0.0f64.to_lexical_radix(2, &mut buffer));
340             assert_eq!(as_slice(b"0"), (-0.0f64).to_lexical_radix(2, &mut buffer));
341             assert_eq!(as_slice(b"1"), 1.0f64.to_lexical_radix(2, &mut buffer));
342             assert_eq!(as_slice(b"10"), 2.0f64.to_lexical_radix(2, &mut buffer));
343         }
344 
345         #[cfg(not(feature = "trim_floats"))] {
346             assert_eq!(as_slice(b"0.0"), 0.0f64.to_lexical_radix(2, &mut buffer));
347             assert_eq!(as_slice(b"-0.0"), (-0.0f64).to_lexical_radix(2, &mut buffer));
348             assert_eq!(as_slice(b"1.0"), 1.0f64.to_lexical_radix(2, &mut buffer));
349             assert_eq!(as_slice(b"10.0"), 2.0f64.to_lexical_radix(2, &mut buffer));
350         }
351 
352         assert_eq!(as_slice(b"1.00111100000011001010010000101000110001"), &1.2345678901234567890e0f64.to_lexical_radix(2, &mut buffer)[..40]);
353         assert_eq!(as_slice(b"1100.01011000011111100110100110010111101"), &1.2345678901234567890e1f64.to_lexical_radix(2, &mut buffer)[..40]);
354         assert_eq!(as_slice(b"1111011.01110100111100000001111111101101"), &1.2345678901234567890e2f64.to_lexical_radix(2, &mut buffer)[..40]);
355         assert_eq!(as_slice(b"10011010010.1001000101100001001111110100"), &1.2345678901234567890e3f64.to_lexical_radix(2, &mut buffer)[..40]);
356 
357         // negative
358         assert_eq!(as_slice(b"-1.00111100000011001010010000101000110001"), &(-1.2345678901234567890e0f64).to_lexical_radix(2, &mut buffer)[..41]);
359         assert_eq!(as_slice(b"-1100.01011000011111100110100110010111101"), &(-1.2345678901234567890e1f64).to_lexical_radix(2, &mut buffer)[..41]);
360         assert_eq!(as_slice(b"-1111011.01110100111100000001111111101101"), &(-1.2345678901234567890e2f64).to_lexical_radix(2, &mut buffer)[..41]);
361         assert_eq!(as_slice(b"-10011010010.1001000101100001001111110100"), &(-1.2345678901234567890e3f64).to_lexical_radix(2, &mut buffer)[..41]);
362 
363         // special
364         assert_eq!(as_slice(b"NaN"), f64::NAN.to_lexical_radix(2, &mut buffer));
365         assert_eq!(as_slice(b"inf"), f64::INFINITY.to_lexical_radix(2, &mut buffer));
366     }
367 
368     #[test]
f64_decimal_test()369     fn f64_decimal_test() {
370         let mut buffer = new_buffer();
371         // positive
372         #[cfg(feature = "trim_floats")] {
373             assert_eq!(as_slice(b"0"), 0.0.to_lexical(&mut buffer));
374             assert_eq!(as_slice(b"0"), (-0.0).to_lexical(&mut buffer));
375             assert_eq!(as_slice(b"1"), 1.0.to_lexical(&mut buffer));
376             assert_eq!(as_slice(b"10"), 10.0.to_lexical(&mut buffer));
377         }
378 
379         #[cfg(not(feature = "trim_floats"))] {
380             assert_eq!(as_slice(b"0.0"), 0.0.to_lexical(&mut buffer));
381             assert_eq!(as_slice(b"-0.0"), (-0.0).to_lexical(&mut buffer));
382             assert_eq!(as_slice(b"1.0"), 1.0.to_lexical(&mut buffer));
383             assert_eq!(as_slice(b"10.0"), 10.0.to_lexical(&mut buffer));
384         }
385 
386         assert_eq!(as_slice(b"1.234567"), &1.2345678901234567890e0.to_lexical(&mut buffer)[..8]);
387         assert_eq!(as_slice(b"12.34567"), &1.2345678901234567890e1.to_lexical(&mut buffer)[..8]);
388         assert_eq!(as_slice(b"123.4567"), &1.2345678901234567890e2.to_lexical(&mut buffer)[..8]);
389         assert_eq!(as_slice(b"1234.567"), &1.2345678901234567890e3.to_lexical(&mut buffer)[..8]);
390 
391         // negative
392         assert_eq!(as_slice(b"-1.234567"), &(-1.2345678901234567890e0).to_lexical(&mut buffer)[..9]);
393         assert_eq!(as_slice(b"-12.34567"), &(-1.2345678901234567890e1).to_lexical(&mut buffer)[..9]);
394         assert_eq!(as_slice(b"-123.4567"), &(-1.2345678901234567890e2).to_lexical(&mut buffer)[..9]);
395         assert_eq!(as_slice(b"-1234.567"), &(-1.2345678901234567890e3).to_lexical(&mut buffer)[..9]);
396 
397         // special
398         assert_eq!(b"NaN".to_vec(), f64::NAN.to_lexical(&mut buffer));
399         assert_eq!(b"inf".to_vec(), f64::INFINITY.to_lexical(&mut buffer));
400     }
401 
402     #[test]
f64_decimal_roundtrip_test()403     fn f64_decimal_roundtrip_test() {
404         let mut buffer = new_buffer();
405         for &f in F64_DATA.iter() {
406             let s = f.to_lexical(&mut buffer);
407             assert_relative_eq!(f64::from_lexical(s).unwrap(), f, epsilon=1e-12, max_relative=1e-12);
408         }
409     }
410 
411     #[cfg(feature = "radix")]
412     #[test]
f64_radix_roundtrip_test()413     fn f64_radix_roundtrip_test() {
414         let mut buffer = new_buffer();
415         for &f in F64_DATA.iter() {
416             for radix in 2..37 {
417                 // The lower accuracy is due to slight rounding errors of
418                 // ftoa for the Grisu method with non-10 bases.
419                 let s = f.to_lexical_radix(radix, &mut buffer);
420                 assert_relative_eq!(f64::from_lexical_radix(s, radix).unwrap(), f, max_relative=3e-5);
421             }
422         }
423     }
424 
425     #[cfg(all(feature = "correct", feature = "property_tests"))]
426     quickcheck! {
427         fn f32_quickcheck(f: f32) -> bool {
428             let mut buffer = new_buffer();
429             let parsed = f32::from_lexical(f.to_lexical(&mut buffer)).unwrap();
430             if f.is_nan() {
431                 parsed.is_nan()
432             } else {
433                 f == parsed
434             }
435         }
436 
437         fn f64_quickcheck(f: f64) -> bool {
438             let mut buffer = new_buffer();
439             let parsed = f64::from_lexical(f.to_lexical(&mut buffer)).unwrap();
440             if f.is_nan() {
441                 parsed.is_nan()
442             } else {
443                 f == parsed
444             }
445         }
446     }
447 
448     #[cfg(all(feature = "correct", feature = "std", feature = "property_tests"))]
449     proptest! {
450         #[test]
451         fn f32_proptest(i in f32::MIN..f32::MAX) {
452             let mut buffer = new_buffer();
453             prop_assert_eq!(i, f32::from_lexical(i.to_lexical(&mut buffer)).unwrap());
454         }
455 
456         #[test]
457         fn f64_proptest(i in f64::MIN..f64::MAX) {
458             let mut buffer = new_buffer();
459             prop_assert_eq!(i, f64::from_lexical(i.to_lexical(&mut buffer)).unwrap());
460         }
461     }
462 
463     #[test]
464     #[should_panic]
f32_buffer_test()465     fn f32_buffer_test() {
466         let mut buffer = [b'0'; f32::FORMATTED_SIZE_DECIMAL-1];
467         1.2345f32.to_lexical(&mut buffer);
468     }
469 
470     #[test]
471     #[should_panic]
f64_buffer_test()472     fn f64_buffer_test() {
473         let mut buffer = [b'0'; f64::FORMATTED_SIZE_DECIMAL-1];
474         1.2345f64.to_lexical(&mut buffer);
475     }
476 }
477