@@ -30,36 +30,36 @@ extension Quaternion/*: ElementaryFunctions */ {
30
30
// Note that naive evaluation of this expression in floating-point would be
31
31
// prone to premature overflow, since `cos` and `sin` both have magnitude
32
32
// less than 1 for most inputs (i.e. `exp(r)` may be infinity when
33
- // `exp(r) cos(θ )` would not be).
33
+ // `exp(r) cos(arg )` would not be).
34
34
@inlinable
35
35
public static func exp( _ q: Quaternion ) -> Quaternion {
36
36
guard q. isFinite else { return q }
37
37
// For real quaternions we can skip phase and axis calculations
38
38
// TODO: Replace q.imaginary == .zero with `q.isReal`
39
- let θ = q. imaginary == . zero ? . zero : q. imaginary. length // θ = ||v||
40
- let n̂ = q. imaginary == . zero ? . zero : ( q. imaginary / θ ) // n̂ = v / ||v||
39
+ let argument = q. imaginary == . zero ? . zero : q. imaginary. length
40
+ let axis = q. imaginary == . zero ? . zero : ( q. imaginary / argument )
41
41
// If real < log(greatestFiniteMagnitude), then exp(q.real) does not overflow.
42
42
// To protect ourselves against sketchy log or exp implementations in
43
43
// an unknown host library, or slight rounding disagreements between
44
44
// the two, subtract one from the bound for a little safety margin.
45
45
guard q. real < RealType . log ( . greatestFiniteMagnitude) - 1 else {
46
46
let halfScale = RealType . exp ( q. real/ 2 )
47
- let rotation = Quaternion ( halfAngle: θ , unitAxis: n̂ )
47
+ let rotation = Quaternion ( halfAngle: argument , unitAxis: axis )
48
48
return rotation. multiplied ( by: halfScale) . multiplied ( by: halfScale)
49
49
}
50
- return Quaternion ( halfAngle: θ , unitAxis: n̂ ) . multiplied ( by: . exp( q. real) )
50
+ return Quaternion ( halfAngle: argument , unitAxis: axis ) . multiplied ( by: . exp( q. real) )
51
51
}
52
52
53
53
@inlinable
54
54
public static func expMinusOne( _ q: Quaternion ) -> Quaternion {
55
- // Note that the imaginary part is just the usual exp(r) sin(θ );
55
+ // Note that the imaginary part is just the usual exp(r) sin(argument );
56
56
// the only trick is computing the real part, which allows us to borrow
57
57
// the derivative of real part for this function from complex numbers.
58
58
// See `expMinusOne` in the ComplexModule for implementation details.
59
59
guard q. isFinite else { return q }
60
60
// TODO: Replace q.imaginary == .zero with `q.isReal`
61
- let θ = q. imaginary == . zero ? . zero : q. imaginary. length // θ = ||v||
62
- let n̂ = q. imaginary == . zero ? . zero : ( q. imaginary / θ ) // n̂ = v / ||v||
61
+ let argument = q. imaginary == . zero ? . zero : q. imaginary. length
62
+ let axis = q. imaginary == . zero ? . zero : ( q. imaginary / argument )
63
63
// If exp(q) is close to the overflow boundary, we don't need to
64
64
// worry about the "MinusOne" part of this function; we're just
65
65
// computing exp(q). (Even when q.y is near a multiple of π/2,
@@ -68,12 +68,12 @@ extension Quaternion/*: ElementaryFunctions */ {
68
68
// these cases exactly the same as exp(q).
69
69
guard q. real < RealType . log ( . greatestFiniteMagnitude) - 1 else {
70
70
let halfScale = RealType . exp ( q. real/ 2 )
71
- let rotation = Quaternion ( halfAngle: θ , unitAxis: n̂ )
71
+ let rotation = Quaternion ( halfAngle: argument , unitAxis: axis )
72
72
return rotation. multiplied ( by: halfScale) . multiplied ( by: halfScale)
73
73
}
74
74
return Quaternion (
75
- real: RealType . _mulAdd ( . cos( θ ) , . expMinusOne( q. real) , . cosMinusOne( θ ) ) ,
76
- imaginary: n̂ * . exp( q. real) * . sin( θ )
75
+ real: RealType . _mulAdd ( . cos( argument ) , . expMinusOne( q. real) , . cosMinusOne( argument ) ) ,
76
+ imaginary: axis * . exp( q. real) * . sin( argument )
77
77
)
78
78
}
79
79
@@ -85,18 +85,9 @@ extension Quaternion/*: ElementaryFunctions */ {
85
85
public static func cosh( _ q: Quaternion ) -> Quaternion {
86
86
guard q. isFinite else { return q }
87
87
// TODO: Replace q.imaginary == .zero with `q.isReal`
88
- let θ = q. imaginary == . zero ? . zero : q. imaginary. length // θ = ||v||
89
- let n̂ = q. imaginary == . zero ? . zero : ( q. imaginary / θ) // n̂ = v / ||v||
90
- guard q. real. magnitude < - RealType. log ( . ulpOfOne) else {
91
- let rotation = Quaternion ( halfAngle: θ, unitAxis: n̂)
92
- let firstScale = RealType . exp ( q. real. magnitude/ 2 )
93
- let secondScale = firstScale/ 2
94
- return rotation. multiplied ( by: firstScale) . multiplied ( by: secondScale)
95
- }
96
- return Quaternion (
97
- real: . cosh( q. real) * . cos( θ) ,
98
- imaginary: n̂ * . sinh( q. real) * . sin( θ)
99
- )
88
+ let argument = q. imaginary == . zero ? . zero : q. imaginary. length
89
+ let axis = q. imaginary == . zero ? . zero : ( q. imaginary / argument)
90
+ return cosh ( q. real, argument, axis: axis)
100
91
}
101
92
102
93
// sinh(r + xi + yj + zk) = sinh(r + v)
@@ -107,17 +98,17 @@ extension Quaternion/*: ElementaryFunctions */ {
107
98
public static func sinh( _ q: Quaternion ) -> Quaternion {
108
99
guard q. isFinite else { return q }
109
100
// TODO: Replace q.imaginary == .zero with `q.isReal`
110
- let θ = q. imaginary == . zero ? . zero : q. imaginary. length // θ = ||v||
111
- let n̂ = q. imaginary == . zero ? . zero : ( q. imaginary / θ ) // n̂ = v / ||v||
101
+ let argument = q. imaginary == . zero ? . zero : q. imaginary. length
102
+ let axis = q. imaginary == . zero ? . zero : ( q. imaginary / argument )
112
103
guard q. real. magnitude < - RealType. log ( . ulpOfOne) else {
113
- let rotation = Quaternion ( halfAngle: θ , unitAxis: n̂ )
104
+ let rotation = Quaternion ( halfAngle: argument , unitAxis: axis )
114
105
let firstScale = RealType . exp ( q. real. magnitude/ 2 )
115
106
let secondScale = RealType ( signOf: q. real, magnitudeOf: firstScale/ 2 )
116
107
return rotation. multiplied ( by: firstScale) . multiplied ( by: secondScale)
117
108
}
118
109
return Quaternion (
119
- real: . sinh( q. real) * . cos( θ ) ,
120
- imaginary: n̂ * . cosh( q. real) * . sin( θ )
110
+ real: . sinh( q. real) * . cos( argument ) ,
111
+ imaginary: axis * . cosh( q. real) * . sin( argument )
121
112
)
122
113
}
123
114
@@ -141,5 +132,75 @@ extension Quaternion/*: ElementaryFunctions */ {
141
132
return sinh ( q) / cosh( q)
142
133
}
143
134
135
+ // cos(r + xi + yj + zk) = cos(r + v)
136
+ // cos(r + v) = cos(r) cosh(||v||) - (v/||v||) sin(r) sinh(||v||).
137
+ //
138
+ // See cosh for algorithm details.
139
+ @inlinable
140
+ public static func cos( _ q: Quaternion ) -> Quaternion {
141
+ guard q. isFinite else { return q }
142
+ // TODO: Replace q.imaginary == .zero with `q.isReal`
143
+ let argument = q. imaginary == . zero ? . zero : q. imaginary. length
144
+ let axis = q. imaginary == . zero ? . zero : ( q. imaginary / argument)
145
+ return cosh ( - argument, q. real, axis: axis)
146
+ }
147
+
148
+ // See sinh on complex numbers for algorithm details.
149
+ @inlinable
150
+ public static func sin( _ q: Quaternion ) -> Quaternion {
151
+ guard q. isFinite else { return q }
152
+ // TODO: Replace q.imaginary == .zero with `q.isReal`
153
+ let argument = q. imaginary == . zero ? . zero : q. imaginary. length
154
+ let axis = q. imaginary == . zero ? . zero : ( q. imaginary / argument)
155
+ let ( x, y) = sinh ( - argument, q. real)
156
+ return Quaternion ( real: y, imaginary: axis * - x)
157
+ }
158
+
159
+ // tan(q) = sin(q) / cos(q)
160
+ //
161
+ // See tanh for algorithm details.
162
+ @inlinable
163
+ public static func tan( _ q: Quaternion ) -> Quaternion {
164
+ return sin ( q) / cos( q)
165
+ }
166
+ }
167
+ }
168
+
169
+ // MARK: - Hyperbolic trigonometric function helper
170
+ extension Quaternion {
171
+
172
+ // See cosh of complex numbers for algorithm details.
173
+ @usableFromInline @_transparent
174
+ internal static func cosh(
175
+ _ x: RealType ,
176
+ _ y: RealType ,
177
+ axis: SIMD3 < RealType >
178
+ ) -> Quaternion {
179
+ guard x. magnitude < - RealType. log ( . ulpOfOne) else {
180
+ let rotation = Quaternion ( halfAngle: y, unitAxis: axis)
181
+ let firstScale = RealType . exp ( x. magnitude/ 2 )
182
+ let secondScale = firstScale/ 2
183
+ return rotation. multiplied ( by: firstScale) . multiplied ( by: secondScale)
184
+ }
185
+ return Quaternion (
186
+ real: . cosh( x) * . cos( y) ,
187
+ imaginary: axis * . sinh( x) * . sin( y)
188
+ )
189
+ }
190
+
191
+ // See sinh of complex numbers for algorithm details.
192
+ @usableFromInline @_transparent
193
+ internal static func sinh(
194
+ _ x: RealType ,
195
+ _ y: RealType
196
+ ) -> ( RealType , RealType ) {
197
+ guard x. magnitude < - RealType. log ( . ulpOfOne) else {
198
+ var ( x, y) = ( RealType . cos ( y) , RealType . sin ( y) )
199
+ let firstScale = RealType . exp ( x. magnitude/ 2 )
200
+ ( x, y) = ( x * firstScale, y * firstScale)
201
+ let secondScale = RealType ( signOf: x, magnitudeOf: firstScale/ 2 )
202
+ return ( x * secondScale, y * secondScale)
203
+ }
204
+ return ( . sinh( x) * . cos( y) , . cosh( x) * . sin( y) )
144
205
}
145
206
}
0 commit comments