On Sun, 3 Feb 2019 at 15:12, Tom Lane <t...@sss.pgh.pa.us> wrote:
>
> Andrew Gierth <and...@tao11.riddles.org.uk> writes:
> > The spec doesn't require the inverse functions (asinh, acosh, atanh),
> > but surely there is no principled reason to omit them?
>
> +1 --- AFAICS, the C library has offered all six since C89.
>

+1 for including the inverse functions. However, it looks to me like
the inverse functions are C99-specific, so they might not be available
on all supported platforms. If they're not, we may need to provide our
own implementations.

I did a bit of research and had play. It looks like it might not be
too hard to provide our own implementations, but it does take a bit of
care to avoid rounding and overflow errors. Attached are some
standalone C programs where I tested various algorithms. A decent
approach seems to be to either use log1p() (which is itself
C99-specific, hence that's the first thing I played with) or to use a
single round of Newton-Raphson to correct rounding errors, in a
similar way to how we implement cbrt() on platforms that don't have
that.

Of course that may all be moot -- those functions may in fact be
available everywhere we care about, but it was interesting to play
around with them anyway.

Regards,
Dean
#include <float.h>
#include <math.h>
#include <stdio.h>

/*
 * A commonly used algorithm, due to Kahan (citation needed).
 *
 * Max error: 1 ulp.
 * Avg error: 0.158 ulp.
 *
 * It's not obvious, but this does appear to be monotonic at the cutover point
 * (at least on my machine). Can that be relied upon on all platforms?
 *
 * -5.5511151231257851673e-17 -> -5.5511151231257851673e-17 (u != 1 branch)
 * -5.5511151231257839347e-17 -> -5.5511151231257839347e-17 (u != 1 branch)
 * -5.5511151231257827021e-17 -> -5.5511151231257827021e-17 (u == 1 branch)
 * -5.5511151231257820858e-17 -> -5.5511151231257820858e-17 (u == 1 branch)
 *
 * 1.1102230246251564172e-16 -> 1.1102230246251564172e-16 (u == 1 branch)
 * 1.1102230246251565404e-16 -> 1.1102230246251565404e-16 (u == 1 branch)
 * 1.1102230246251567869e-16 -> 1.1102230246251565404e-16 (u != 1 branch)
 * 1.1102230246251570335e-16 -> 1.1102230246251567869e-16 (u != 1 branch)
 *
 * However, it doesn't appear to be monotonic at various other points.
 */
double kahan_log1p(double x)
{
	volatile double u = 1+x;
	return u == 1 ? x : x * (log(u) / (u-1));
}

/*
 * Algorithm used in the GNU Scientific Library.
 *
 * Max error: 1 ulp.
 * Avg error: 0.086 ulp.
 *
 * Where does this formula come from? Licensing?
 * Doesn't return -0 when x is -0, but that could be fixed up.
 * Looks to be monotonic everywhere.
 */
double gsl_log1p(double x)
{
	volatile double y, z;
	y = 1 + x;
	z = y - 1;
	return log(y) - (z-x)/y;
}

/*
 * Alternative algorithm using one round of Newton-Raphson.
 *
 * Max error: 1 ulp.
 * Avg error: 0.094 ulp.
 *
 * Works well for all inputs.
 * Looks to be monotonic everywhere.
 */
double nr_log1p(double x)
{
	double y, e;

	if (x == 0)
		return x;

	y = log(1+x);
	e = exp(y);

	return y - (e-1-x)/e;
}

/* === TESTS === */

int num_tests = 0;
double max_kahan_err = 0.0;
double max_gsl_err = 0.0;
double max_nr_err = 0.0;
double total_kahan_err = 0.0;
double total_gsl_err = 0.0;
double total_nr_err = 0.0;

double err(double x, double y)
{
	if (x < y)
	{
		double diff = y - x;
		double ulp = nextafter(x, y) - x;
		return diff / ulp;
	}
	if (x > y)
	{
		double diff = x - y;
		double ulp = nextafter(y, x) - y;
		return diff / ulp;
	}
	return 0.0;
}

void test_log1p(double x)
{
	double y = log1p(x);
	double kahan_y = kahan_log1p(x);
	double gsl_y = gsl_log1p(x);
	double nr_y = nr_log1p(x);
	double kahan_err = err(y, kahan_y);
	double gsl_err = err(y, gsl_y);
	double nr_err = err(y, nr_y);

	double prev_x = nextafter(x, -DBL_MAX);
	double next_x = nextafter(x, DBL_MAX);
#define monotonic(fn) \
	( prev_x == -1 || (fn)(prev_x) <= (fn)(x) ) && \
	( next_x == DBL_MAX || (fn)(next_x) >= (fn)(x) ) ? \
	"Monotonic" : "Not monotonic"

	printf("x = %.20g\n", x);
	printf("Standard result: %.20g %s\n", y, monotonic(log1p));
	printf("Kahan log1p():   %.20g,  err=%f %s\n", kahan_y, kahan_err,
		   monotonic(kahan_log1p));
	printf("GSL log1p():     %.20g,  err=%f %s\n", gsl_y, gsl_err,
		   monotonic(gsl_log1p));
	printf("NR log1p():      %.20g,  err=%f %s\n", nr_y, nr_err,
		   monotonic(nr_log1p));

	if (kahan_err > max_kahan_err) max_kahan_err = kahan_err;
	if (gsl_err > max_gsl_err) max_gsl_err = gsl_err;
	if (nr_err > max_nr_err) max_nr_err = nr_err;

	total_kahan_err += kahan_err;
	total_gsl_err += gsl_err;
	total_nr_err += nr_err;
	num_tests++;
}

int main()
{
	test_log1p(nextafter(-1, 0));
	test_log1p(3.141e-16-1);
	test_log1p(3.141e-15-1);
	test_log1p(3.141e-14-1);
	test_log1p(3.141e-13-1);
	test_log1p(3.141e-12-1);
	test_log1p(3.141e-11-1);
	test_log1p(3.141e-10-1);
	test_log1p(3.141e-9-1);
	test_log1p(3.141e-8-1);
	test_log1p(3.141e-7-1);
	test_log1p(3.141e-6-1);
	test_log1p(3.141e-5-1);
	test_log1p(3.141e-4-1);
	test_log1p(3.141e-3-1);
	test_log1p(3.141e-2-1);
	test_log1p(-6.859e-1);
	test_log1p(-6.859e-1);
	test_log1p(-6.859e-2);
	test_log1p(-6.859e-3);
	test_log1p(-6.859e-4);
	test_log1p(-6.859e-5);
	test_log1p(-6.859e-6);
	test_log1p(-6.859e-7);
	test_log1p(-6.859e-8);
	test_log1p(-6.859e-9);
	test_log1p(-6.859e-10);
	test_log1p(-6.859e-11);
	test_log1p(-6.859e-12);
	test_log1p(-6.859e-13);
	test_log1p(-6.859e-14);
	test_log1p(-6.859e-15);
	test_log1p(-6.859e-16);
	test_log1p(-6.859e-17);
	test_log1p(-5.5511151231257851673e-17);
	test_log1p(-5.5511151231257839347e-17);
	test_log1p(-5.5511151231257827021e-17);
	test_log1p(-5.5511151231257820858e-17);
	test_log1p(-6.859e-18);
	test_log1p(-6.859e-19);
	test_log1p(-6.859e-20);
	test_log1p(-6.859e-30);
	test_log1p(-6.859e-40);
	test_log1p(-6.859e-50);
	test_log1p(-6.859e-60);
	test_log1p(-6.859e-70);
	test_log1p(-6.859e-80);
	test_log1p(-6.859e-90);
	test_log1p(-6.859e-100);
	test_log1p(-6.859e-200);
	test_log1p(-6.859e-300);
	test_log1p(-2.23e-308);
	test_log1p(-2.23e-315);
	test_log1p(-2.23e-320);
	test_log1p(-9.9e-324);
	test_log1p(-4.94e-324);
	test_log1p(nextafter(0, -1));
	test_log1p(-0.0);
	test_log1p(+0.0);
	test_log1p(nextafter(0, 1));
	test_log1p(4.94e-324);
	test_log1p(9.9e-324);
	test_log1p(2.23e-320);
	test_log1p(2.23e-315);
	test_log1p(2.23e-308);
	test_log1p(3.141e-300);
	test_log1p(3.141e-200);
	test_log1p(3.141e-100);
	test_log1p(3.141e-90);
	test_log1p(3.141e-80);
	test_log1p(3.141e-70);
	test_log1p(3.141e-60);
	test_log1p(3.141e-50);
	test_log1p(3.141e-40);
	test_log1p(3.141e-30);
	test_log1p(3.141e-20);
	test_log1p(3.141e-19);
	test_log1p(3.141e-18);
	test_log1p(3.141e-17);
	test_log1p(1.1102230246251564172e-16);
	test_log1p(1.1102230246251565404e-16);
	test_log1p(1.1102230246251567869e-16);
	test_log1p(1.1102230246251570335e-16);
	test_log1p(3.141e-16);
	test_log1p(3.141e-15);
	test_log1p(3.141e-14);
	test_log1p(3.141e-13);
	test_log1p(3.141e-12);
	test_log1p(3.141e-11);
	test_log1p(3.141e-10);
	test_log1p(3.141e-9);
	test_log1p(3.141e-8);
	test_log1p(3.141e-7);
	test_log1p(3.141e-6);
	test_log1p(3.141e-5);
	test_log1p(3.141e-4);
	test_log1p(3.141e-3);
	test_log1p(3.141e-2);
	test_log1p(0.3141);
	test_log1p(0.6324432);
	test_log1p(0.96324432);
	test_log1p(1.6324432);
	test_log1p(2.6324432);
	test_log1p(5.6324432);
	test_log1p(24.6324432);
	test_log1p(5.6324432e1);
	test_log1p(5.6324432e2);
	test_log1p(5.6324432e3);
	test_log1p(5.6324432e4);
	test_log1p(5.6324432e5);
	test_log1p(5.6324432e6);
	test_log1p(5.6324432e7);
	test_log1p(5.6324432e8);
	test_log1p(5.6324432e9);
	test_log1p(5.6324432e10);
	test_log1p(5.6324432e11);
	test_log1p(5.6324432e12);
	test_log1p(5.6324432e13);
	test_log1p(5.6324432e14);
	test_log1p(5.6324432e15);
	test_log1p(5.6324432e16);
	test_log1p(5.6324432e17);
	test_log1p(5.6324432e18);
	test_log1p(5.6324432e19);
	test_log1p(5.6324432e20);
	test_log1p(5.6324432e30);
	test_log1p(5.6324432e40);
	test_log1p(5.6324432e50);
	test_log1p(5.6324432e60);
	test_log1p(5.6324432e70);
	test_log1p(5.6324432e80);
	test_log1p(5.6324432e90);
	test_log1p(5.6324432e100);
	test_log1p(5.6324432e200);
	test_log1p(5.6324432e300);
	test_log1p(5.6324432e307);
	test_log1p(8.98e307);
	test_log1p(1.797e308);
	test_log1p(DBL_MAX);

	printf("\n");
	printf("Number of tests: %d\n", num_tests);

	printf("\n");
	printf("Max error for Kahan log1p(): %f\n", max_kahan_err);
	printf("Max error for GSL log1p():   %f\n", max_gsl_err);
	printf("Max error for NR log1p():    %f\n", max_nr_err);

	printf("\n");
	printf("Avg. error for Kahan log1p(): %f\n", total_kahan_err / num_tests);
	printf("Avg. error for GSL log1p():   %f\n", total_gsl_err / num_tests);
	printf("Avg. error for NR log1p():    %f\n", total_nr_err / num_tests);

	return 0;
}
#include <float.h>
#include <math.h>
#include <stdio.h>

double nr_log1p(double x)
{
	double y, e;

	if (x == 0)
		return x;

	y = log(1+x);
	e = exp(y);

	return y - (e-1-x)/e;
}

/*
 * Algorithm recommended by [1] "The Right Way to Calculate Stuff".
 *
 * Loses all accuracy for negative inputs, but easily fixed, as below.
 * Also loses all accuracy for large inputs. Not so easily fixed.
 *
 * Max error: 12193974156573 ulp (when x ~ +/- 5.6e200).
 * Avg error: 554271552571 ulp.
 *
 * Not the right way to calculate asinh.
 *
 * [1] http://www.plunk.org/~hatch/rightway.php
 */
double plunk_asinh(double x)
{
	if (x < 0)
		return -nr_log1p(-x * (1 - x/(sqrt(x*x+1)+1)));
	if (x > 0)
		return nr_log1p(x * (1 + x/(sqrt(x*x+1)+1)));
	return x;
}

/*
 * Alternative algorithm using one round of Newton-Raphson.
 * Have to stitch together 2 different formulae at x=1 to avoid overflow risk.
 *
 * Max error: 1 ulp.
 * Avg error: 0.117 ulp.
 *
 * Works for all inputs.
 * Looks to be monotonic everywhere.
 */
double nr_asinh(double x)
{
	int sign;
	double y;

	if (x == 0)
		return x;

	sign = x < 0 ? -1 : 1;
	x = fabs(x);

	if (x < 1)
		y = log(x+sqrt(1+x*x));
	else
		y = log(x) + log(1+sqrt(1+1/(x*x)));

	return sign * (y - (sinh(y)-x)/cosh(y));
}

/* === TESTS === */

int num_tests = 0;
double max_plunk_err = 0.0;
double max_nr_err = 0.0;
double total_plunk_err = 0.0;
double total_nr_err = 0.0;

double err(double x, double y)
{
	if (x < y)
	{
		double diff = y - x;
		double ulp = nextafter(x, y) - x;
		return diff / ulp;
	}
	if (x > y)
	{
		double diff = x - y;
		double ulp = nextafter(y, x) - y;
		return diff / ulp;
	}
	return 0.0;
}

void test_asinh(double x)
{
	double y = asinh(x);
	double plunk_y = plunk_asinh(x);
	double nr_y = nr_asinh(x);
	double plunk_err = err(y, plunk_y);
	double nr_err = err(y, nr_y);

	double prev_x = nextafter(x, -DBL_MAX);
	double next_x = nextafter(x, DBL_MAX);
#define monotonic(fn) \
	( prev_x == -DBL_MAX || (fn)(prev_x) <= (fn)(x) ) && \
	( next_x == DBL_MAX || (fn)(next_x) >= (fn)(x) ) ? \
	"Monotonic" : "Not monotonic"

	printf("x = %.20g\n", x);
	printf("Standard result: %.20g %s\n", y, monotonic(asinh));
	printf("Plunk asinh():   %.20g,  err=%f %s\n", plunk_y, plunk_err,
		   monotonic(plunk_asinh));
	printf("NR asinh():      %.20g,  err=%f %s\n", nr_y, nr_err,
		   monotonic(nr_asinh));

	if (plunk_err > max_plunk_err) max_plunk_err = plunk_err;
	if (nr_err > max_nr_err) max_nr_err = nr_err;

	total_plunk_err += plunk_err;
	total_nr_err += nr_err;
	num_tests++;
}

int main()
{
	int s;

	for (s=-1; s<=1; s+=2)
	{
		test_asinh(s*0.0);
		test_asinh(s*nextafter(0, 1));
		test_asinh(s*4.94e-324);
		test_asinh(s*9.9e-324);
		test_asinh(s*2.23e-320);
		test_asinh(s*2.23e-315);
		test_asinh(s*2.23e-308);
		test_asinh(s*3.141e-300);
		test_asinh(s*3.141e-200);
		test_asinh(s*3.141e-100);
		test_asinh(s*3.141e-90);
		test_asinh(s*3.141e-80);
		test_asinh(s*3.141e-70);
		test_asinh(s*3.141e-60);
		test_asinh(s*3.141e-50);
		test_asinh(s*3.141e-40);
		test_asinh(s*3.141e-30);
		test_asinh(s*3.141e-20);
		test_asinh(s*3.141e-19);
		test_asinh(s*3.141e-18);
		test_asinh(s*3.141e-17);
		test_asinh(s*3.141e-16);
		test_asinh(s*3.141e-15);
		test_asinh(s*3.141e-14);
		test_asinh(s*3.141e-13);
		test_asinh(s*3.141e-12);
		test_asinh(s*3.141e-11);
		test_asinh(s*3.141e-10);
		test_asinh(s*3.141e-9);
		test_asinh(s*3.141e-8);
		test_asinh(s*3.141e-7);
		test_asinh(s*3.141e-6);
		test_asinh(s*3.141e-5);
		test_asinh(s*3.141e-4);
		test_asinh(s*3.141e-3);
		test_asinh(s*3.141e-2);
		test_asinh(s*0.3141);
		test_asinh(s*0.6324432);
		test_asinh(s*0.96324432);
		test_asinh(s*nextafter(1, 0));
		test_asinh(s*1.0);
		test_asinh(s*nextafter(1, 2));
		test_asinh(s*1.6324432);
		test_asinh(s*2.6324432);
		test_asinh(s*5.6324432);
		test_asinh(s*24.6324432);
		test_asinh(s*5.6324432e1);
		test_asinh(s*5.6324432e2);
		test_asinh(s*5.6324432e3);
		test_asinh(s*5.6324432e4);
		test_asinh(s*5.6324432e5);
		test_asinh(s*5.6324432e6);
		test_asinh(s*5.6324432e7);
		test_asinh(s*5.6324432e8);
		test_asinh(s*5.6324432e9);
		test_asinh(s*5.6324432e10);
		test_asinh(s*5.6324432e11);
		test_asinh(s*5.6324432e12);
		test_asinh(s*5.6324432e13);
		test_asinh(s*5.6324432e14);
		test_asinh(s*5.6324432e15);
		test_asinh(s*5.6324432e16);
		test_asinh(s*5.6324432e17);
		test_asinh(s*5.6324432e18);
		test_asinh(s*5.6324432e19);
		test_asinh(s*5.6324432e20);
		test_asinh(s*5.6324432e30);
		test_asinh(s*5.6324432e40);
		test_asinh(s*5.6324432e50);
		test_asinh(s*5.6324432e60);
		test_asinh(s*5.6324432e70);
		test_asinh(s*5.6324432e80);
		test_asinh(s*5.6324432e90);
		test_asinh(s*5.6324432e100);
		test_asinh(s*5.6324432e200);
		test_asinh(s*5.6324432e300);
		test_asinh(s*5.6324432e307);
		test_asinh(s*8.98e307);
		test_asinh(s*1.797e308);
		test_asinh(s*DBL_MAX);
	}

	printf("\n");
	printf("Number of tests: %d\n", num_tests);

	printf("\n");
	printf("Max error for Plunk asinh(): %f\n", max_plunk_err);
	printf("Max error for NR asinh():    %f\n", max_nr_err);

	printf("\n");
	printf("Avg. error for Plunk asinh(): %f\n", total_plunk_err / num_tests);
	printf("Avg. error for NR asinh():    %f\n", total_nr_err / num_tests);

	return 0;
}
#include <float.h>
#include <math.h>
#include <stdio.h>

double nr_log1p(double x)
{
	double y, e;

	if (x == 0)
		return x;

	y = log(1+x);
	e = exp(y);

	return y - (e-1-x)/e;
}

/*
 * Algorithm using one round of Newton-Raphson.
 * Not expected to work well, because of infinite slope at x=1.
 *
 * Max error: 25216051 ulp (near x=1).
 * Avg error: 1236022 ulp.
 *
 * Not recommended.
 */
double nr_acosh(double x)
{
	double y;

	if (x < 1)
		return NAN;
	if (x == 1)
        return 0;

	y = log(x) + log(1+sqrt(1-1/(x*x)));
	return y - (cosh(y)-x)/sinh(y);
}

/*
 * Alternative algorithm using log1p.
 * Need to be careful to avoid overflow risk for large inputs.
 *
 * Max error: 2 ulp.
 * Avg error: 0.210 ulp.
 *
 * Works for all inputs.
 * Looks to be monotonic everywhere.
 */
double log1p_acosh(double x)
{
	double t, u;

	if (x < 1)
		return NAN;
	if (x == 1)
		return 0;

	t = x-1;
	u = t/x;
	return log(x) + nr_log1p(u * sqrt(1+2/t));
}

/* === TESTS === */

int num_tests = 0;
double max_nr_err = 0.0;
double max_log1p_err = 0.0;
double total_nr_err = 0.0;
double total_log1p_err = 0.0;

double err(double x, double y)
{
	if (x < y)
	{
		double diff = y - x;
		double ulp = nextafter(x, y) - x;
		return diff / ulp;
	}
	if (x > y)
	{
		double diff = x - y;
		double ulp = nextafter(y, x) - y;
		return diff / ulp;
	}
	return 0.0;
}

void test_acosh(volatile double x)
{
	volatile double y = acosh(x);
	volatile double nr_y = nr_acosh(x);
	volatile double log1p_y = log1p_acosh(x);
	volatile double nr_err = err(y, nr_y);
	volatile double log1p_err = err(y, log1p_y);

	double prev_x = nextafter(x, -DBL_MAX);
	double next_x = nextafter(x, DBL_MAX);
#define monotonic(fn) \
	( prev_x < 1 || (fn)(prev_x) <= (fn)(x) ) && \
	( next_x == DBL_MAX || (fn)(next_x) >= (fn)(x) ) ? \
	"Monotonic" : "Not monotonic"

	printf("x = %.20g\n", x);
	printf("Standard result: %.20g %s\n", y, monotonic(acosh));
	printf("NR acosh():      %.20g,  err=%f %s\n", nr_y, nr_err,
		   monotonic(nr_acosh));
	printf("log1p acosh():   %.20g,  err=%f %s\n", log1p_y, log1p_err,
		   monotonic(log1p_acosh));

	if (nr_err > max_nr_err) max_nr_err = nr_err;
	if (log1p_err > max_log1p_err) max_log1p_err = log1p_err;

	total_nr_err += nr_err;
	total_log1p_err += log1p_err;
	num_tests++;
}

int main()
{
	test_acosh(1);
	test_acosh(1+3.141e-20);
	test_acosh(1+3.141e-19);
	test_acosh(1+3.141e-18);
	test_acosh(1+3.141e-17);
	test_acosh(1+3.141e-16);
	test_acosh(nextafter(1, 2));
	test_acosh(1+3.141e-15);
	test_acosh(1+3.141e-14);
	test_acosh(1+3.141e-13);
	test_acosh(1+3.141e-12);
	test_acosh(1+3.141e-11);
	test_acosh(1+3.141e-10);
	test_acosh(1+3.141e-9);
	test_acosh(1+3.141e-8);
	test_acosh(1+3.141e-7);
	test_acosh(1+3.141e-6);
	test_acosh(1+3.141e-5);
	test_acosh(1+3.141e-4);
	test_acosh(1+3.141e-3);
	test_acosh(1+3.141e-2);
	test_acosh(1.3141);
	test_acosh(1.6324432);
	test_acosh(1.96324432);
	test_acosh(2.6324432);
	test_acosh(3.6324432);
	test_acosh(5.6324432);
	test_acosh(24.6324432);
	test_acosh(5.6324432e1);
	test_acosh(5.6324432e2);
	test_acosh(5.6324432e3);
	test_acosh(5.6324432e4);
	test_acosh(5.6324432e5);
	test_acosh(5.6324432e6);
	test_acosh(5.6324432e7);
	test_acosh(5.6324432e8);
	test_acosh(5.6324432e9);
	test_acosh(5.6324432e10);
	test_acosh(5.6324432e11);
	test_acosh(5.6324432e12);
	test_acosh(5.6324432e13);
	test_acosh(5.6324432e14);
	test_acosh(5.6324432e15);
	test_acosh(5.6324432e16);
	test_acosh(5.6324432e17);
	test_acosh(5.6324432e18);
	test_acosh(5.6324432e19);
	test_acosh(5.6324432e20);
	test_acosh(5.6324432e30);
	test_acosh(5.6324432e40);
	test_acosh(5.6324432e50);
	test_acosh(5.6324432e60);
	test_acosh(5.6324432e70);
	test_acosh(5.6324432e80);
	test_acosh(5.6324432e90);
	test_acosh(5.6324432e100);
	test_acosh(5.6324432e200);
	test_acosh(5.6324432e300);
	test_acosh(5.6324432e307);
	test_acosh(8.98e307);
	test_acosh(1.797e308);
	test_acosh(DBL_MAX);

	printf("\n");
	printf("Number of tests: %d\n", num_tests);

	printf("\n");
	printf("Max error for NR acosh():    %f\n", max_nr_err);
	printf("Max error for log1p acosh(): %f\n", max_log1p_err);

	printf("\n");
	printf("Avg. error for NR acosh():    %f\n", total_nr_err / num_tests);
	printf("Avg. error for log1p acosh(): %f\n", total_log1p_err / num_tests);

	return 0;
}
#include <float.h>
#include <math.h>
#include <stdio.h>

double nr_log1p(double x)
{
	double y, e;

	if (x == 0)
		return x;

	y = log(1+x);
	e = exp(y);

	return y - (e-1-x)/e;
}

/*
 * Algorithm recommended by [1] "The Right Way to Calculate Stuff".
 * Corrected to avoid loss of accuracy for negative inputs.
 *
 * Max error: 1 ulp.
 * Avg error: 0.129 ulp.
 *
 * Works for all inputs.
 * Looks to be monotonic everywhere.
 *
 * [1] http://www.plunk.org/~hatch/rightway.php
 */
double plunk_atanh(double x)
{
	if (x == 1)
		return INFINITY;
	if (x == -1)
		return -INFINITY;
	if (x < 0)
		return -nr_log1p(-2*x/(1+x)) / 2;
	if (x > 0)
		return nr_log1p(2*x/(1-x)) / 2;
	return x;
}

/*
 * Algorithm using one round of Newton-Raphson.
 *
 * Max error: 2 ulp.
 * Avg error: 0.081 ulp.
 *
 * Works for all inputs.
 * Looks to be monotonic everywhere.
 */
double nr_atanh(double x)
{
	int sign;
	double y, t;

	if (x == 0)
		return x;
	if (x == 1)
		return INFINITY;
	if (x == -1)
		return -INFINITY;

	sign = x < 0 ? -1 : 1;
	x = fabs(x);

	y = log((1+x)/(1-x)) / 2;
	t = tanh(y);

	return sign * (y - (t-x)/(1-t*t));
}

/*
 * Alternative algorithm using log1p in the obvious way.
 *
 * Max error: 1 ulp.
 * Avg error: 0.129 ulp.
 *
 * Works for all inputs.
 * Looks to be monotonic everywhere.
 * Nice and simple.
 */
double log1p_atanh(double x)
{
	if (x == 0)
		return x;
	if (x == 1)
		return INFINITY;
	if (x == -1)
		return -INFINITY;
	return (nr_log1p(x) - nr_log1p(-x)) / 2;
}

/* === TESTS === */

int num_tests = 0;
double max_plunk_err = 0.0;
double max_nr_err = 0.0;
double max_log1p_err = 0.0;
double total_plunk_err = 0.0;
double total_nr_err = 0.0;
double total_log1p_err = 0.0;

double err(double x, double y)
{
	if (x < y)
	{
		double diff = y - x;
		double ulp = nextafter(x, y) - x;
		return diff / ulp;
	}
	if (x > y)
	{
		double diff = x - y;
		double ulp = nextafter(y, x) - y;
		return diff / ulp;
	}
	return 0.0;
}

void test_atanh(double x)
{
	double y = atanh(x);
	double plunk_y = plunk_atanh(x);
	double nr_y = nr_atanh(x);
	double log1p_y = log1p_atanh(x);
	double plunk_err = err(y, plunk_y);
	double nr_err = err(y, nr_y);
	double log1p_err = err(y, log1p_y);

	double prev_x = nextafter(x, -DBL_MAX);
	double next_x = nextafter(x, DBL_MAX);
#define monotonic(fn) \
	( prev_x < -1 || (fn)(prev_x) <= (fn)(x) ) && \
	( next_x > 1 || (fn)(next_x) >= (fn)(x) ) ? \
	"Monotonic" : "Not monotonic"

	printf("x = %.20g\n", x);
	printf("Standard result: %.20g %s\n", y, monotonic(atanh));
	printf("Plunk atanh():   %.20g,  err=%f %s\n", plunk_y, plunk_err,
		   monotonic(plunk_atanh));
	printf("NR atanh():      %.20g,  err=%f %s\n", nr_y, nr_err,
		   monotonic(nr_atanh));
	printf("log1p atanh():   %.20g,  err=%f %s\n", log1p_y, log1p_err,
		   monotonic(log1p_atanh));

	if (plunk_err > max_plunk_err) max_plunk_err = plunk_err;
	if (nr_err > max_nr_err) max_nr_err = nr_err;
	if (log1p_err > max_log1p_err) max_log1p_err = log1p_err;

	total_plunk_err += plunk_err;
	total_nr_err += nr_err;
	total_log1p_err += log1p_err;
	num_tests++;
}

int main()
{
	int s;

	for (s=-1; s<=1; s+=2)
	{
		test_atanh(s*0.0);
		test_atanh(s*nextafter(0, 1));
		test_atanh(s*4.94e-324);
		test_atanh(s*9.9e-324);
		test_atanh(s*2.23e-320);
		test_atanh(s*2.23e-315);
		test_atanh(s*2.23e-308);
		test_atanh(s*3.141e-300);
		test_atanh(s*3.141e-200);
		test_atanh(s*3.141e-100);
		test_atanh(s*3.141e-90);
		test_atanh(s*3.141e-80);
		test_atanh(s*3.141e-70);
		test_atanh(s*3.141e-60);
		test_atanh(s*3.141e-50);
		test_atanh(s*3.141e-40);
		test_atanh(s*3.141e-30);
		test_atanh(s*3.141e-20);
		test_atanh(s*3.141e-19);
		test_atanh(s*3.141e-18);
		test_atanh(s*3.141e-17);
		test_atanh(s*3.141e-16);
		test_atanh(s*3.141e-15);
		test_atanh(s*3.141e-14);
		test_atanh(s*3.141e-13);
		test_atanh(s*3.141e-12);
		test_atanh(s*3.141e-11);
		test_atanh(s*3.141e-10);
		test_atanh(s*3.141e-9);
		test_atanh(s*3.141e-8);
		test_atanh(s*3.141e-7);
		test_atanh(s*3.141e-6);
		test_atanh(s*3.141e-5);
		test_atanh(s*3.141e-4);
		test_atanh(s*3.141e-3);
		test_atanh(s*3.141e-2);
		test_atanh(s*0.13141);
		test_atanh(s*0.23141);
		test_atanh(s*0.33141);
		test_atanh(s*0.43141);
		test_atanh(s*0.53141);
		test_atanh(s*0.63141);
		test_atanh(s*0.73141);
		test_atanh(s*0.83141);
		test_atanh(s*0.93141);
		test_atanh(s*(1-3.141e-2));
		test_atanh(s*(1-3.141e-3));
		test_atanh(s*(1-3.141e-4));
		test_atanh(s*(1-3.141e-5));
		test_atanh(s*(1-3.141e-6));
		test_atanh(s*(1-3.141e-7));
		test_atanh(s*(1-3.141e-8));
		test_atanh(s*(1-3.141e-9));
		test_atanh(s*(1-3.141e-10));
		test_atanh(s*(1-3.141e-11));
		test_atanh(s*(1-3.141e-12));
		test_atanh(s*(1-3.141e-13));
		test_atanh(s*(1-3.141e-14));
		test_atanh(s*(1-3.141e-15));
		test_atanh(s*(1-3.141e-16));
		test_atanh(s*nextafter(1, 0));
		test_atanh(s);
	}

	printf("\n");
	printf("Number of tests: %d\n", num_tests);

	printf("\n");
	printf("Max error for Plunk atanh(): %f\n", max_plunk_err);
	printf("Max error for NR atanh():    %f\n", max_nr_err);
	printf("Max error for log1p atanh(): %f\n", max_log1p_err);

	printf("\n");
	printf("Avg. error for Plunk atanh(): %f\n", total_plunk_err / num_tests);
	printf("Avg. error for NR atanh():    %f\n", total_nr_err / num_tests);
	printf("Avg. error for log1p atanh(): %f\n", total_log1p_err / num_tests);

	return 0;
}

Reply via email to