() = evalfile ("inc.sl");

testing_feature ("Math");

#ifnexists log1p
print ("Not supported\n");
exit(0);
#stop
#endif

private define log1p_taylor (x)
{
   return polynom ([0,1,-1/2.0,1.0/3,-1.0/4,1.0/5,-1.0/6,1.0/7], x);
}

private define expm1_taylor (x)
{
   % exp(x)-1 = x+x^2/2!+...
   return polynom ([0,1,1,1,1,1,1,1,1,1,1], x, 1);
}

private define test_smallx_funct_internal (func, tfunc, x)
{
   variable y = (@tfunc)(x);
   variable y1 = (@func)(x);

   variable dy = (y1-y)/x;
   variable i = wherefirst(abs(dy)>1e-15);
   if (i != NULL)
     {
	failed ("%S(%S) ==> %S, expected %S", func, x[i], y1[i], y[i]);
     }
}

private define test_smallx_funct (f, t, x)
{
   test_smallx_funct_internal (f, t, x);
   test_smallx_funct_internal (f, t, typecast(x, Float_Type));
   test_smallx_funct_internal (f, t, [x,x,x,x]);
   test_smallx_funct_internal (f, t, typecast([x,x,x,x], Float_Type));
}

test_smallx_funct (&log1p, &log1p_taylor, 1e-9);
test_smallx_funct (&log1p, &log1p_taylor, 1e-3);
test_smallx_funct (&log1p, &log1p_taylor, 1e-15);
test_smallx_funct (&log1p, &log1p_taylor, 1e-20);

test_smallx_funct (&expm1, &expm1_taylor, 1e-3);
test_smallx_funct (&expm1, &expm1_taylor, 1e-9);
test_smallx_funct (&expm1, &expm1_taylor, 1e-15);
test_smallx_funct (&expm1, &expm1_taylor, 1e-20);

ifnot(isinf(expm1(_Inf))) failed ("expm1 _Inf");
if (expm1(-_Inf) != -1) failed ("expm1 -_Inf");
ifnot(isinf(log1p(_Inf))) failed ("log1p _Inf");
ifnot(isnan(log1p(-_Inf))) failed ("log1p -_Inf");

define check_hypot (a, b, c)
{
   variable cc;
   cc = hypot (a, b);
   if (_typeof (c) != _typeof (cc))
     failed ("Wrong return type for hypot");
   if (0 == _eqs(c, cc))
     failed ("hypot: expected %S, got %S", c, cc);

   if (length (a) != length(b))
     return;

   cc = hypot ([a,a,a,a],[b,b,b,b]);
   if (0 == _eqs([c,c,c,c], cc))
     failed ("hypot ([a,a,a,a],[b,b,b,b])");

   c = c[0];
   cc = hypot ([0,a,b]); if (0 == _eqs(cc, c)) failed ("hypot ([0,a,b])");
   cc = hypot ([a,0,b]); if (0 == _eqs(cc, c)) failed ("hypot ([a,0,b])");
   cc = hypot ([a,b,0]); if (0 == _eqs(cc, c)) failed ("hypot ([a,0,b])");
}

private define check_hypot_with_types ()
{
   variable a, b, c, s, t, u;
   foreach t (Util_Arith_Types)
     {
	a = typecast (3, t);
	foreach s (Util_Arith_Types)
	  {
	     b = typecast (4, s);
	     c = 5.0;
	     if ((t == Float_Type) && (s == Float_Type))
	       c = 5.0f;
	     u = typeof (c);

	     check_hypot (a, b, c);
	     check_hypot (a, [b], [c]);
	     check_hypot ([a], b, [c]);
	     check_hypot ([a], [b], [c]);
	     check_hypot (t[0], b, u[0]);
	     check_hypot (a, s[0], u[0]);
	  }
     }
}
check_hypot_with_types ();

