/*
 * Nickle test suite
 *
 * Trig operator tests
 */

load "math-tables.5c"

int test_precision = 333;
int errors = 0;
real error_bound = 1e-70;

real checked(real(real) f, real a) {
	try {
		real r = f(imprecise (a, test_precision));
		if (r > 1e10)
			return 9999;
		if (r < -1e10)
			return 9999;
		return r;
	} catch invalid_argument(string type, int i, real v) {
		return 10000;
	}
	return 0;
}

real checked2(real(real, real) f, real a, real b) {
	try {
		real r = f(imprecise(a, test_precision), imprecise(b, test_precision));

		/* Just return the same value for large magnitudes
		 * as small variations in input cause the sign to flip
		 */

		if (r > 1e10)
			return 9999;
		if (r < -1e10)
			return 9999;
		return r;
	} catch invalid_argument(string type, int i, real v) {
		return 10000;
	}
	return 0;
}

void check(string op, real(real) f, real input, real correct)
{
	real	value = checked(f, input);

	if (is_number (value))
	{
		real	error = abs (value - correct);

		if (abs(correct) >= 1 && error < abs(correct) * error_bound)
			return;
		if (abs(correct) < 1 && error < error_bound)
			return;
		printf ("error %v value %v\n", error, value);
	}
	if (value != correct) {
		printf ("check failed %s(%v) (was %.-g, should be %.-g)\n",
			op, input, value, correct);
		errors++;
	}
}

void check2(string op, real(real, real) f, real input1, real input2, real correct)
{
	real	value = checked2(f, input1, input2);

	if (is_number (value))
	{
		real	error = abs (value - correct);

		if (abs(correct) >= 1 && error < abs(correct) * error_bound)
			return;
		if (abs(correct) < 1 && error < error_bound)
			return;
		printf ("error %v value %v\n", error, value);
	}
	if (value != correct) {
		printf ("check failed %s(%.-g, %.-g) (was %.-g, should be %.-g)\n",
			op, input1, input2, value, correct);
		errors++;
	}
}

void check_sin_cos()
{
	printf("Checking sin and cos\n");
	for (int i = 0; i < dim(sin_cos_table); i++) {
		real	angle = sin_cos_table[i].angle;
		real	sin_value = sin_cos_table[i].sin;
		real	cos_value = sin_cos_table[i].cos;
		check("sin", sin, angle, sin_value);
		check("cos", cos, angle, cos_value);
	}
	printf("Checking asin and acos\n");
	for (int i = 0; i < dim(asin_acos_table); i++) {
		real	ratio = asin_acos_table[i].ratio;
		real	asin_value = asin_acos_table[i].asin;
		real	acos_value = asin_acos_table[i].acos;
		check("asin", asin, ratio, asin_value);
		check("acos", acos, ratio, acos_value);
	}
}

void check_tan()
{
	printf("Checking tan\n");
	for (int i = 0; i < dim(tan_table); i++) {
		real angle = tan_table[i].angle;
		real tan_value = tan_table[i].tan;
		check("tan", tan, angle, tan_value);
	}
	printf("Checking atan\n");
	for (int i = 0; i < dim(atan_table); i++) {
		real ratio = atan_table[i].ratio;
		real atan_value = atan_table[i].atan;
		check("atan", atan, ratio, atan_value);
	}
	printf("Checking atan2\n");
	for (int i = 0; i < dim(atan2_table); i++) {
		real y = atan2_table[i].y;
		real x = atan2_table[i].x;
		real atan2_value = atan2_table[i].atan2;
		check2("atan2", atan2, y, x, atan2_value);
	}
}

/* 1000 digits of e */
string e_digits = ("2." +
		   "71828182845904523536028747135266249775724709369995" +
		   "95749669676277240766303535475945713821785251664274" +
		   "27466391932003059921817413596629043572900334295260" +
		   "59563073813232862794349076323382988075319525101901" +
		   "15738341879307021540891499348841675092447614606680" +
		   "82264800168477411853742345442437107539077744992069" +
		   "55170276183860626133138458300075204493382656029760" +
		   "67371132007093287091274437470472306969772093101416" +
		   "92836819025515108657463772111252389784425056953696" +
		   "77078544996996794686445490598793163688923009879312" +
		   "77361782154249992295763514822082698951936680331825" +
		   "28869398496465105820939239829488793320362509443117" +
		   "30123819706841614039701983767932068328237646480429" +
		   "53118023287825098194558153017567173613320698112509" +
		   "96181881593041690351598888519345807273866738589422" +
		   "87922849989208680582574927961048419844436346324496" +
		   "84875602336248270419786232090021609902353043699418" +
		   "49146314093431738143640546253152096183690888707016" +
		   "76839642437814059271456354906130310720851038375051" +
		   "01157477041718986106873969655212671546889570350353");

