ftos.ha (12514B)
1 // SPDX-License-Identifier: MPL-2.0 2 // (c) Hare authors <https://harelang.org> 3 4 // Uses Ryū for shortest, falls back to multiprecision for fixed precision. 5 6 use io; 7 use math; 8 use memio; 9 use strings; 10 use types; 11 12 // Format styles for the [[ftosf]] functions. 13 export type ffmt = enum { 14 // General format. Uses whichever of E and F is shortest, not accounting 15 // for flags. 16 G, 17 // Scientific notation. Consists of a number in [1, 10), an 'e' (or 'E', 18 // if UPPER_EXP flag is present), then an exponent. 19 E, 20 // Fixed-point notation. 21 F, 22 }; 23 24 // Flags for the [[ftosf]] functions. 25 export type fflags = enum uint { 26 NONE = 0, 27 // Use a sign for both positive and negative numbers. 28 SHOW_POS = 1 << 0, 29 // Include at least one decimal digit. 30 SHOW_POINT = 1 << 1, 31 // Uppercase INFINITY and NAN. 32 UPPERCASE = 1 << 2, 33 // Uppercase exponent symbols E and P rather than e and p. 34 UPPER_EXP = 1 << 3, 35 // Use a sign for both positive and negative exponents. 36 SHOW_POS_EXP = 1 << 4, 37 // Show at least two digits of the exponent. 38 SHOW_TWO_EXP_DIGITS = 1 << 5, 39 }; 40 41 // Just for convenience... inline functions when? 42 fn ffpos(f: fflags) bool = f & fflags::SHOW_POS != 0; 43 fn ffpoint(f: fflags) bool = f & fflags::SHOW_POINT != 0; 44 fn ffcaps(f: fflags) bool = f & fflags::UPPERCASE != 0; 45 fn ffcaps_exp(f: fflags) bool = f & fflags::UPPER_EXP != 0; 46 fn ffpos_exp(f: fflags) bool = f & fflags::SHOW_POS_EXP != 0; 47 fn fftwodigs(f: fflags) bool = f & fflags::SHOW_TWO_EXP_DIGITS != 0; 48 49 fn declen(n: u64) uint = { 50 assert(n <= 1e17); 51 return if (n >= 1e17) 18 52 else if (n >= 1e16) 17 53 else if (n >= 1e15) 16 54 else if (n >= 1e14) 15 55 else if (n >= 1e13) 14 56 else if (n >= 1e12) 13 57 else if (n >= 1e11) 12 58 else if (n >= 1e10) 11 59 else if (n >= 1e9) 10 60 else if (n >= 1e8) 9 61 else if (n >= 1e7) 8 62 else if (n >= 1e6) 7 63 else if (n >= 1e5) 6 64 else if (n >= 1e4) 5 65 else if (n >= 1e3) 4 66 else if (n >= 100) 3 67 else if (n >= 10) 2 68 else 1; 69 }; 70 71 fn writestr(h: io::handle, s: str) (size | io::error) = { 72 return io::writeall(h, strings::toutf8(s))?; 73 }; 74 75 // XXX: this can likely be dedup'd with the other encode functions. 76 fn encode_zero( 77 h: io::handle, 78 f: ffmt, 79 prec: (void | uint), 80 flag: fflags, 81 ) (size | io::error) = { 82 let z = 0z; 83 z += memio::appendrune(h, '0')?; 84 let hasdec = false; 85 match (prec) { 86 case void => void; 87 case let u: uint => 88 if (u > 0 && f != ffmt::G) { 89 z += memio::appendrune(h, '.')?; 90 for (let i = 0u; i < u; i += 1) { 91 z += memio::appendrune(h, '0')?; 92 }; 93 hasdec = true; 94 }; 95 }; 96 if (!hasdec && ffpoint(flag)) { 97 z += memio::appendrune(h, '.')?; 98 z += memio::appendrune(h, '0')?; 99 }; 100 if (f == ffmt::E) { 101 z += memio::appendrune(h, if (ffcaps_exp(flag)) 'E' else 'e')?; 102 if (ffpos_exp(flag)) z += memio::appendrune(h, '+')?; 103 z += memio::appendrune(h, '0')?; 104 if (fftwodigs(flag)) z += memio::appendrune(h, '0')?; 105 }; 106 return z; 107 }; 108 109 fn encode_f_dec( 110 dec: *decimal, 111 h: io::handle, 112 f: ffmt, 113 prec: (void | uint), 114 flag: fflags, 115 ) (size | io::error) = { 116 // we will loop from lo <= i < hi, printing either zeros or a digit. 117 // lo is simple, but hi depends intricately on f, prec, and the 118 // SHOW_POINT flag. 119 const lo = if (dec.dp <= 0) dec.dp - 1 else 0i32; 120 let hi = match (prec) { 121 case void => 122 yield if (dec.nd: i32 > dec.dp) dec.nd: i32 else dec.dp; 123 case let u: uint => 124 yield if (dec.dp <= 0) lo + u: i32 + 1 else dec.dp + u: i32; 125 }; 126 // ffmt::G: we need to remove trailing zeros 127 if (f == ffmt::G) { 128 // first, make sure we include at least prec digits 129 if (prec is uint) { 130 const p = prec as uint; 131 if (dec.dp <= 0 && hi < p: i32) { 132 hi = p: int; 133 }; 134 }; 135 // then, cut back to the decimal point or nd 136 if (hi > dec.nd: i32 && dec.dp <= 0) { 137 hi = dec.nd: int; 138 } else if (hi > dec.dp && dec.dp > 0) { 139 hi = if (dec.nd: i32 > dec.dp) dec.nd: int else dec.dp; 140 }; 141 }; 142 // SHOW_POINT: we need to go at least one past the decimal 143 if (ffpoint(flag) && hi <= dec.dp) { 144 hi = dec.dp + 1; 145 }; 146 let z = 0z; 147 for (let i = lo; i < hi; i += 1) { 148 if (i == dec.dp) { 149 z += memio::appendrune(h, '.')?; 150 }; 151 if (0 <= i && i < dec.nd: i32) { 152 z += memio::appendrune(h, (dec.digits[i] + '0'): rune)?; 153 } else { 154 z += memio::appendrune(h, '0')?; 155 }; 156 }; 157 return z; 158 }; 159 160 fn encode_e_dec( 161 dec: *decimal, 162 h: io::handle, 163 f: ffmt, 164 prec: (void | uint), 165 flag: fflags, 166 ) (size | io::error) = { 167 let z = 0z; 168 assert(dec.nd > 0); 169 z += memio::appendrune(h, (dec.digits[0] + '0'): rune)?; 170 const zeros: uint = match (prec) { 171 case void => 172 yield 0; 173 case let u: uint => 174 yield switch (f) { 175 case ffmt::G => 176 yield if (dec.nd + 1 < u) u - dec.nd: uint + 1 else 0; 177 case ffmt::E => 178 yield if (dec.nd < u + 1) u - dec.nd: uint + 1 else 0; 179 case => abort(); 180 }; 181 }; 182 if (dec.nd <= 1 && ffpoint(flag) && zeros < 1) { 183 zeros = 1; 184 }; 185 if (dec.nd > 1 || zeros > 0) { 186 z += memio::appendrune(h, '.')?; 187 }; 188 for (let i = 1z; i < dec.nd; i += 1) { 189 z += memio::appendrune(h, (dec.digits[i] + '0'): rune)?; 190 }; 191 for (let i = 0u; i < zeros; i += 1) { 192 z += memio::appendrune(h, '0')?; 193 }; 194 z += memio::appendrune(h, if (ffcaps_exp(flag)) 'E' else 'e')?; 195 let e = dec.dp - 1; 196 if (e < 0) { 197 e = -e; 198 z += memio::appendrune(h, '-')?; 199 } else if (ffpos_exp(flag)) { 200 z += memio::appendrune(h, '+')?; 201 }; 202 let ebuf: [3]u8 = [0...]; // max and min exponents are 3 digits 203 let l = declen(e: u64); 204 for (let i = 0z; i < l; i += 1) { 205 ebuf[2 - i] = (e % 10): u8; 206 e /= 10; 207 }; 208 if (fftwodigs(flag) && l == 1) { 209 l = 2; 210 }; 211 for (let i = 3 - l; i < 3; i += 1) { 212 z += memio::appendrune(h, (ebuf[i] + '0'): rune)?; 213 }; 214 return z; 215 }; 216 217 fn init_dec_mant_exp(d: *decimal, mantissa: u64, exponent: i32) void = { 218 const dl = declen(mantissa); 219 for (let i = 0u; i < dl; i += 1) { 220 d.digits[dl - i - 1] = (mantissa % 10): u8; 221 mantissa /= 10; 222 }; 223 d.nd = dl; 224 d.dp = dl: i32 + exponent; 225 }; 226 227 fn init_dec( 228 dec: *decimal, 229 mantissa: u64, 230 exponent: u32, 231 eb: u64, 232 mb: u64, 233 ) void = { 234 let e2 = (eb + mb): i32; 235 let m2: u64 = 0; 236 if (exponent == 0) { 237 e2 = 1 - e2; 238 m2 = mantissa; 239 } else { 240 e2 = (exponent: i32) - e2; 241 m2 = (1u64 << mb) | mantissa; 242 }; 243 244 dec.nd = declen(m2); 245 dec.dp = dec.nd: int; 246 for (let i = 0z; i < dec.nd; i += 1) { 247 dec.digits[dec.nd - i - 1] = (m2 % 10): u8; 248 m2 /= 10; 249 }; 250 decimal_shift(dec, e2); 251 }; 252 253 // Compute the number of figs to round to for the given arguments. 254 fn compute_round( 255 dec: *decimal, 256 f: ffmt, 257 prec: (void | uint), 258 flag: fflags, 259 ) uint = { 260 // nd is the number of sig figs that we want to end up with 261 let nd: int = match (prec) { 262 case void => 263 // we should only get here if Ryu did not extend past the 264 // decimal point 265 assert(ffpoint(flag)); 266 yield dec.nd: int + (if (dec.dp > 0) dec.dp: int else 0); 267 case let u: uint => 268 yield switch (f) { 269 case ffmt::E => 270 yield u: int + 1; 271 case ffmt::F => 272 yield u: int + dec.dp: int; 273 case ffmt::G => 274 yield if (u == 0) 1 else u: int; 275 }; 276 }; 277 const nde = if (nd < 2) 2 else nd; 278 const ndf = if (dec.dp >= 0 && nd: int < dec.dp: int + 1) dec.dp + 1 279 else nd; 280 if (ffpoint(flag)) { 281 nd = switch (f) { 282 case ffmt::E => 283 // need at least two digits, d.de0. 284 yield nde; 285 case ffmt::F => 286 // need enough to clear the decimal point by one. 287 yield ndf: int; 288 case ffmt::G => 289 // XXX: dup'd with the condition in ftosf_handle 290 if (dec.dp < -1 || dec.dp: int - dec.nd: int > 2) 291 yield nde: int; 292 yield ndf: int; 293 }; 294 }; 295 if (nd <= 0) { 296 nd = 0; 297 }; 298 return if (nd: uint > dec.nd) dec.nd: uint else nd: uint; 299 }; 300 301 // Converts a [[types::floating]] to a string in base 10 and writes the result 302 // to the provided handle. Format parameters are as in [[ftosf]]. 303 export fn fftosf( 304 h: io::handle, 305 n: types::floating, 306 f: ffmt = ffmt::G, 307 prec: (void | uint) = void, 308 flag: fflags = fflags::NONE, 309 ) (size | io::error) = { 310 const (mantissa, exponent, sign, special) = match (n) { 311 case let n: f64 => 312 const bits = math::f64bits(n); 313 const mantissa = bits & math::F64_MANTISSA_MASK; 314 const exponent = ((bits >> math::F64_MANTISSA_BITS) & 315 math::F64_EXPONENT_MASK): u32; 316 const sign = bits >> (math::F64_EXPONENT_BITS + 317 math::F64_MANTISSA_BITS) > 0; 318 const special = exponent == math::F64_EXPONENT_MASK; 319 yield (mantissa, exponent, sign, special); 320 case let n: f32 => 321 const bits = math::f32bits(n); 322 const mantissa: u64 = bits & math::F32_MANTISSA_MASK; 323 const exponent = ((bits >> math::F32_MANTISSA_BITS) & 324 math::F32_EXPONENT_MASK): u32; 325 const sign = bits >> (math::F32_EXPONENT_BITS + 326 math::F32_MANTISSA_BITS) > 0; 327 const special = exponent == math::F32_EXPONENT_MASK; 328 yield (mantissa, exponent, sign, special); 329 }; 330 331 if (special && mantissa != 0) { 332 return writestr(h, if (ffcaps(flag)) "NAN" else "nan"); 333 }; 334 335 let z = 0z; 336 if (sign) { 337 z += memio::appendrune(h, '-')?; 338 } else if (ffpos(flag)) { 339 z += memio::appendrune(h, '+')?; 340 }; 341 342 if (special) { 343 return z + writestr(h, 344 if (ffcaps(flag)) "INFINITY" else "infinity")?; 345 } else if (exponent == 0 && mantissa == 0) { 346 return z + encode_zero(h, f, prec, flag)?; 347 }; 348 349 let dec = decimal { ... }; 350 let ok = false; 351 if (prec is void) { 352 // Shortest via Ryū. It is not correct to use f64todecf64 for 353 // f32s, they must be handled separately. 354 const (mdec, edec) = match (n) { 355 case f64 => 356 const d = f64todecf64(mantissa, exponent); 357 yield (d.mantissa, d.exponent); 358 case f32 => 359 const d = f32todecf32(mantissa: u32, exponent); 360 yield (d.mantissa: u64, d.exponent); 361 }; 362 init_dec_mant_exp(&dec, mdec, edec); 363 // If SHOW_POINT and we have too few digits, then we need to 364 // fall back to multiprecision. 365 ok = !ffpoint(flag) || dec.dp < dec.nd: i32 366 || (f != ffmt::F && dec.dp - dec.nd: i32 > 2); 367 }; 368 369 if (!ok) { 370 // Fall back to multiprecision. 371 match (n) { 372 case f64 => 373 init_dec(&dec, mantissa, exponent, 374 math::F64_EXPONENT_BIAS, 375 math::F64_MANTISSA_BITS); 376 case f32 => 377 init_dec(&dec, mantissa, exponent, 378 math::F32_EXPONENT_BIAS, 379 math::F32_MANTISSA_BITS); 380 }; 381 trim(&dec); 382 const nd = compute_round(&dec, f, prec, flag); 383 round(&dec, nd); 384 }; 385 386 if (f == ffmt::G) { 387 trim(&dec); 388 }; 389 390 if (f == ffmt::G && prec is uint) { 391 if (prec as uint == 0) prec = 1; 392 }; 393 394 if (dec.nd == 0) { 395 // rounded to zero 396 return z + encode_zero(h, f, prec, flag)?; 397 } else if (f == ffmt::E || (f == ffmt::G && 398 (dec.dp < -1 || dec.dp - dec.nd: i32 > 2))) { 399 return z + encode_e_dec(&dec, h, f, prec, flag)?; 400 } else { 401 return z + encode_f_dec(&dec, h, f, prec, flag)?; 402 }; 403 }; 404 405 // Converts any [[types::floating]] to a string in base 10. The return value 406 // must be freed. 407 // 408 // A precision of void yields the smallest number of digits that can be parsed 409 // into the exact same number. Otherwise, the meaning depends on f: 410 // - ffmt::F, ffmt::E: Number of digits after the decimal point. 411 // - ffmt::G: Number of significant digits. 0 is equivalent to 1 precision, and 412 // trailing zeros are removed. 413 export fn ftosf( 414 n: types::floating, 415 f: ffmt = ffmt::G, 416 prec: (void | uint) = void, 417 flag: fflags = fflags::NONE, 418 ) str = { 419 let m = memio::dynamic(); 420 fftosf(&m, n, f, prec, flag)!; 421 return memio::string(&m)!; 422 }; 423 424 // Converts a f64 to a string in base 10. The return value is statically 425 // allocated and will be overwritten on subsequent calls; see [[strings::dup]] 426 // to duplicate the result. The result is equivalent to [[ftosf]] with format G 427 // and precision void. 428 export fn f64tos(n: f64) const str = { 429 // The biggest string produced by a f64 number in base 10 would have the 430 // negative sign, followed by a digit and decimal point, and then 431 // sixteen more decimal digits, followed by 'e' and another negative 432 // sign and the maximum of three digits for exponent. 433 // (1 + 1 + 1 + 16 + 1 + 1 + 3) = 24 434 static let buf: [24]u8 = [0...]; 435 let m = memio::fixed(buf); 436 fftosf(&m, n)!; 437 return memio::string(&m)!; 438 }; 439 440 // Converts a f32 to a string in base 10. The return value is statically 441 // allocated and will be overwritten on subsequent calls; see [[strings::dup]] 442 // to duplicate the result. The result is equivalent to [[ftosf]] with format G 443 // and precision void. 444 export fn f32tos(n: f32) const str = { 445 // The biggest string produced by a f32 number in base 10 would have the 446 // negative sign, followed by a digit and decimal point, and then seven 447 // more decimal digits, followed by 'e' and another negative sign and 448 // the maximum of two digits for exponent. 449 // (1 + 1 + 1 + 7 + 1 + 1 + 2) = 14 450 static let buf: [14]u8 = [0...]; 451 let m = memio::fixed(buf); 452 fftosf(&m, n)!; 453 return memio::string(&m)!; 454 };