private define check_nint (x, n)
{
   if (nint (x) != n)
     failed ("nint(%g)!=%d, found %d", x, n, nint(x));
   ifnot (_eqs([n,n,n], nint([x,x,x])))
     failed ("nint(%S)", [x,x,x]);
}
check_nint (0.0, 0);
check_nint (0.4, 0);
check_nint (0.49, 0);
check_nint (0.50, 1);
check_nint (1.2, 1);
check_nint (1.49, 1);
check_nint (1.5, 2);
check_nint (-0.1, 0);
check_nint (-0.4, 0);
check_nint (-0.5, -1);
check_nint (-0.9, -1);
check_nint (-1.4, -1);
check_nint (-1.5, -2);
check_nint (-1.51, -2);

private define check_round (x, rx)
{
   if (round (x) != rx)
     failed ("round(%g)!=%g, found %g", x, rx, round(x));
   ifnot (_eqs([rx,rx,rx], round([x,x,x])))
     failed ("round(%S)", [x,x,x]);
}
check_round (0.0, 0);
check_round (0.4, 0);
check_round (-0.4, 0);
check_round (0.51, 1);
check_round (-0.51, -1);
check_round (0.9, 1);
check_round (-0.9, -1);
check_round (1.1, 1);
check_round (-1.1, -1);
check_round (-1.51, -2);
check_round (1.51, 2);

private define check_floor (x, rx)
{
   if (floor (x) != rx)
     failed ("floor(%g)!=%g, found %g", x, rx, round(x));
   ifnot (_eqs([rx,rx,rx], floor([x,x,x])))
     failed ("floor(%S)", [x,x,x]);
}
check_floor (1.2, 1);
check_floor (1.2f, 1);
check_floor (-1.2, -2);
check_floor (-1.2f, -2);

private define check_ceil (x, rx)
{
   if (ceil (x) != rx)
     failed ("ceil(%g)!=%g, found %g", x, rx, round(x));
   ifnot (_eqs([rx,rx,rx], ceil([x,x,x])))
     failed ("ceil(%S)", [x,x,x]);
}
check_ceil (1.2, 2);
check_ceil (1.2f, 2);
check_ceil (-1.2, -1);
check_ceil (-1.2f, -1);

private define check_with_types ()
{
   foreach (Util_Arith_Types)
     {
	variable t = ();
	variable x = typecast(31,t);
	check_nint (x, 31);
	check_round (x, 31);
	check_floor (x, 31);
	check_ceil (x, 31);
     }
}
check_with_types ();

private define sl_feqs (a, b, relerr, abserr)
{
   if (abs(a-b) <= abserr)
     return 1;
   if (abs(a) > abs(b)) (b,a)=(a,b);

   return (abs((b-a)/b) <= relerr);
}

define test_feqs (a, b, relerr, abserr)
{
   variable c = feqs (a, b, relerr, abserr);
   variable d = array_map (Char_Type, &sl_feqs, a, b, relerr, abserr);
   if (typeof (c) != Array_Type)
     d = d[0];
   if (not _eqs(c,d))
     failed ("feqs(4 args) did not return expected result");

   c = feqs (a, b, relerr);
   d = array_map (Char_Type, &sl_feqs, a, b, relerr, 0.0);
   if (typeof (c) != Array_Type)
     d = d[0];
   if (not _eqs(c, d))
     failed ("feqs(3 args) did not return expected result");

   a = typecast (a, Double_Type);
   b = typecast (b, Float_Type);
   c = feqs (a, b, relerr);
   d = array_map (Char_Type, &sl_feqs, a, b, relerr, 0.0);
   if (typeof (c) != Array_Type)
     d = d[0];
   if (not _eqs(c, d))
     failed ("feqs(double,float) did not return expected result");

   a = typecast (a, Float_Type);
   b = typecast (b, Double_Type);
   c = feqs (a, b, relerr);
   d = array_map (Char_Type, &sl_feqs, a, b, relerr, 0.0);
   if (typeof (c) != Array_Type)
     d = d[0];
   if (not _eqs(c, d))
     failed ("feqs(float,double) did not return expected result");

   a = typecast (a, Float_Type);
   b = typecast (b, Float_Type);
   c = feqs (a, b, relerr);
   d = array_map (Char_Type, &sl_feqs, a, b, relerr, 0.0);
   if (typeof (c) != Array_Type)
     d = d[0];
   if (not _eqs(c, d))
     failed ("feqs(float,float) did not return expected result");
}

