Skip to content

Commit 2a6fc89

Browse files
HalidOdatjedel1043
andauthored
Implement Number.prototype.toFixed() (#35)
* Implement draft `Number.prototype.toFixed()` * Add more tests * Some code clean up * Some more code cleanup and add test * Some more code cleanup and add test * Reduce POW10_SPLIT from 1224 to 8 entries * Add some boundry tests * Code cleanup * Reduce POW10_SPLIT_2 from 3133 to 564 * Reduce POW10_SPLIT_2 from 564 to 527 * Fix clippy warnings * Reduce POW10_SPLIT_2 from 527 to 481 * Max buffer size * Refactor and add documentation * Put under feature flag * Some cleanup * Apply review * Add missing newlines * Fix clippy lints * Remove panics in `format64_to_fixed --------- Co-authored-by: José Julián Espina <jedel0124@gmail.com>
1 parent 6138e7c commit 2a6fc89

File tree

8 files changed

+1895
-12
lines changed

8 files changed

+1895
-12
lines changed

.vscode/launch.json

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
{
2+
// Use IntelliSense to learn about possible attributes.
3+
// Hover to view descriptions of existing attributes.
4+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5+
"version": "0.2.0",
6+
"configurations": [
7+
{
8+
"type": "lldb",
9+
"request": "launch",
10+
"name": "Debug unit tests in library 'ryu-js'",
11+
"cargo": {
12+
"args": [
13+
"test",
14+
"--no-run",
15+
"--lib",
16+
"--package=ryu-js"
17+
],
18+
"filter": {
19+
"name": "ryu-js",
20+
"kind": "lib"
21+
}
22+
},
23+
"args": [],
24+
"cwd": "${workspaceFolder}"
25+
},
26+
{
27+
"type": "lldb",
28+
"request": "launch",
29+
"name": "Debug example 'upstream_benchmark'",
30+
"cargo": {
31+
"args": [
32+
"build",
33+
"--example=upstream_benchmark",
34+
"--package=ryu-js"
35+
],
36+
"filter": {
37+
"name": "upstream_benchmark",
38+
"kind": "example"
39+
}
40+
},
41+
"args": [],
42+
"cwd": "${workspaceFolder}"
43+
},
44+
{
45+
"type": "lldb",
46+
"request": "launch",
47+
"name": "Debug unit tests in example 'upstream_benchmark'",
48+
"cargo": {
49+
"args": [
50+
"test",
51+
"--no-run",
52+
"--example=upstream_benchmark",
53+
"--package=ryu-js"
54+
],
55+
"filter": {
56+
"name": "upstream_benchmark",
57+
"kind": "example"
58+
}
59+
},
60+
"args": [],
61+
"cwd": "${workspaceFolder}"
62+
},
63+
{
64+
"type": "lldb",
65+
"request": "launch",
66+
"name": "Debug integration test 'd2s_test'",
67+
"cargo": {
68+
"args": [
69+
"test",
70+
"--no-run",
71+
"--test=d2s_test",
72+
"--package=ryu-js"
73+
],
74+
"filter": {
75+
"name": "d2s_test",
76+
"kind": "test"
77+
}
78+
},
79+
"args": [],
80+
"cwd": "${workspaceFolder}"
81+
},
82+
{
83+
"type": "lldb",
84+
"request": "launch",
85+
"name": "Debug integration test 's2f_test'",
86+
"cargo": {
87+
"args": [
88+
"test",
89+
"--no-run",
90+
"--test=s2f_test",
91+
"--package=ryu-js"
92+
],
93+
"filter": {
94+
"name": "s2f_test",
95+
"kind": "test"
96+
}
97+
},
98+
"args": [],
99+
"cwd": "${workspaceFolder}"
100+
},
101+
{
102+
"type": "lldb",
103+
"request": "launch",
104+
"name": "Debug integration test 'to_fixed'",
105+
"cargo": {
106+
"args": [
107+
"test",
108+
"--no-run",
109+
"--test=to_fixed",
110+
"--package=ryu-js"
111+
],
112+
"filter": {
113+
"name": "to_fixed",
114+
"kind": "test"
115+
}
116+
},
117+
"args": [],
118+
"cwd": "${workspaceFolder}"
119+
},
120+
{
121+
"type": "lldb",
122+
"request": "launch",
123+
"name": "Debug integration test 's2d_test'",
124+
"cargo": {
125+
"args": [
126+
"test",
127+
"--no-run",
128+
"--test=s2d_test",
129+
"--package=ryu-js"
130+
],
131+
"filter": {
132+
"name": "s2d_test",
133+
"kind": "test"
134+
}
135+
},
136+
"args": [],
137+
"cwd": "${workspaceFolder}"
138+
},
139+
{
140+
"type": "lldb",
141+
"request": "launch",
142+
"name": "Debug integration test 'common_test'",
143+
"cargo": {
144+
"args": [
145+
"test",
146+
"--no-run",
147+
"--test=common_test",
148+
"--package=ryu-js"
149+
],
150+
"filter": {
151+
"name": "common_test",
152+
"kind": "test"
153+
}
154+
},
155+
"args": [],
156+
"cwd": "${workspaceFolder}"
157+
},
158+
{
159+
"type": "lldb",
160+
"request": "launch",
161+
"name": "Debug integration test 'exhaustive'",
162+
"cargo": {
163+
"args": [
164+
"test",
165+
"--no-run",
166+
"--test=exhaustive",
167+
"--package=ryu-js"
168+
],
169+
"filter": {
170+
"name": "exhaustive",
171+
"kind": "test"
172+
}
173+
},
174+
"args": [],
175+
"cwd": "${workspaceFolder}"
176+
},
177+
{
178+
"type": "lldb",
179+
"request": "launch",
180+
"name": "Debug integration test 'f2s_test'",
181+
"cargo": {
182+
"args": [
183+
"test",
184+
"--no-run",
185+
"--test=f2s_test",
186+
"--package=ryu-js"
187+
],
188+
"filter": {
189+
"name": "f2s_test",
190+
"kind": "test"
191+
}
192+
},
193+
"args": [],
194+
"cwd": "${workspaceFolder}"
195+
},
196+
{
197+
"type": "lldb",
198+
"request": "launch",
199+
"name": "Debug integration test 'd2s_table_test'",
200+
"cargo": {
201+
"args": [
202+
"test",
203+
"--no-run",
204+
"--test=d2s_table_test",
205+
"--package=ryu-js"
206+
],
207+
"filter": {
208+
"name": "d2s_table_test",
209+
"kind": "test"
210+
}
211+
},
212+
"args": [],
213+
"cwd": "${workspaceFolder}"
214+
},
215+
{
216+
"type": "lldb",
217+
"request": "launch",
218+
"name": "Debug benchmark 'bench'",
219+
"cargo": {
220+
"args": [
221+
"test",
222+
"--no-run",
223+
"--bench=bench",
224+
"--package=ryu-js"
225+
],
226+
"filter": {
227+
"name": "bench",
228+
"kind": "bench"
229+
}
230+
},
231+
"args": [],
232+
"cwd": "${workspaceFolder}"
233+
}
234+
]
235+
}

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,3 @@ targets = ["x86_64-unknown-linux-gnu"]
3838
[[bench]]
3939
name = "bench"
4040
harness = false
41-

src/buffer/mod.rs

Lines changed: 60 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use crate::pretty::to_fixed::MAX_BUFFER_SIZE;
2+
13
use crate::raw;
24
use core::mem::MaybeUninit;
35
use core::{slice, str};
@@ -8,6 +10,8 @@ const NAN: &str = "NaN";
810
const INFINITY: &str = "Infinity";
911
const NEG_INFINITY: &str = "-Infinity";
1012

13+
const BUFFER_SIZE: usize = MAX_BUFFER_SIZE;
14+
1115
/// Safe API for formatting floating point numbers to text.
1216
///
1317
/// ## Example
@@ -17,8 +21,9 @@ const NEG_INFINITY: &str = "-Infinity";
1721
/// let printed = buffer.format_finite(1.234);
1822
/// assert_eq!(printed, "1.234");
1923
/// ```
24+
#[derive(Copy, Clone)]
2025
pub struct Buffer {
21-
bytes: [MaybeUninit<u8>; 25],
26+
bytes: [MaybeUninit<u8>; BUFFER_SIZE],
2227
}
2328

2429
impl Buffer {
@@ -27,7 +32,7 @@ impl Buffer {
2732
#[inline]
2833
#[cfg_attr(feature = "no-panic", no_panic)]
2934
pub fn new() -> Self {
30-
let bytes = [MaybeUninit::<u8>::uninit(); 25];
35+
let bytes = [MaybeUninit::<u8>::uninit(); BUFFER_SIZE];
3136

3237
Buffer { bytes }
3338
}
@@ -80,14 +85,37 @@ impl Buffer {
8085
str::from_utf8_unchecked(slice)
8186
}
8287
}
83-
}
8488

85-
impl Copy for Buffer {}
89+
/// Print a floating point number into this buffer using the `Number.prototype.toFixed()` notation
90+
/// and return a reference to its string representation within the buffer.
91+
///
92+
/// The `fraction_digits` argument must be between `[0, 100]` inclusive,
93+
/// If a values value that is greater than the max is passed in will be clamped to max.
94+
///
95+
/// # Special cases
96+
///
97+
/// This function formats NaN as the string "NaN", positive infinity as
98+
/// "Infinity", and negative infinity as "-Infinity" to match the [ECMAScript specification][spec].
99+
///
100+
/// [spec]: https://tc39.es/ecma262/#sec-numeric-types-number-tofixed
101+
#[cfg_attr(feature = "no-panic", inline)]
102+
#[cfg_attr(feature = "no-panic", no_panic)]
103+
pub fn format_to_fixed<F: FloatToFixed>(&mut self, f: F, fraction_digits: u8) -> &str {
104+
let fraction_digits = fraction_digits.min(100);
105+
106+
if f.is_nonfinite() {
107+
return f.format_nonfinite();
108+
}
86109

87-
impl Clone for Buffer {
88-
#[inline]
89-
fn clone(&self) -> Self {
90-
*self
110+
unsafe {
111+
let n = f.write_to_ryu_buffer_to_fixed(
112+
fraction_digits,
113+
self.bytes.as_mut_ptr().cast::<u8>(),
114+
);
115+
debug_assert!(n <= self.bytes.len());
116+
let slice = slice::from_raw_parts(self.bytes.as_ptr().cast::<u8>(), n);
117+
str::from_utf8_unchecked(slice)
118+
}
91119
}
92120
}
93121

@@ -99,19 +127,31 @@ impl Default for Buffer {
99127
}
100128
}
101129

102-
/// A floating point number, f32 or f64, that can be written into a
130+
/// A floating point number, [`f32`] or [`f64`], that can be written into a
103131
/// [`ryu_js::Buffer`][Buffer].
104132
///
105133
/// This trait is sealed and cannot be implemented for types outside of the
106-
/// `ryu_js` crate.
134+
/// `ryu-js` crate.
107135
pub trait Float: Sealed {}
108136
impl Float for f32 {}
109137
impl Float for f64 {}
110138

139+
/// A floating point number that can be written into a
140+
/// [`ryu_js::Buffer`][Buffer] using the fixed notation as defined in the
141+
/// [`Number.prototype.toFixed( fractionDigits )`][spec] ECMAScript specification.
142+
///
143+
/// This trait is sealed and cannot be implemented for types outside of the
144+
/// `ryu-js` crate.
145+
///
146+
/// [spec]: https://tc39.es/ecma262/#sec-number.prototype.tofixed
147+
pub trait FloatToFixed: Sealed {}
148+
impl FloatToFixed for f64 {}
149+
111150
pub trait Sealed: Copy {
112151
fn is_nonfinite(self) -> bool;
113152
fn format_nonfinite(self) -> &'static str;
114153
unsafe fn write_to_ryu_buffer(self, result: *mut u8) -> usize;
154+
unsafe fn write_to_ryu_buffer_to_fixed(self, fraction_digits: u8, result: *mut u8) -> usize;
115155
}
116156

117157
impl Sealed for f32 {
@@ -141,6 +181,11 @@ impl Sealed for f32 {
141181
unsafe fn write_to_ryu_buffer(self, result: *mut u8) -> usize {
142182
raw::format32(self, result)
143183
}
184+
185+
#[inline]
186+
unsafe fn write_to_ryu_buffer_to_fixed(self, _fraction_digits: u8, _result: *mut u8) -> usize {
187+
panic!("toFixed for f32 type is not implemented yet!")
188+
}
144189
}
145190

146191
impl Sealed for f64 {
@@ -170,4 +215,9 @@ impl Sealed for f64 {
170215
unsafe fn write_to_ryu_buffer(self, result: *mut u8) -> usize {
171216
raw::format64(self, result)
172217
}
218+
219+
#[inline]
220+
unsafe fn write_to_ryu_buffer_to_fixed(self, fraction_digits: u8, result: *mut u8) -> usize {
221+
raw::format64_to_fixed(self, fraction_digits, result)
222+
}
173223
}

src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,10 @@ mod f2s;
8787
mod f2s_intrinsics;
8888
mod pretty;
8989

90-
pub use crate::buffer::{Buffer, Float};
90+
pub use crate::buffer::{Buffer, Float, FloatToFixed};
9191

9292
/// Unsafe functions that mirror the API of the C implementation of Ryū.
9393
pub mod raw {
94+
pub use crate::pretty::format64_to_fixed;
9495
pub use crate::pretty::{format32, format64};
9596
}

src/pretty/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ use core::ptr;
1010
#[cfg(feature = "no-panic")]
1111
use no_panic::no_panic;
1212

13+
pub mod to_fixed;
14+
pub use to_fixed::{format64_to_fixed, Cursor};
15+
1316
/// Print f64 to the given buffer and return number of bytes written.
1417
///
1518
/// At most 25 bytes will be written.

0 commit comments

Comments
 (0)