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)
}
}