private define test_feqs1 (a, b, c, d)
{
   test_feqs (a, b, c, d);

   if ((typeof (a) == Array_Type)
       && (typeof (b) == Array_Type))
     {
	variable i, n = length (a);
	_for i (0, n-1, 1)
	  {
	     test_feqs (a, b[i], c, d);
	     test_feqs (a[i], b, c, d);
	  }
     }
}

foreach (10.0^[-12:20])
{
   $1 = ();
   $2 = $1 * 1.01;
   test_feqs1 ($1, $2, 0.001, 1e-6);

   $2 = -$1 * 1.01;
   test_feqs1 ($1, $2, 0.001, 1e-6);

   $1 = -$1;
   $2 = $1 * 1.01;
   test_feqs1 ($1, $2, 0.001, 1e-6);

   $2 = -$1 * 1.01;
   test_feqs1 ($1, $2, 0.001, 1e-6);
}

$1 = 10.0^[-12:20];
$2 = $1 * 1.01;
test_feqs1 ($1, $2, 0.001, 1e-6);

$2 = -$1 * 1.01;
test_feqs1 ($1, $2, 0.001, 1e-6);

$1 = -$1;
$2 = $1 * 1.01;
test_feqs1 ($1, $2, 0.001, 1e-6);

$2 = -$1 * 1.01;
test_feqs1 ($1, $2, 0.001, 1e-6);

if (feqs (_NaN,_NaN,0.1, 1.0))
  failed ("feqs (_NaN,_NaN)");

if (not fneqs (_NaN,_NaN,0.1, 1.0))
  failed ("fneqs (_NaN,_NaN)");

if (fgteqs (_NaN,_NaN,0.1, 1.0))
  failed ("fgteqs (_NaN,_NaN)");

if (flteqs (_NaN,_NaN,0.1, 1.0))
  failed ("flteqs (_NaN,_NaN)");

if (fgteqs (2.0, 3.0, 0.001, 0.1))
  failed ("fgteqs(2,3)");

if (flteqs (2.0, 1.0, 0.001, 0.1))
  failed ("fgteqs(2,1)");

