| 1 | module strconv |
| 2 | |
| 3 | /* |
| 4 | f32/f64 to string utilities |
| 5 | |
| 6 | Copyright (c) 2019-2024 Dario Deledda. All rights reserved. |
| 7 | Use of this source code is governed by an MIT license |
| 8 | that can be found in the LICENSE file. |
| 9 | |
| 10 | This file contains the f32/f64 to string utilities functions |
| 11 | |
| 12 | These functions are based on the work of: |
| 13 | Publication:PLDI 2018: Proceedings of the 39th ACM SIGPLAN |
| 14 | Conference on Programming Language Design and ImplementationJune 2018 |
| 15 | Pages 270–282 https://doi.org/10.1145/3192366.3192369 |
| 16 | |
| 17 | inspired by the Go version here: |
| 18 | https://github.com/cespare/ryu/tree/ba56a33f39e3bbbfa409095d0f9ae168a595feea |
| 19 | */ |
| 20 | |
| 21 | /* |
| 22 | f64 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] |
| 30 | pub 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] |
| 42 | pub 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] |
| 53 | pub 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] |
| 65 | pub 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] |
| 77 | pub 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] |
| 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 | tmp_res := unsafe { tos(res.data, r_i).clone() } |
| 339 | unsafe { res.free() } |
| 340 | return tmp_res |
| 341 | } |
| 342 | |