v / vlib / strconv / utilities.c.v
341 lines · 309 sloc · 6.53 KB · be650fb01d7344ef2d088c1a136f2d845fec8f29
Raw
1module strconv
2
3/*
4f32/f64 to string utilities
5
6Copyright (c) 2019-2024 Dario Deledda. All rights reserved.
7Use of this source code is governed by an MIT license
8that can be found in the LICENSE file.
9
10This file contains the f32/f64 to string utilities functions
11
12These functions are based on the work of:
13Publication:PLDI 2018: Proceedings of the 39th ACM SIGPLAN
14Conference on Programming Language Design and ImplementationJune 2018
15Pages 270–282 https://doi.org/10.1145/3192366.3192369
16
17inspired by the Go version here:
18https://github.com/cespare/ryu/tree/ba56a33f39e3bbbfa409095d0f9ae168a595feea
19*/
20
21/*
22f64 to string with string format
23*/
24
25// TODO: Investigate precision issues
26// f32_to_str_l returns `f` as a `string` in decimal notation with a maximum of 8 digits after the dot.
27// Example: assert strconv.f32_to_str_l(0.1234567891) == '0.12345679'
28// Example: assert strconv.f32_to_str_l(34.1234567891) == '34.123455'
29@[manualfree]
30pub fn f32_to_str_l(f f32) string {
31 s := f32_to_str(f, 8)
32 res := fxx_to_str_l_parse(s)
33 unsafe { s.free() }
34 return res
35}
36
37// f32_to_str_l_with_dot returns `f` as a `string` in decimal notation with a maximum of 8 digits after the dot.
38// If the decimal digits after the dot are zero, a '.0' is appended for clarity.
39//
40// Example: assert strconv.f32_to_str_l_with_dot(34.2) == '34.2'
41@[manualfree]
42pub fn f32_to_str_l_with_dot(f f32) string {
43 s := f32_to_str(f, 8)
44 res := fxx_to_str_l_parse_with_dot(s)
45 unsafe { s.free() }
46 return res
47}
48
49// f64_to_str_l returns `f` as a `string` in decimal notation with a maximum of 18 digits after the dot.
50//
51// Example: assert strconv.f64_to_str_l(123.1234567891011121) == '123.12345678910111'
52@[manualfree]
53pub fn f64_to_str_l(f f64) string {
54 s := f64_to_str(f, 18)
55 res := fxx_to_str_l_parse(s)
56 unsafe { s.free() }
57 return res
58}
59
60// f64_to_str_l_with_dot returns `f` as a `string` in decimal notation with a maximum of 18 digits after the dot.
61// If the decimal digits after the dot are zero, a '.0' is appended for clarity.
62//
63// Example: assert strconv.f64_to_str_l_with_dot(34.7) == '34.7'
64@[manualfree]
65pub fn f64_to_str_l_with_dot(f f64) string {
66 s := f64_to_str(f, 18)
67 res := fxx_to_str_l_parse_with_dot(s)
68 unsafe { s.free() }
69 return res
70}
71
72// fxx_to_str_l_parse returns a `string` in decimal notation converted from a
73// floating-point `string` in scientific notation.
74//
75// Example: assert strconv.fxx_to_str_l_parse('34.22e+00') == '34.22'
76@[direct_array_access; manualfree]
77pub fn fxx_to_str_l_parse(s string) string {
78 // check for +inf -inf Nan
79 if s.len > 2 && (s[0] == `n` || s[1] == `i`) {
80 return s.clone()
81 }
82
83 m_sgn_flag := false
84 mut sgn := 1
85 mut b := [26]u8{}
86 mut d_pos := 1
87 mut i := 0
88 mut i1 := 0
89 mut exp := 0
90 mut exp_sgn := 1
91
92 // get sign and decimal parts
93 for c in s {
94 if c == `-` {
95 sgn = -1
96 i++
97 } else if c == `+` {
98 sgn = 1
99 i++
100 } else if c >= `0` && c <= `9` {
101 b[i1] = c
102 i1++
103 i++
104 } else if c == `.` {
105 if sgn > 0 {
106 d_pos = i
107 } else {
108 d_pos = i - 1
109 }
110 i++
111 } else if c == `e` {
112 i++
113 break
114 } else {
115 return 'Float conversion error!!'
116 }
117 }
118 b[i1] = 0
119
120 // get exponent
121 if s[i] == `-` {
122 exp_sgn = -1
123 i++
124 } else if s[i] == `+` {
125 exp_sgn = 1
126 i++
127 }
128
129 mut c := i
130 for c < s.len {
131 exp = exp * 10 + int(s[c] - `0`)
132 c++
133 }
134
135 // allocate exp+32 chars for the return string
136 mut res := []u8{len: exp + 32, init: 0}
137 mut r_i := 0 // result string buffer index
138
139 // println("s:${sgn} b:${b[0]} es:${exp_sgn} exp:${exp}")
140
141 if sgn == 1 {
142 if m_sgn_flag {
143 res[r_i] = `+`
144 r_i++
145 }
146 } else {
147 res[r_i] = `-`
148 r_i++
149 }
150
151 i = 0
152 if exp_sgn >= 0 {
153 for b[i] != 0 {
154 res[r_i] = b[i]
155 r_i++
156 i++
157 if i >= d_pos && exp >= 0 {
158 if exp == 0 {
159 res[r_i] = `.`
160 r_i++
161 }
162 exp--
163 }
164 }
165 for exp >= 0 {
166 res[r_i] = `0`
167 r_i++
168 exp--
169 }
170 } else {
171 mut dot_p := true
172 for exp > 0 {
173 res[r_i] = `0`
174 r_i++
175 exp--
176 if dot_p {
177 res[r_i] = `.`
178 r_i++
179 dot_p = false
180 }
181 }
182 for b[i] != 0 {
183 res[r_i] = b[i]
184 r_i++
185 i++
186 }
187 }
188
189 // Add a zero after the dot from the numbers like 2.
190 if r_i > 1 && res[r_i - 1] == `.` {
191 res[r_i] = `0`
192 r_i++
193 } else if `.` !in res {
194 // If there is no dot, add it with a zero
195 res[r_i] = `.`
196 r_i++
197 res[r_i] = `0`
198 r_i++
199 }
200
201 res[r_i] = 0
202 tmp_res := unsafe { tos(res.data, r_i).clone() }
203 unsafe { res.free() }
204 return tmp_res
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]
213pub 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 tmp_res := unsafe { tos(res.data, r_i).clone() }
339 unsafe { res.free() }
340 return tmp_res
341}
342