num/quat: fix Sinh, Cosh and Tanh for infinite input
diff --git a/num/quat/quat_test.go b/num/quat/quat_test.go
index 0e4a51f..5e3d44a 100644
--- a/num/quat/quat_test.go
+++ b/num/quat/quat_test.go
@@ -191,11 +191,28 @@
 		floats.EqualWithinAbsOrRel(a.Kmag, b.Kmag, tol, tol)
 }
 
+func sameApprox(a, b Number, tol float64) bool {
+	switch {
+	case a.Real == 0 && b.Real == 0:
+		return math.Signbit(a.Real) == math.Signbit(b.Real)
+	case a.Imag == 0 && b.Imag == 0:
+		return math.Signbit(a.Imag) == math.Signbit(b.Imag)
+	case a.Jmag == 0 && b.Jmag == 0:
+		return math.Signbit(a.Jmag) == math.Signbit(b.Jmag)
+	case a.Kmag == 0 && b.Kmag == 0:
+		return math.Signbit(a.Kmag) == math.Signbit(b.Kmag)
+	}
+	return (sameFloat(a.Real, b.Real) || floats.EqualWithinAbsOrRel(a.Real, b.Real, tol, tol)) &&
+		(sameFloat(a.Imag, b.Imag) || floats.EqualWithinAbsOrRel(a.Imag, b.Imag, tol, tol)) &&
+		(sameFloat(a.Jmag, b.Jmag) || floats.EqualWithinAbsOrRel(a.Jmag, b.Jmag, tol, tol)) &&
+		(sameFloat(a.Kmag, b.Kmag) || floats.EqualWithinAbsOrRel(a.Kmag, b.Kmag, tol, tol))
+}
+
 func sameNumber(a, b Number) bool {
-	return a == b || (sameFloat(a.Real, b.Real) &&
+	return sameFloat(a.Real, b.Real) &&
 		sameFloat(a.Imag, b.Imag) &&
 		sameFloat(a.Jmag, b.Jmag) &&
-		sameFloat(a.Kmag, b.Kmag))
+		sameFloat(a.Kmag, b.Kmag)
 }
 
 func sameFloat(a, b float64) bool {
diff --git a/num/quat/trig.go b/num/quat/trig.go
index a3f0e26..7dbca92 100644
--- a/num/quat/trig.go
+++ b/num/quat/trig.go
@@ -31,7 +31,7 @@
 	v := Abs(uv)
 	s, c := math.Sincos(v)
 	sh, ch := sinhcosh(w)
-	return join(c*sh, Scale(s*ch/v, uv))
+	return join(c*sh, scale(s*ch/v, uv))
 }
 
 // Cos returns the cosine of q.
@@ -55,7 +55,7 @@
 	v := Abs(uv)
 	s, c := math.Sincos(v)
 	sh, ch := sinhcosh(w)
-	return join(c*ch, Scale(s*sh/v, uv))
+	return join(c*ch, scale(s*sh/v, uv))
 }
 
 // Tan returns the tangent of q.
@@ -69,6 +69,14 @@
 
 // Tanh returns the hyperbolic tangent of q.
 func Tanh(q Number) Number {
+	if math.IsInf(q.Real, 1) {
+		r := Number{Real: 1}
+		// Change signs dependent on imaginary parts.
+		r.Imag *= math.Sin(2 * q.Imag)
+		r.Jmag *= math.Sin(2 * q.Jmag)
+		r.Kmag *= math.Sin(2 * q.Kmag)
+		return r
+	}
 	d := Cosh(q)
 	if d == zero {
 		return Inf()
@@ -141,3 +149,23 @@
 	e *= 0.5
 	return e - ei, e + ei
 }
+
+// scale returns q scaled by f, except that inf×0 is 0.
+func scale(f float64, q Number) Number {
+	if f == 0 {
+		return Number{}
+	}
+	if q.Real != 0 {
+		q.Real *= f
+	}
+	if q.Imag != 0 {
+		q.Imag *= f
+	}
+	if q.Jmag != 0 {
+		q.Jmag *= f
+	}
+	if q.Kmag != 0 {
+		q.Kmag *= f
+	}
+	return q
+}
diff --git a/num/quat/trig_test.go b/num/quat/trig_test.go
index 4b7fed4..187f5b7 100644
--- a/num/quat/trig_test.go
+++ b/num/quat/trig_test.go
@@ -66,13 +66,18 @@
 		q := Asinh(Number{1, 1, 1, 1})
 		return Scale(0.5, Sub(Exp(q), Exp(Scale(-1, q))))
 	}()},