private define test_misc_trig ()
{
   variable angle = [0.1:2*PI:0.1]-PI;
   variable s, c, s1, c1;
   variable type;

   foreach type ([Double_Type, Float_Type])
     {
	variable theta = typecast (angle, type);

	(s, c) = (sin(theta), cos(theta));
	(s1, c1) = sincos (theta);
	if (any (fneqs (c, c1, 1e-6, 1e-8)) || any(fneqs(s,s1,1e-6,1e-8)))
	  failed ("sincos %S failed", type);

	if (any (fneqs (theta, atan2(s1,c1), 1e-5, 1e-6)))
	  failed ("atan2 %S", type);
	(s, c) = sincos(theta[0]);
	if ((s != s1[0]) || (c != c1[0]))
	  failed ("sincos scalar %S", type);
     }

   foreach type (Util_Arith_Types)
     {
	variable one = typecast (1, type);
	variable ten = typecast (10, type);

	if (fneqs (tan(one), 1.5574077246549023, 1e-6)) failed("tan(%S)",one);
	if (fneqs (2*asin(one), PI, 1e-6)) failed ("asin(%S)", one);
	if (fneqs (2*acos(one), 0, 1e-6)) failed ("acos(%S)", one);
	if (fneqs (4*atan(one), PI, 1e-6)) failed ("atan(%S)", one);
	if (fneqs (expm1(one), 1.718281828459045, 1e-6)) failed("expm1(%S)",one);
	if (fneqs (log1p(one), 0.6931471805599453, 1e-6)) failed("log1p(%S)",one);
	if (fneqs (sinh(one), 1.1752011936438014, 1e-6)) failed("sinh(%S)",one);
	if (fneqs (cosh(one), 1.5430806348152437, 1e-6)) failed("cosh(%S)",one);
	if (fneqs (tanh(one), 0.7615941559557649, 1e-6)) failed("tanh(%S)",one);
	if (fneqs (asinh(one), 0.881373587019543, 1e-6)) failed("asinh(%S)",one);
	if (fneqs (acosh(one), 0.0, 1e-6)) failed("acosh(%S)",one);
	ifnot (isinf(atanh(one))) failed("atanh(%S)",one);
	if (fneqs (log(ten), 2.302585092994046, 1e-6)) failed("log(%S)",ten);
	if (fneqs (exp(one), E, 1e-6)) failed("exp(%S)",one);
	if (log10(ten) != 1) failed("log10(%S)", ten);
#ifexists Complex_Type
	if (Imag(one) != 0) failed ("Imag(%S)", one);
	if (Real(one) != 1) failed ("Real(%S)", one);
	if (Conj(one) != 1) failed ("Conj(%S)", one);
#endif
	one = [one, one, one];
	ten = [ten,ten,ten];
	if (any(fneqs (tan(one), 1.5574077246549023, 1e-6))) failed("tan(%S)",one);
	if (any(fneqs (2*asin(one), PI, 1e-6))) failed ("asin(%S)", one);
	if (any(fneqs (2*acos(one), 0, 1e-6))) failed ("acos(%S)", one);
	if (any(fneqs (4*atan(one), PI, 1e-6))) failed ("atan(%S)", one);
	if (any(fneqs (expm1(one), 1.718281828459045, 1e-6))) failed("expm1(%S)",one);
	if (any(fneqs (log1p(one), 0.6931471805599453, 1e-6))) failed("log1p(%S)",one);
	if (any(fneqs (sinh(one), 1.1752011936438014, 1e-6))) failed("sinh(%S)",one);
	if (any(fneqs (cosh(one), 1.5430806348152437, 1e-6))) failed("cosh(%S)",one);
	if (any(fneqs (tanh(one), 0.7615941559557649, 1e-6))) failed("tanh(%S)",one);
	if (any(fneqs (asinh(one), 0.881373587019543, 1e-6))) failed("asinh(%S)",one);
	if (any(fneqs (acosh(one), 0.0, 1e-6))) failed("acosh(%S)",one);
	ifnot (all(isinf(atanh(one)))) failed("atanh(%S)",one);
	if (any(fneqs (log(ten), 2.302585092994046, 1e-6))) failed("log(%S)",ten);
	if (any(fneqs (exp(one), E, 1e-6))) failed("exp(%S)",one);
	if (any(log10(ten) != 1)) failed("log10(%S)", ten);
#ifexists Complex_Type
	if (any(Imag(one) != 0)) failed ("Imag(%S)", one);
	if (any(Real(one) != 1)) failed ("Real(%S)", one);
	if (any(Conj(one) != 1)) failed ("Conj(%S)", one);
#endif
     }
}
test_misc_trig ();

private define test_frexp_ldexp ()
{
   foreach ([Float_Type, Double_Type])
     {
	variable type = ();
	variable x = typecast (2560, type);
	variable e, f;
	(f,e) = frexp (x);
	if ((f != 0.625) || (e != 12))
	  failed ("frexp(%S)", x);

	if (2560 != ldexp(f,e))
	  failed ("ldexp(%S,%S)", f, e);

	x = [x,x,x];
	(f,e) = frexp (x);
	if (any(f != 0.625) || any(e != 12))
	  failed ("frexp(%S)", x);

	if (any(2560 != ldexp(f,e)))
	  failed ("ldexp(%S,%S)", f, e);

	if (any(2560 != ldexp(f[0],e)))
	  failed ("ldexp(%S,%S)",f[0], e);

	if (any(2560 != ldexp(f,e[0])))
	  failed ("ldexp(%S,%S)",f,e[0]);
     }
}
test_frexp_ldexp ();

#ifexists Complex_Type
private define check_complex()
{
   if (isinf (1+2i)) failed ("isinf complex");
   ifnot (isinf (_Inf+2i)) failed ("isinf complex");
   ifnot (isinf (2i*_Inf)) failed ("isinf complex");

   if (isnan (1+2i)) failed ("isnan complex");
   ifnot (isnan (_NaN+2i)) failed ("isnan complex");
   ifnot (isnan (_NaN*2i)) failed ("isnan complex");
}
check_complex ();
#endif
print ("Ok\n");

exit (0);

