shithub: mc

ref: f762f96f07cf2dfc7b3bb745d6a767dccda0a01a
dir: /lib/std/fltfmt.myr/

View raw version
use "alloc"
use "bigint"
use "die"
use "extremum"
use "fltbits"
use "slpush"
use "strbuf"
use "types"
use "utf"
use "memops"

pkg std =
	pkglocal const MNormal	= 0
	pkglocal const MAbsolute = 1
	pkglocal const MRelative = 2

	pkglocal const flt64bfmt	: (sb : strbuf#, val : flt64, mode : int, precision : int -> void)
	pkglocal const flt32bfmt	: (sb : strbuf#, val : flt32, mode : int, precision : int -> void)
;;

const Dblbias = 1023
const Fltbias = 127

const flt64bfmt = {sb, val, mode, precision
	var isneg, exp, mant

	(isneg, exp, mant) = flt64explode(val)
	exp = max(exp, 1 - Dblbias)
	dragon4(sb, isneg, mant, exp - 52, Dblbias, mode, precision)
}

const flt32bfmt = {sb, val, mode, precision
	var isneg, exp, mant

	(isneg, exp, mant) = flt32explode(val)
	exp = (max((exp : int64), 1 - Fltbias) : int32)
	dragon4(sb, isneg, (mant : uint64), (exp - 23 : int64), Fltbias, mode, precision)
}

/*
sb: output buffer
e: exponent
p: precision
f: mantissa

floating value: x = f^(e - p)
*/
const dragon4 = {sb, isneg, f, e, p, mode, cutoff
	var r, s, t, u, v, y
	var udig
	var mm, mp	/* margins above and below */
	var roundup
	var low, high
	var k
	var a, i

	if isneg
		sbputs(sb, "-")
	;;
	/* if we have zero for the mantissa, we can return early */
	if f == 0
		sbputs(sb, "0.0")
		-> void
	;;

	/* initialize */
	roundup = false
	low = false
	high = false
	u = mkbigint(0)
	r = bigshli(mkbigint(f), max(e, 0))
	s = bigshli(mkbigint(1), max(0, -e))
	mm = bigshli(mkbigint(1), max(e, 0))
	mp = bigdup(mm)

	/* fixup: unequal gaps */
	t = mkbigint(1)
	bigshli(t, p - 1)
	if bigeqi(t, f)
		bigshli(mp, 1)
		bigshli(r, 1)
		bigshli(s, 1)
	;;
	bigfree(t)

	k = 0
	while true
		/* r < ceil(s/b) */
		t = bigdup(s)
		bigaddi(t, 9)
		bigdivi(t, 10)
		match bigcmp(r, t)
		| `Before:
			k--
			bigmuli(r, 10)
			bigmuli(mm, 10)
			bigmuli(mp, 10)
		| _:
			bigfree(t)
			break
		;;
		bigfree(t)
	;;

	while true
		t = bigdup(r)
		bigshli(t, 1)
		bigadd(t, mp)
		while true
			u = bigdup(s)
			bigshli(u, 1)
			match bigcmp(t, u)
			| `Before:
				bigfree(u)
				break
			| _:
				k++
				bigmuli(s, 10)
				bigfree(u)
			;;
		;;
		if mode == MNormal
			cutoff = k
		else
			if mode == MRelative
				cutoff += k - 1
			;;
			/* common between relative and absolute */
			a = cutoff - k - 1
			y = bigdup(s)
			if a < 0
				for i = 0; i < a; i++
					bigmuli(y, 10)
				;;
			else
				for i = 0; i < -a; i++
					bigaddi(y, 9)
					bigdivi(y, 10)
				;;
			;;
			match bigcmp(y, mm)
			| `Before:	/* nothing */
			| _:
				bigfree(mm)
				mm = y
			;;
			match bigcmp(y, mp)
			| `Before:	/* nothing */
			| _:
				bigfree(mp)
				mp = y
				roundup = true
			;;
		;;
		u = bigdup(s)
		bigshli(u, 1)
		match bigcmp(t, u)
		| `Before:
			bigfree(t)
			bigfree(u)
			break
		| _:
		;;
	;;

	if k <= 0
		sbputs(sb, "0.")
		for var i = 0; i < -k; i++
			sbputs(sb, "0")
		;;
	;;
	while true
		k--

		bigmuli(r, 10)
		u = bigdup(r);
		bigdiv(u, s)

		bigmod(r, s)
		bigmuli(mm, 10)
		bigmuli(mp, 10)

		low = false
		t = bigdup(r)
		bigshli(t, 1)
		match bigcmp(t, mm)
		| `Before:	low = true
		| _:
		;;
		bigfree(t)

		/* v = 2*r */
		v = bigdup(r)
		bigshli(v, 1)

		/* t = 2*s - mp */
		t = bigdup(s)
		bigshli(t, 1)
		bigsub(t, mp)

		match bigcmp(v, t)
		| `Before: high = false;
		| `Equal: high = roundup;
		| `After: high = true;
		;;
		bigfree(v)
		bigfree(t)
		if low || high || k == cutoff
			break
		;;
		format(sb, lowdig(u), k)
		bigfree(u)
	;;

	/* format the last digit */
	udig = lowdig(u)
	if low && !high
		format(sb, udig, k)
	elif high && !low
		format(sb, udig + 1, k)
	else
		bigmuli(r, 2)
		match bigcmp(r, s)
		| `Before:	format(sb, udig, k)
		| `Equal:	format(sb, udig, k)
		| `After:	format(sb, udig + 1, k)
		;;
	;;
	k--
	while k >= -1
		format(sb, 0, k--)
	;;

	bigfree(u)
	bigfree(r)
	bigfree(s)
	bigfree(mm)
	bigfree(mp)

}

const lowdig = {u
	if u.dig.len > 0
		-> u.dig[0]
	;;
	-> 0
}

const format = {sb, d, k
	const dig = "0123456789"

	sbputb(sb, dig[d])
	if k == 0
		sbputs(sb, ".")
	;;
}