hare

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

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