void check_e()
{
	printf("Checking e\n");
	File::sscanf(e_digits, "%f", &(real e_good));
	for (int prec = 10; prec < 3000; prec *= 2) {
		real e_tmp = Math::e_value(prec);
		real error = abs (e_tmp - e_good);
		/* prec-2 bits right of the decimal point */
		real limit = 2**(-(prec-2));
		if (error > limit) {
			printf("e_value(%d) error %g limit %g value %g\n", prec, error, limit, e_tmp);
			errors++;
		}
	}
}

void check_exp()
{
	printf("Checking exp precision\n");
	for (int prec = 10; prec <= 1000; prec *= 10) {
		real eval = Math::e_value(1000);
		for (int x = 0; x < 100; x++) {
			real expx = exp(imprecise(x, prec));
			real powx = imprecise(eval ** x, prec);
			real	error = abs((imprecise(expx,prec*10) - imprecise(powx,prec*10)) / imprecise(expx,prec*10));
			real	limit = 2 ** (-(prec - 8));

			if (error > limit) {
				printf ("exp(i): prec %d error %g limit %g x %.-g val %.-g %.-g\n",
					prec, error, limit, x, expx, powx);
				errors++;
			}
		}
		for (int p = 0; p < prec // 3; p++) {
		    real x = imprecise(2 ** -p, prec);
		    if (exp(x) - 1 == 0) {
			printf("exp(x): prec %d is one for p %d x %.-g\n", prec, p, x);
			errors++;
		    }
		}
		for (real x = imprecise(0.001, prec); x < 20; x *= 1.5) {
			for (real y = imprecise(0.001, prec); y < 20; y *= 1.5) {

				real	expx = exp(x);
				real	expy = exp(y);
				real	expx_expy = exp(x) * exp(y);
				real	expxy = exp(x+y);

				int	bitdiff = ceil(abs(log2(abs(x)) - log2(abs(y))));

				real	error = abs((expxy - expx_expy) / expxy);
				real	limit = 2 ** (-(prec - 5 - bitdiff));

				if (error > limit) {
					printf("exp(x+y): prec %d error %g limit %g x %.-g y %.-g\n",
					       prec, error, limit, x, y);
					errors++;
				}
			}
		}
	}
	printf("Checking exp\n");
	for (int i = 0; i < dim(exp_table); i++) {
		real in = exp_table[i].in;
		real exp_value = exp_table[i].exp;
		check("exp", exp, in, exp_value);
	}
}

void check_log()
{
	printf("Checking log precision\n");
	int log_errors = 0;
	for (int prec = 10; prec <= 1000; prec *= 10) {
		real eval = Math::e_value(prec);
		for (real x = imprecise(0.001, prec); x < 20; x *= 1.5) {
			real	lnx = log(x);

			real	explnx = exp(lnx);

			real	rterror = abs((x - explnx) / x);
			real	rtlimit = 2 ** (-(prec - 6));

			if (rterror > rtlimit) {
				printf("exp(log(x)) = x: prec %d error %g limit %g x %.-g\n",
				       prec, rterror, rtlimit, x);
				errors++;
			}

			for (real y = imprecise(0.001, prec); y < 20; y *= 1.5) {
				real	lny = log(y);

				if (sign (lnx) != sign(lny))
				    continue;

				real	lnxy = log(x * y);
				real	lnx_lny = lnx + lny;
				real	error = abs ((lnxy - lnx_lny) / lnxy);

				int plnxy = precision(lnxy);
				int plnx_lny = precision(lnx_lny);

				if (plnxy != plnx_lny)
					printf("precision difference %d %d\n",
					       plnxy, plnx_lny);

				int	bitdiff = max(2, ceil(abs(log2(abs(lnx)) - log2(abs(lny)))));

				real	limit = 2 ** -(prec - bitdiff - 5);

				if (error > limit) {
					printf("log(x*y) = log(x)+log(y): prec %d error %g limit %g x %.-g y %.-g\n",
					       prec, error, limit, x, y);
					errors++;
				}
			}
		}
	}
	printf("Checking log\n");
	for (int i = 0; i < dim(log_table); i++) {
		real in = log_table[i].in;
		real log_value = log_table[i].log;
		check("log", log, in, log_value);
	}
}

check_sin_cos();
check_tan();
check_exp();
check_log();
check_e();

exit (errors);
