Dear Mr. Plauger,

In response to the letter of Steve Price (CUJ 13, #10, Oct 95) regarding the
lack on inverse hyperbolic trig. functions, I offer the three source files for
double and long double inverse hyperbolic functions ArcSinh, ArcTanh and
ArcCosh, together with the following sketch of the derivations of their
formulae.  I hope this code solves Mr. Price's problem.

The inverse hyperbolic code here is offered as "Bannerware"; use it freely,
even in commercial settings, provided the copyright notice is retained.  See
the copyright notice in the source for full details.

A minor aside on copyright is relevant here.  In one of the professional rags I
regularly read I saw a reviewer explode over the "All Rights Reserved" on the
software in a book/code disk combo package.  Anyone dealing with intellectual
property should be aware that in certain countries the affixing of a valid
copyright notice was, (is?), NOT sufficient to protect the work.  In these
countries the additional phrase "All Rights Reserved" was (is?) required to
secure ANY protection under copyright laws, statutes and treaties.  Because of
this, the use of this phrase has become standard legal boilerplate for
copyright notices.  In the code supplied here, I retain the copyright but grant
free license for copying and distribution provided my copyright notice is
retained.

As someone who primarily programs number crunchers in support of my other
engineering activities, I ordinarily work in FORTRAN.  When FORTRAN fails to
suffice, I usually turn to assembler.  C is not my primary or secondary
language.  I hope my coding "style" does not offend anyone.

I have compiled and tested these routines with Microsoft's compilers:
[cl /AL /Ox /FPi /G2 test.c arc_sinh.c arc_cosh.c arc_tanh.c]
(C 5.1, C 6.0, C/C++ 7.0 and VC 1.0, a.k.a., C/C++ 8.0) and with Borland 's
compilers: [bcc -ml -1 -Ox test.c arc_sinh.c arc_cosh.c arc_tanh.c] or
[tcc -ml -1 -O test.c arc_sinh.c arc_cosh.c arc_tanh.c]
(Turbo C 2.0, Turbo C/C++ 3.0 and Borland C 4.02).

The Turbo C++ 3.0 compiler didn't handle long doubles properly; I don't know if
this was the compiler or my code.  Microsoft C 5.1 and Turbo C 2.0 don't do
long doubles.  BCC 4.02 and Microsoft 6.0 and later all properly handle the
long doubles as used in this code.

I tested the inverse hyperbolic functions by computing a hyperbolic then
undoing it.  The result should match the original argument, trivial differences
around the precision limit are, of course, understandable.  TEST.C is the major
testing program.

In his letter Mr. Price did not indicate over what domain and range he needed
to evaluate the inverse hyperbolics.  I tried to exercise the inverse
hyperbolics over a fairly broad domain.

-----------------------------------------------------------------------------
The derivation of ArcSinh:
Hyperbolic sine, a.k.a., sinh, is defined in terms of exponentials:

(1)	 sinh(x) = (   exp(x) - exp(-x)   )/2

Finding the inverse of this function involves inverting the definitional
equation (1).  The first step is to multiply both sides by 2 exp(x) getting:

(2)	 2 exp(x) sinh(x) = (exp(x))^2 - 1

Equation (2) is a quadratic in exp(x).	To clarify, let z = exp(x) and rewrite:

(2')     2 z sinh(x) = z^2 - 1

This is expressed in standard form (e.g., A z^2 + B z + C = 0) as

(3)	z^2 + (- 2 sinh(x)) z - 1 = 0
Where A = 1, B = -2 sinh(x) and C = -1.

Therefore z can be solved for as either

(4)	z = ( -B  sqrt( B^2 - 4 A C) / (2 A)
or as
(4')    z = (2 C) / ( -B  sqrt( B^2 - 4 A C)

The excellent discussion of the problems of machine implementation of either of
these forms in Numerical Recipes (any edition) points out that the use of either
equation to find both roots will encounter precision problems.	The
resolution results from the fact that both (4) and (4') will compute one of the
two roots very accurately, and the root that (4) handles well is the one that
(4') handles poorly and vice versa.

Therefore if B is positive we choose to calculate the two roots by
(5a)	z1 = ( -B - sqrt( B^2 - 4 A C) / (2 A)
(5b)	z2 = (2 C) / ( -B - sqrt( B^2 - 4 A C)

Whereas if B is negative the optimum choices are
(6a)	z1 = ( -B + sqrt( B^2 - 4 A C) / (2 A)
(6b)	z2 = (2 C) / ( -B + sqrt( B^2 - 4 A C)

Since Sinh(-x) = - Sinh(x) it suffices to handle the case of positive real
values and to handle negative reals by a simple test and sign inversion.
With this limitation the two roots are given by (6a) and (6b).

The next issue is to determine which of these roots is valid.  In general
the answer can range from none to all, but often only one is valid.  Since we
know that a positive value of Sinh(x) results from a positive value of x, (6a)
is the correct solution.

As previously noted A = 1, B = -2 sinh(x) and C = -1, so (6a) reduces to

(7)	z = (2 sinh(x) + sqrt( sinh(x)*sinh(x) - 4 * (1) * (-1) ) /(2 * 1)

Which simplifies to

(8)	z = sinh(x) + sqrt( (sinh(x))^2 + 1)

Remembering that z is exp(x), we solve for x by taking the natural log of
both sides of (8) to yield:

(9)	x = ln( sinh(x) + sqrt( (sinh(x))^2 + 1) )

Therefore

(10a)	arcsinh(u) = +ln( +u + sqrt( u^2 + 1 ) )   for positive u
(10b)	arcsinh(u) = -ln( -u + sqrt( u^2 + 1 ) )   for negative u

which can be reexpressed as

(11)	arcsinh(u) = sgn(u) * ln( |u| + sqrt( u^2 + 1 )  )

where sgn(u) = u / |u|	  whenever u is not 0

Since standard C does not have an sgn function, but does have the ? : ternary
operator, arcsinh can be expressed as:

	     ( (u >= 0.0e+000)	?
		    ( +log( fabs(u) + sqrt(u*u + 1.0e+000) ) )
		:
		    ( -log( fabs(u) + sqrt(u*u + 1.0e+000) ) )
	     )

Where the the fabs(u) in the +log( ... ) expression is unnecessary, but
was left in for clarity.  If your compiler does not optimize this out, which is
likely, remove the first fabs(u) call and code it as:

	     ( (u >= 0.0e+000)	?
		    ( +log( u	    + sqrt(u*u + 1.0e+000) ) )
		:
		    ( -log( fabs(u) + sqrt(u*u + 1.0e+000) ) )
	     )


Of course, if you know your input will be always positive or always negative,
the ternary operator, its test and the unused branch can be eliminated as well.

While it is possible to derive expressions in infinite series of primitive
operations, such as a Taylor's series (i.e., power series expansion about the
point u = 0) for arcsinh(u), machine implementation of such series is fraught
with peril.  Poor use of machine cycles, excessive loss of precision,
and improper handling of exceptional values are just three of the more common
problems with series implementations.  Unless the problem demands a
simultaneous maximization of precision with minimization of compute time, and
can justify spending development resources to the accomplishment of such goals,
the solution in terms of log and sqrt will yield acceptable results.  The
run time and precision will be limited by the (presumably highly, or at
least so we hope, tuned) implementations of log and sqrt in the vendor's
library.  Calculation by the formulae of (10) or (11) will be only slightly
slower and a bit or three less precise than an optimized direct implementation
(which is NOT a mechanical transliteration of the Taylor's series) would yield.

-----------------------------------------------------------------------------
Inverting Cosh and Tanh brings the additional issue of input range checking.
Tanh maps the entire real line onto the range (-1 .. +1), so it's inverse
should not be called upon to handle values with magnitude greater than 1.0.
(Actually it can if we generalize to complex numbers, but I am not familiar
with the complex classes for C++ and I doubt you'd care to feed FORTRAN to MS
C/C++ 7.0.  Further, I get the impression that pure real values are all that
are of interest in your problem.)

The derivation of ArcTanh:
Tanh(x) is defined in terms of exponentials by

(12)	tanh(x) = (exp(x) - exp(-x)) / (exp(x) + exp(-x))

This can be reexpressed as

(13)	tanh(x) = ( (exp(x))^2 - 1 ) / ( (exp(x))^2 + 1 )

Which is once again recognizeable as a quadratic in exp(x).  Again substituting
z = exp(x), we get, after simplification:

(14)	z^2 = ( 1 + tanh(x) ) / ( 1 - tanh(x) )

And so

(15)	x =  (1/2) * ln ( ( 1 + tanh(x) ) / ( 1 - tanh(x) ) )

Since Tanh(-x) = -Tanh(x), it is clear by testing a few values that the
+ branch of (15) is the correct solution.  Implementing the input range test
and computation in one formula yields for arctanh(u):

	     ( (fabs(u) > 1.0e+000)  ?
		    /* input range error */ ( log(-1.0e+000) )
		:
		    ( +0.5e+000 * log( (u+1.0e+000)/(u-1.0e+000) ) )
	     )

I strongly recommend you use a different way of handling the case of erroneous
input values.


-----------------------------------------------------------------------------
Finally, for hyperbolic cosine, Cosh, we must note at the outset that since
Cosh maps both the positive and negative real half-lines onto the positive real
half-line (i.e., Cosh(-x) = Cosh(x)), there is no way to determine which of the
two possible solutions is valid.  Thus, following the time honored convention
of sqrt, solutions will be taken as positive.

The derivation of ArcCosh:
The definition of Cosh(x) in terms of exponentials is:

(16)	cosh(x) = (exp(x) + exp(-x)) / 2

Taking the same approach as before, this becomes a quadratic in exp(x), which
results in:

(17)	x = ln( cosh(x)  sqrt(cosh(x)*cosh(x) - 1) )
or
(18)	x = -ln( cosh(x)  sqrt(cosh(x)*cosh(x) - 1) )

Clearly, to avoid subtraction of two nearly equal numbers we choose the +
branch of both (17) and (18), and since we've (arbitrarily, but with historical
precedents) chosen a positive result we use (17).  This is implemented in C as:

	     ( (u >= 1.0e+000)	?
		    ( +log( fabs(u) + sqrt(u*u - 1.0e+000) ) )
		:
		    /* input range error */ ( log(-1.0e+000) )
	     )

Once again I strongly recommend you use a different way of handling the case of
erroneous input values.

