1 | module strconv |
2 | |
3 | // import math |
4 | |
5 | /* |
6 | f32/f64 to string utilities |
7 | |
8 | Copyright (c) 2019-2023 Dario Deledda. All rights reserved. |
9 | Use of this source code is governed by an MIT license |
10 | that can be found in the LICENSE file. |
11 | |
12 | This file contains the f32/f64 to string utilities functions |
13 | |
14 | These functions are based on the work of: |
15 | Publication:PLDI 2018: Proceedings of the 39th ACM SIGPLAN |
16 | Conference on Programming Language Design and ImplementationJune 2018 |
17 | Pages 270–282 https://doi.org/10.1145/3192366.3192369 |
18 | |
19 | inspired by the Go version here: |
20 | https://github.com/cespare/ryu/tree/ba56a33f39e3bbbfa409095d0f9ae168a595feea |
21 | */ |
22 | |
23 | /* |
24 | f64 to string with string format |
25 | */ |
26 | |
27 | // TODO: Investigate precision issues |
28 | // f32_to_str_l returns `f` as a `string` in decimal notation with a maximum of 6 digits after the dot. |
29 | // |
30 | // Example: assert strconv.f32_to_str_l(34.1234567) == '34.12346' |
31 | [manualfree] |
32 | pub fn f32_to_str_l(f f32) string { |
33 | s := f32_to_str(f, 6) |
34 | res := fxx_to_str_l_parse(s) |
35 | unsafe { s.free() } |
36 | return res |
37 | } |
38 | |
39 | // f32_to_str_l_with_dot returns `f` as a `string` in decimal notation with a maximum of 6 digits after the dot. |
40 | // If the decimal digits after the dot are zero, a '.0' is appended for clarity. |
41 | // |
42 | // Example: assert strconv.f32_to_str_l_with_dot(34.) == '34.0' |
43 | [manualfree] |
44 | pub fn f32_to_str_l_with_dot(f f32) string { |
45 | s := f32_to_str(f, 6) |
46 | res := fxx_to_str_l_parse_with_dot(s) |
47 | unsafe { s.free() } |
48 | return res |
49 | } |
50 | |
51 | // f64_to_str_l returns `f` as a `string` in decimal notation with a maximum of 18 digits after the dot. |
52 | // |
53 | // Example: assert strconv.f64_to_str_l(123.1234567891011121) == '123.12345678910111' |
54 | [manualfree] |
55 | pub fn f64_to_str_l(f f64) string { |
56 | s := f64_to_str(f, 18) |
57 | res := fxx_to_str_l_parse(s) |
58 | unsafe { s.free() } |
59 | return res |
60 | } |
61 | |
62 | // f64_to_str_l_with_dot returns `f` as a `string` in decimal notation with a maximum of 18 digits after the dot. |
63 | // If the decimal digits after the dot are zero, a '.0' is appended for clarity. |
64 | // |
65 | // Example: assert strconv.f64_to_str_l_with_dot (34.) == '34.0' |
66 | [manualfree] |
67 | pub fn f64_to_str_l_with_dot(f f64) string { |
68 | s := f64_to_str(f, 18) |
69 | res := fxx_to_str_l_parse_with_dot(s) |
70 | unsafe { s.free() } |
71 | return res |
72 | } |
73 | |
74 | // fxx_to_str_l_parse returns a `string` in decimal notation converted from a |
75 | // floating-point `string` in scientific notation. |
76 | // |
77 | // Example: assert strconv.fxx_to_str_l_parse('34.22e+00') == '34.22' |
78 | [direct_array_access; manualfree] |
79 | pub fn fxx_to_str_l_parse(s string) string { |
80 | // check for +inf -inf Nan |
81 | if s.len > 2 && (s[0] == `n` || s[1] == `i`) { |
82 | return s.clone() |
83 | } |
84 | |
85 | m_sgn_flag := false |
86 | mut sgn := 1 |
87 | mut b := [26]u8{} |
88 | mut d_pos := 1 |
89 | mut i := 0 |
90 | mut i1 := 0 |
91 | mut exp := 0 |
92 | mut exp_sgn := 1 |
93 | |
94 | // get sign and decimal parts |
95 | for c in s { |
96 | if c == `-` { |
97 | sgn = -1 |
98 | i++ |
99 | } else if c == `+` { |
100 | sgn = 1 |
101 | i++ |
102 | } else if c >= `0` && c <= `9` { |
103 | b[i1] = c |
104 | i1++ |
105 | i++ |
106 | } else if c == `.` { |
107 | if sgn > 0 { |
108 | d_pos = i |
109 | } else { |
110 | d_pos = i - 1 |
111 | } |
112 | i++ |
113 | } else if c == `e` { |
114 | i++ |
115 | break |
116 | } else { |
117 | return 'Float conversion error!!' |
118 | } |
119 | } |
120 | b[i1] = 0 |
121 | |
122 | // get exponent |
123 | if s[i] == `-` { |
124 | exp_sgn = -1 |
125 | i++ |
126 | } else if s[i] == `+` { |
127 | exp_sgn = 1 |
128 | i++ |
129 | } |
130 | |
131 | mut c := i |
132 | for c < s.len { |
133 | exp = exp * 10 + int(s[c] - `0`) |
134 | c++ |
135 | } |
136 | |
137 | // allocate exp+32 chars for the return string |
138 | mut res := []u8{len: exp + 32, init: 0} |
139 | mut r_i := 0 // result string buffer index |
140 | |
141 | // println("s:${sgn} b:${b[0]} es:${exp_sgn} exp:${exp}") |
142 | |
143 | if sgn == 1 { |
144 | if m_sgn_flag { |
145 | res[r_i] = `+` |
146 | r_i++ |
147 | } |
148 | } else { |
149 | res[r_i] = `-` |
150 | r_i++ |
151 | } |
152 | |
153 | i = 0 |
154 | if exp_sgn >= 0 { |
155 | for b[i] != 0 { |
156 | res[r_i] = b[i] |
157 | r_i++ |
158 | i++ |
159 | if i >= d_pos && exp >= 0 { |
160 | if exp == 0 { |
161 | res[r_i] = `.` |
162 | r_i++ |
163 | } |
164 | exp-- |
165 | } |
166 | } |
167 | for exp >= 0 { |
168 | res[r_i] = `0` |
169 | r_i++ |
170 | exp-- |
171 | } |
172 | } else { |
173 | mut dot_p := true |
174 | for exp > 0 { |
175 | res[r_i] = `0` |
176 | r_i++ |
177 | exp-- |
178 | if dot_p { |
179 | res[r_i] = `.` |
180 | r_i++ |
181 | dot_p = false |
182 | } |
183 | } |
184 | for b[i] != 0 { |
185 | res[r_i] = b[i] |
186 | r_i++ |
187 | i++ |
188 | } |
189 | } |
190 | |
191 | // Add a zero after the dot from the numbers like 2. |
192 | if r_i > 1 && res[r_i - 1] == `.` { |
193 | res[r_i] = `0` |
194 | r_i++ |
195 | } else if `.` !in res { |
196 | // If there is no dot, add it with a zero |
197 | res[r_i] = `.` |
198 | r_i++ |
199 | res[r_i] = `0` |
200 | r_i++ |
201 | } |
202 | |
203 | res[r_i] = 0 |
204 | return unsafe { tos(res.data, r_i) } |
205 | } |
206 | |
207 | // fxx_to_str_l_parse_with_dot returns a `string` in decimal notation converted from a |
208 | // floating-point `string` in scientific notation. |
209 | // If the decimal digits after the dot are zero, a '.0' is appended for clarity. |
210 | // |
211 | // Example: assert strconv.fxx_to_str_l_parse_with_dot ('34.e+01') == '340.0' |
212 | [direct_array_access; manualfree] |
213 | pub fn fxx_to_str_l_parse_with_dot(s string) string { |
214 | // check for +inf -inf Nan |
215 | if s.len > 2 && (s[0] == `n` || s[1] == `i`) { |
216 | return s.clone() |
217 | } |
218 | |
219 | m_sgn_flag := false |
220 | mut sgn := 1 |
221 | mut b := [26]u8{} |
222 | mut d_pos := 1 |
223 | mut i := 0 |
224 | mut i1 := 0 |
225 | mut exp := 0 |
226 | mut exp_sgn := 1 |
227 | |
228 | // get sign and decimal parts |
229 | for c in s { |
230 | if c == `-` { |
231 | sgn = -1 |
232 | i++ |
233 | } else if c == `+` { |
234 | sgn = 1 |
235 | i++ |
236 | } else if c >= `0` && c <= `9` { |
237 | b[i1] = c |
238 | i1++ |
239 | i++ |
240 | } else if c == `.` { |
241 | if sgn > 0 { |
242 | d_pos = i |
243 | } else { |
244 | d_pos = i - 1 |
245 | } |
246 | i++ |
247 | } else if c == `e` { |
248 | i++ |
249 | break |
250 | } else { |
251 | return 'Float conversion error!!' |
252 | } |
253 | } |
254 | b[i1] = 0 |
255 | |
256 | // get exponent |
257 | if s[i] == `-` { |
258 | exp_sgn = -1 |
259 | i++ |
260 | } else if s[i] == `+` { |
261 | exp_sgn = 1 |
262 | i++ |
263 | } |
264 | |
265 | mut c := i |
266 | for c < s.len { |
267 | exp = exp * 10 + int(s[c] - `0`) |
268 | c++ |
269 | } |
270 | |
271 | // allocate exp+32 chars for the return string |
272 | mut res := []u8{len: exp + 32, init: 0} |
273 | mut r_i := 0 // result string buffer index |
274 | |
275 | // println("s:${sgn} b:${b[0]} es:${exp_sgn} exp:${exp}") |
276 | |
277 | if sgn == 1 { |
278 | if m_sgn_flag { |
279 | res[r_i] = `+` |
280 | r_i++ |
281 | } |
282 | } else { |
283 | res[r_i] = `-` |
284 | r_i++ |
285 | } |
286 | |
287 | i = 0 |
288 | if exp_sgn >= 0 { |
289 | for b[i] != 0 { |
290 | res[r_i] = b[i] |
291 | r_i++ |
292 | i++ |
293 | if i >= d_pos && exp >= 0 { |
294 | if exp == 0 { |
295 | res[r_i] = `.` |
296 | r_i++ |
297 | } |
298 | exp-- |
299 | } |
300 | } |
301 | for exp >= 0 { |
302 | res[r_i] = `0` |
303 | r_i++ |
304 | exp-- |
305 | } |
306 | } else { |
307 | mut dot_p := true |
308 | for exp > 0 { |
309 | res[r_i] = `0` |
310 | r_i++ |
311 | exp-- |
312 | if dot_p { |
313 | res[r_i] = `.` |
314 | r_i++ |
315 | dot_p = false |
316 | } |
317 | } |
318 | for b[i] != 0 { |
319 | res[r_i] = b[i] |
320 | r_i++ |
321 | i++ |
322 | } |
323 | } |
324 | |
325 | // Add a zero after the dot from the numbers like 2. |
326 | if r_i > 1 && res[r_i - 1] == `.` { |
327 | res[r_i] = `0` |
328 | r_i++ |
329 | } else if `.` !in res { |
330 | // If there is no dot, add it with a zero |
331 | res[r_i] = `.` |
332 | r_i++ |
333 | res[r_i] = `0` |
334 | r_i++ |
335 | } |
336 | |
337 | res[r_i] = 0 |
338 | return unsafe { tos(res.data, r_i) } |
339 | } |