+	{q: Number{Real: math.Inf(1)}, want: Number{Real: math.Inf(1)}},
+	{q: Number{Real: math.Inf(1), Imag: math.Pi / 2}, want: Number{Real: math.Inf(1), Imag: math.Inf(1)}},
+	{q: Number{Real: math.Inf(1), Imag: math.Pi}, want: Number{Real: math.Inf(-1), Imag: math.Inf(1)}},
+	{q: Number{Real: math.Inf(1), Imag: 3 * math.Pi / 2}, want: Number{Real: math.Inf(-1), Imag: math.Inf(-1)}},
+	{q: Number{Real: math.Inf(1), Imag: 2 * math.Pi}, want: Number{Real: math.Inf(1), Imag: math.Inf(-1)}},
 }
 
 func TestSinh(t *testing.T) {
 	const tol = 1e-14
 	for _, test := range sinhTests {
 		got := Sinh(test.q)
-		if !equalApprox(got, test.want, tol) {
+		if !sameApprox(got, test.want, tol) {
 			t.Errorf("unexpected result for Sinh(%v): got:%v want:%v", test.q, got, test.want)
 		}
 	}
@@ -123,13 +128,18 @@
 		q := Number{1, 1, 1, 1}
 		return Scale(0.5, Add(Exp(q), Exp(Scale(-1, q))))
 	}()},
+	{q: Number{Real: math.Inf(1)}, want: Number{Real: math.Inf(1)}},
+	{q: Number{Real: math.Inf(1), Imag: math.Pi / 2}, want: Number{Real: math.Inf(1), Imag: math.Inf(1)}},
+	{q: Number{Real: math.Inf(1), Imag: math.Pi}, want: Number{Real: math.Inf(-1), Imag: math.Inf(1)}},
+	{q: Number{Real: math.Inf(1), Imag: 3 * math.Pi / 2}, want: Number{Real: math.Inf(-1), Imag: math.Inf(-1)}},
+	{q: Number{Real: math.Inf(1), Imag: 2 * math.Pi}, want: Number{Real: math.Inf(1), Imag: math.Inf(-1)}},
 }
 
 func TestCosh(t *testing.T) {
 	const tol = 1e-14
 	for _, test := range coshTests {
 		got := Cosh(test.q)
-		if !equalApprox(got, test.want, tol) {
+		if !sameApprox(got, test.want, tol) {
 			t.Errorf("unexpected result for Cosh(%v): got:%v want:%v", test.q, got, test.want)
 		}
 	}
@@ -171,13 +181,18 @@
 	{q: Number{Kmag: math.Pi / 4}, want: Number{Kmag: imag(cmplx.Tanh(complex(0, math.Pi/4)))}},
 	{q: Number{Imag: 1}, want: Mul(Sinh(Number{Imag: 1}), Inv(Cosh(Number{Imag: 1})))},
 	{q: Number{1, 1, 1, 1}, want: Mul(Sinh(Number{1, 1, 1, 1}), Inv(Cosh(Number{1, 1, 1, 1})))},
+	{q: Number{Real: math.Inf(1)}, want: Number{Real: 1}},
+	{q: Number{Real: math.Inf(1), Imag: math.Pi / 4}, want: Number{Real: 1, Imag: 0 * math.Sin(math.Pi/2)}},
+	{q: Number{Real: math.Inf(1), Imag: math.Pi / 2}, want: Number{Real: 1, Imag: 0 * math.Sin(math.Pi)}},
+	{q: Number{Real: math.Inf(1), Imag: 3 * math.Pi / 4}, want: Number{Real: 1, Imag: 0 * math.Sin(3*math.Pi/2)}},
+	{q: Number{Real: math.Inf(1), Imag: math.Pi}, want: Number{Real: 1, Imag: 0 * math.Sin(2*math.Pi)}},
 }
 
 func TestTanh(t *testing.T) {
 	const tol = 1e-14
 	for _, test := range tanhTests {
 		got := Tanh(test.q)
-		if !equalApprox(got, test.want, tol) {
+		if !sameApprox(got, test.want, tol) {
 			t.Errorf("unexpected result for Tanh(%v): got:%v want:%v", test.q, got, test.want)
 		}
 	}