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