10
10
//
11
11
//===----------------------------------------------------------------------===//
12
12
13
+ // (r + xi + yj + zk) is a common representation that is often seen for
14
+ // quaternions. However, when we want to expand the elementary functions of
15
+ // quaternions in terms of real operations it is almost always easier to view
16
+ // them as real part (r) and imaginary vector part (v),
17
+ // i.e: r + xi + yj + zk = r + v; and so we diverge a little from the
18
+ // representation that is used in the documentation in other files and use this
19
+ // notation of quaternions in the comments of the following functions.
20
+ //
21
+ // Quaternionic elementary functions have many similarities with elementary
22
+ // functions of complex numbers and their definition in terms of real
23
+ // operations. Therefore, if you make a modification to one of the following
24
+ // functions, you should almost surely make a parallel modification to the same
25
+ // elementary function of complex numbers.
26
+
13
27
import RealModule
14
28
15
- // As the following elementary functions algorithms are adaptations of the
16
- // elementary functions of complex numbers: If you make a modification to either
17
- // of the following functions, you should almost surely make a parallel
18
- // modification to the same elementary function of complex numbers (See
19
- // ElementaryFunctions.swift in ComplexModule).
20
29
extension Quaternion /*: ElementaryFunctions */ {
21
30
22
31
// MARK: - exp-like functions
23
32
24
- // exp(r + xi + yj + zk) = exp(r + v) = exp(r) exp(v)
25
- // = exp(r) (cos(||v||) + (v/||v||) sin(||v||))
26
- //
27
- // See exp on complex numbers for algorithm details.
28
33
@inlinable
29
34
public static func exp( _ q: Quaternion ) -> Quaternion {
35
+ // Mathematically, this operation can be expanded in terms of the `Real`
36
+ // operations `exp`, `cos` and `sin` as follows (`let θ = ||v||`):
37
+ //
38
+ // ```
39
+ // exp(r + v) = exp(r) exp(v)
40
+ // = exp(r) (cos(θ) + (v/θ) sin(θ))
41
+ // ```
42
+ //
43
+ // Note that naive evaluation of this expression in floating-point would be
44
+ // prone to premature overflow, since `cos` and `sin` both have magnitude
45
+ // less than 1 for most inputs (i.e. `exp(r)` may be infinity when
46
+ // `exp(r) cos(||v||)` would not be.
30
47
guard q. isFinite else { return q }
31
- // For real quaternions we can skip phase and axis calculations
32
- let argument = q. isReal ? . zero : q. imaginary. length
33
- let axis = q. isReal ? . zero : ( q. imaginary / argument)
34
- // If real < log(greatestFiniteMagnitude), then exp(q.real) does not overflow.
48
+ let θ = q. imaginary. length
49
+ let axis = !θ. isZero ? ( q. imaginary / θ) : . zero
50
+ // If real < log(greatestFiniteMagnitude), then exp(real) does not overflow.
35
51
// To protect ourselves against sketchy log or exp implementations in
36
52
// an unknown host library, or slight rounding disagreements between
37
53
// the two, subtract one from the bound for a little safety margin.
38
54
guard q. real < RealType . log ( . greatestFiniteMagnitude) - 1 else {
39
55
let halfScale = RealType . exp ( q. real/ 2 )
40
- let rotation = Quaternion ( halfAngle: argument , unitAxis: axis)
56
+ let rotation = Quaternion ( halfAngle: θ , unitAxis: axis)
41
57
return rotation. multiplied ( by: halfScale) . multiplied ( by: halfScale)
42
58
}
43
- return Quaternion ( halfAngle: argument, unitAxis: axis) . multiplied ( by: . exp( q. real) )
59
+ return Quaternion (
60
+ halfAngle: θ,
61
+ unitAxis: axis
62
+ ) . multiplied ( by: . exp( q. real) )
44
63
}
45
64
46
65
@inlinable
47
66
public static func expMinusOne( _ q: Quaternion ) -> Quaternion {
48
- // Note that the imaginary part is just the usual exp(r) sin(argument);
49
- // the only trick is computing the real part, which allows us to borrow
50
- // the derivative of real part for this function from complex numbers.
51
- // See `expMinusOne` in the ComplexModule for implementation details.
67
+ // Mathematically, this operation can be expanded in terms of the `Real`
68
+ // operations `exp`, `cos` and `sin` as follows (`let θ = ||v||`):
69
+ //
70
+ // ```
71
+ // exp(r + v) - 1 = exp(r) exp(v) - 1
72
+ // = exp(r) (cos(θ) + (v/θ) sin(θ)) - 1
73
+ // = exp(r) cos(θ) + exp(r) (v/θ) sin(θ) - 1
74
+ // = (exp(r) cos(θ) - 1) + exp(r) (v/θ) sin(θ)
75
+ // -------- u --------
76
+ // ```
77
+ //
78
+ // Note that the imaginary part is just the usual exp(x) sin(y);
79
+ // the only trick is computing the real part ("u"):
80
+ //
81
+ // ```
82
+ // u = exp(r) cos(θ) - 1
83
+ // = exp(r) cos(θ) - cos(θ) + cos(θ) - 1
84
+ // = (exp(r) - 1) cos(θ) + (cos(θ) - 1)
85
+ // = expMinusOne(r) cos(θ) + cosMinusOne(θ)
86
+ // ```
87
+ //
88
+ // See `expMinusOne` on complex numbers for error bounds.
52
89
guard q. isFinite else { return q }
53
- let argument = q . isReal ? . zero : q. imaginary. length
54
- let axis = q . isReal ? . zero : ( q. imaginary / argument )
90
+ let θ = q. imaginary. length
91
+ let axis = !θ . isZero ? ( q. imaginary / θ ) : . zero
55
92
// If exp(q) is close to the overflow boundary, we don't need to
56
93
// worry about the "MinusOne" part of this function; we're just
57
- // computing exp(q). (Even when q.y is near a multiple of π/2,
58
- // it can't be close enough to overcome the scaling from exp(q.x),
59
- // so the -1 term is _always_ negligable). So we simply handle
60
- // these cases exactly the same as exp(q).
94
+ // computing exp(q). (Even when θ is near a multiple of π/2,
95
+ // it can't be close enough to overcome the scaling from exp(r),
96
+ // so the -1 term is _always_ negligable).
61
97
guard q. real < RealType . log ( . greatestFiniteMagnitude) - 1 else {
62
98
let halfScale = RealType . exp ( q. real/ 2 )
63
- let rotation = Quaternion ( halfAngle: argument , unitAxis: axis)
99
+ let rotation = Quaternion ( halfAngle: θ , unitAxis: axis)
64
100
return rotation. multiplied ( by: halfScale) . multiplied ( by: halfScale)
65
101
}
66
102
return Quaternion (
67
- real: RealType . _mulAdd ( . cos( argument ) , . expMinusOne( q. real) , . cosMinusOne( argument ) ) ,
68
- imaginary: axis * . exp( q. real) * . sin( argument )
103
+ real: RealType . _mulAdd ( . cos( θ ) , . expMinusOne( q. real) , . cosMinusOne( θ ) ) ,
104
+ imaginary: axis * . exp( q. real) * . sin( θ )
69
105
)
70
106
}
71
107
72
- // cosh(r + xi + yj + zk) = cosh(r + v)
73
- // = cosh(r) cos(||v||) + (v/||v||) sinh(r) sin(||v||)
74
- //
75
- // See cosh on complex numbers for algorithm details.
76
108
@inlinable
77
109
public static func cosh( _ q: Quaternion ) -> Quaternion {
110
+ // Mathematically, this operation can be expanded in terms of
111
+ // trigonometric `Real` operations as follows (`let θ = ||v||`):
112
+ //
113
+ // ```
114
+ // cosh(q) = (exp(q) + exp(-q)) / 2
115
+ // = cosh(r) cos(θ) + (v/θ) sinh(r) sin(θ)
116
+ // ```
117
+ //
118
+ // Like exp, cosh is entire, so we do not need to worry about where
119
+ // branch cuts fall. Also like exp, cancellation never occurs in the
120
+ // evaluation of the naive expression, so all we need to be careful
121
+ // about is the behavior near the overflow boundary.
122
+ //
123
+ // Fortunately, if |x| >= -log(ulpOfOne), cosh(x) and sinh(x) are
124
+ // both just exp(|x|)/2, and we already know how to compute that.
125
+ //
126
+ // This function and sinh should stay in sync; if you make a
127
+ // modification here, you should almost surely make a parallel
128
+ // modification to sinh below.
78
129
guard q. isFinite else { return q }
79
- let argument = q. isReal ? . zero : q. imaginary. length
80
- let axis = q. isReal ? . zero : ( q. imaginary / argument)
81
- return cosh ( q. real, argument, axis: axis)
130
+ let θ = q. imaginary. length
131
+ let axis = !θ. isZero ? ( q. imaginary / θ) : . zero
132
+ guard q. real. magnitude < - RealType. log ( . ulpOfOne) else {
133
+ let rotation = Quaternion ( halfAngle: θ, unitAxis: axis)
134
+ let firstScale = RealType . exp ( q. real. magnitude/ 2 )
135
+ return rotation. multiplied ( by: firstScale) . multiplied ( by: firstScale/ 2 )
136
+ }
137
+ return Quaternion (
138
+ real: . cosh( q. real) * . cos( θ) ,
139
+ imaginary: axis * . sinh( q. real) * . sin( θ)
140
+ )
82
141
}
83
142
84
- // sinh(r + xi + yj + zk) = sinh(r + v)
85
- // = sinh(r) cos(||v||) + (v/||v||) cosh(r) sin(||v||)
86
- //
87
- // See sinh on complex numbers for algorithm details.
88
143
@inlinable
89
144
public static func sinh( _ q: Quaternion ) -> Quaternion {
145
+ // Mathematically, this operation can be expanded in terms of
146
+ // trigonometric `Real` operations as follows (`let θ = ||v||`):
147
+ //
148
+ // ```
149
+ // sinh(q) = (exp(q) - exp(-q)) / 2
150
+ // = sinh(r) cos(θ) + (v/θ) cosh(r) sin(θ)
151
+ // ```
90
152
guard q. isFinite else { return q }
91
- let argument = q . isReal ? . zero : q. imaginary. length
92
- let axis = q . isReal ? . zero : ( q. imaginary / argument )
153
+ let θ = q. imaginary. length
154
+ let axis = !θ . isZero ? ( q. imaginary / θ ) : . zero
93
155
guard q. real. magnitude < - RealType. log ( . ulpOfOne) else {
94
- let rotation = Quaternion ( halfAngle: argument , unitAxis: axis)
156
+ let rotation = Quaternion ( halfAngle: θ , unitAxis: axis)
95
157
let firstScale = RealType . exp ( q. real. magnitude/ 2 )
96
158
let secondScale = RealType ( signOf: q. real, magnitudeOf: firstScale/ 2 )
97
159
return rotation. multiplied ( by: firstScale) . multiplied ( by: secondScale)
98
160
}
99
161
return Quaternion (
100
- real: . sinh( q. real) * . cos( argument ) ,
101
- imaginary: axis * . cosh( q. real) * . sin( argument )
162
+ real: . sinh( q. real) * . cos( θ ) ,
163
+ imaginary: axis * . cosh( q. real) * . sin( θ )
102
164
)
103
165
}
104
166
105
- // tanh(q) = sinh(q) / cosh(q)
106
- //
107
- // See tanh on complex numbers for algorithm details.
108
167
@inlinable
109
168
public static func tanh( _ q: Quaternion ) -> Quaternion {
169
+ // Mathematically, this operation can be expanded in terms of
170
+ // trigonometric `Real` operations as follows (`let θ = ||v||`):
171
+ //
172
+ // ```
173
+ // tanh(q) = sinh(q) / cosh(q)
174
+ // ```
110
175
guard q. isFinite else { return q }
111
176
// Note that when |r| is larger than -log(.ulpOfOne),
112
177
// sinh(r + v) == ±cosh(r + v), so tanh(r + v) is just ±1.
@@ -122,36 +187,75 @@ extension Quaternion/*: ElementaryFunctions */ {
122
187
return sinh ( q) / cosh( q)
123
188
}
124
189
125
- // cos(r + xi + yj + zk) = cos(r + v)
126
- // = cos(r) cosh(||v||) - (v/||v||) sin(r) sinh(||v||)
127
- //
128
- // See cosh for algorithm details.
129
190
@inlinable
130
191
public static func cos( _ q: Quaternion ) -> Quaternion {
192
+ // Mathematically, this operation can be expanded in terms of
193
+ // trigonometric `Real` operations as follows (`let θ = ||v||`):
194
+ //
195
+ // ```
196
+ // cos(r + v) = (exp(q * (v/θ)) + exp(-q * (v/θ))) / 2
197
+ // = cos(r) cosh(θ) - (v/θ) sin(r) sinh(θ)
198
+ // ```
131
199
guard q. isFinite else { return q }
132
- let argument = q. isReal ? . zero : q. imaginary. length
133
- let axis = q. isReal ? . zero : ( q. imaginary / argument)
134
- return cosh ( - argument, q. real, axis: axis)
200
+ let θ = q. imaginary. length
201
+ let axis = !θ. isZero ? ( q. imaginary / θ) : . zero
202
+ guard θ. magnitude < - RealType. log ( . ulpOfOne) else {
203
+ let rotation = Quaternion ( halfAngle: q. real, unitAxis: axis)
204
+ let firstScale = RealType . exp ( θ. magnitude/ 2 )
205
+ let secondScale = firstScale/ 2
206
+ return rotation. multiplied ( by: firstScale) . multiplied ( by: secondScale)
207
+ }
208
+ return Quaternion (
209
+ real: . cosh( θ) * . cos( q. real) ,
210
+ imaginary: - axis * . sinh( θ) * . sin( q. real)
211
+ )
135
212
}
136
213
137
- // sin(r + xi + yj + zk) = sin(r + v)
138
- // = sin(r) cosh(-||v||) - (v/||v||) cos(r) sinh(-||v||)
139
- //
140
- // See sinh for algorithm details.
141
214
@inlinable
142
215
public static func sin( _ q: Quaternion ) -> Quaternion {
216
+ // Mathematically, this operation can be expanded in terms of
217
+ // trigonometric `Real` operations as follows (`let θ = ||v||`):
218
+ //
219
+ // ```
220
+ // sin(r + v) = -((exp(q * (v/θ)) - exp(-q * (v/θ))) (v/θ * 2)
221
+ // = sin(r) cosh(θ) + (v/θ) cos(r) sinh(θ)
222
+ // ```
143
223
guard q. isFinite else { return q }
144
- let argument = q. isReal ? . zero : q. imaginary. length
145
- let axis = q. isReal ? . zero : ( q. imaginary / argument)
146
- let ( x, y) = sinh ( - argument, q. real)
147
- return Quaternion ( real: y, imaginary: axis * - x)
224
+ let θ = q. imaginary. length
225
+ let axis = !θ. isZero ? ( q. imaginary / θ) : . zero
226
+ guard θ. magnitude < - RealType. log ( . ulpOfOne) else {
227
+ let rotation = Quaternion ( halfAngle: q. real, unitAxis: axis)
228
+ let firstScale = RealType . exp ( θ. magnitude/ 2 )
229
+ let secondScale = RealType ( signOf: θ, magnitudeOf: firstScale/ 2 )
230
+ return rotation. multiplied ( by: firstScale) . multiplied ( by: secondScale)
231
+ }
232
+ return Quaternion (
233
+ real: . cosh( θ) * . sin( q. real) ,
234
+ imaginary: axis * . sinh( θ) * . cos( q. real)
235
+ )
148
236
}
149
237
150
- // tan(q) = sin(q) / cos(q)
151
- //
152
- // See tanh for algorithm details.
153
238
@inlinable
154
239
public static func tan( _ q: Quaternion ) -> Quaternion {
240
+ // Mathematically, this operation can be expanded in terms of
241
+ // trigonometric `Real` operations as follows (`let θ = ||v||`):
242
+ //
243
+ // ```
244
+ // tan(q) = sin(q) / cos(q)
245
+ // ```
246
+ guard q. isFinite else { return q }
247
+ let θ = q. imaginary. length
248
+ // Note that when |θ| is larger than -log(.ulpOfOne),
249
+ // sin(r + v) == ±cos(r + v), so tan(r + v) is just ±1.
250
+ guard θ. magnitude < - RealType. log ( . ulpOfOne) else {
251
+ return Quaternion (
252
+ real: RealType ( signOf: q. real, magnitudeOf: 1 ) ,
253
+ imaginary:
254
+ RealType ( signOf: q. imaginary. x, magnitudeOf: 0 ) ,
255
+ RealType ( signOf: q. imaginary. y, magnitudeOf: 0 ) ,
256
+ RealType ( signOf: q. imaginary. z, magnitudeOf: 0 )
257
+ ) * Quaternion( RealType ( signOf: q. real, magnitudeOf: 1 ) )
258
+ }
155
259
return sin ( q) / cos( q)
156
260
}
157
261
@@ -162,14 +266,14 @@ extension Quaternion/*: ElementaryFunctions */ {
162
266
// the single exceptional value.
163
267
guard q. isFinite && !q. isZero else { return . infinity }
164
268
165
- let vectorLength = q. imaginary. length
166
- let scale = q. halfAngle / vectorLength
269
+ let argument = q. imaginary. length
270
+ let axis = q. imaginary / argument
167
271
168
272
// We deliberatly choose log(length) over the (faster)
169
273
// log(lengthSquared) / 2 which is used for complex numbers; as
170
274
// the squared length of quaternions is more prone to overflows than the
171
275
// squared length of complex numbers.
172
- return Quaternion ( real: . log( q. length) , imaginary: q . imaginary * scale )
276
+ return Quaternion ( real: . log( q. length) , imaginary: axis * q . halfAngle )
173
277
}
174
278
175
279
// MARK: - pow-like functions
@@ -206,42 +310,3 @@ extension Quaternion/*: ElementaryFunctions */ {
206
310
return exp ( log ( q) . divided ( by: RealType ( n) ) )
207
311
}
208
312
}
209
-
210
- // MARK: - Hyperbolic trigonometric function helper
211
- extension Quaternion {
212
-
213
- // See cosh of complex numbers for algorithm details.
214
- @usableFromInline @_transparent
215
- internal static func cosh(
216
- _ x: RealType ,
217
- _ y: RealType ,
218
- axis: SIMD3 < RealType >
219
- ) -> Quaternion {
220
- guard x. magnitude < - RealType. log ( . ulpOfOne) else {
221
- let rotation = Quaternion ( halfAngle: y, unitAxis: axis)
222
- let firstScale = RealType . exp ( x. magnitude/ 2 )
223
- let secondScale = firstScale/ 2
224
- return rotation. multiplied ( by: firstScale) . multiplied ( by: secondScale)
225
- }
226
- return Quaternion (
227
- real: . cosh( x) * . cos( y) ,
228
- imaginary: axis * . sinh( x) * . sin( y)
229
- )
230
- }
231
-
232
- // See sinh of complex numbers for algorithm details.
233
- @usableFromInline @_transparent
234
- internal static func sinh(
235
- _ x: RealType ,
236
- _ y: RealType
237
- ) -> ( RealType , RealType ) {
238
- guard x. magnitude < - RealType. log ( . ulpOfOne) else {
239
- var ( x, y) = ( RealType . cos ( y) , RealType . sin ( y) )
240
- let firstScale = RealType . exp ( x. magnitude/ 2 )
241
- ( x, y) = ( x * firstScale, y * firstScale)
242
- let secondScale = RealType ( signOf: x, magnitudeOf: firstScale/ 2 )
243
- return ( x * secondScale, y * secondScale)
244
- }
245
- return ( . sinh( x) * . cos( y) , . cosh( x) * . sin( y) )
246
- }
247
- }
0 commit comments