hare

[hare] The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

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 };