@@ -29,7 +29,6 @@ import RealModule
2929extension Quaternion /*: ElementaryFunctions */ {
3030
3131 // MARK: - exp-like functions
32-
3332 @inlinable
3433 public static func exp( _ q: Quaternion ) -> Quaternion {
3534 // Mathematically, this operation can be expanded in terms of the `Real`
@@ -45,21 +44,17 @@ extension Quaternion/*: ElementaryFunctions */ {
4544 // less than 1 for most inputs (i.e. `exp(r)` may be infinity when
4645 // `exp(r) cos(||v||)` would not be.
4746 guard q. isFinite else { return q }
48- let θ = q. imaginary. length
49- let axis = !θ . isZero ? ( q . imaginary / θ ) : . zero
47+ let ( â , θ ) = q. imaginary. unitAxisAndLength
48+ let rotation = Quaternion ( halfAngle : θ , unitAxis : â )
5049 // If real < log(greatestFiniteMagnitude), then exp(real) does not overflow.
5150 // To protect ourselves against sketchy log or exp implementations in
5251 // an unknown host library, or slight rounding disagreements between
5352 // the two, subtract one from the bound for a little safety margin.
5453 guard q. real < RealType . log ( . greatestFiniteMagnitude) - 1 else {
5554 let halfScale = RealType . exp ( q. real/ 2 )
56- let rotation = Quaternion ( halfAngle: θ, unitAxis: axis)
5755 return rotation. multiplied ( by: halfScale) . multiplied ( by: halfScale)
5856 }
59- return Quaternion (
60- halfAngle: θ,
61- unitAxis: axis
62- ) . multiplied ( by: . exp( q. real) )
57+ return rotation. multiplied ( by: . exp( q. real) )
6358 }
6459
6560 @inlinable
@@ -87,21 +82,20 @@ extension Quaternion/*: ElementaryFunctions */ {
8782 //
8883 // See `expMinusOne` on complex numbers for error bounds.
8984 guard q. isFinite else { return q }
90- let θ = q. imaginary. length
91- let axis = !θ. isZero ? ( q. imaginary / θ) : . zero
85+ let ( â, θ) = q. imaginary. unitAxisAndLength
9286 // If exp(q) is close to the overflow boundary, we don't need to
9387 // worry about the "MinusOne" part of this function; we're just
9488 // computing exp(q). (Even when θ is near a multiple of π/2,
9589 // it can't be close enough to overcome the scaling from exp(r),
9690 // so the -1 term is _always_ negligable).
9791 guard q. real < RealType . log ( . greatestFiniteMagnitude) - 1 else {
9892 let halfScale = RealType . exp ( q. real/ 2 )
99- let rotation = Quaternion ( halfAngle: θ, unitAxis: axis )
93+ let rotation = Quaternion ( halfAngle: θ, unitAxis: â )
10094 return rotation. multiplied ( by: halfScale) . multiplied ( by: halfScale)
10195 }
10296 return Quaternion (
10397 real: RealType . _mulAdd ( . cos( θ) , . expMinusOne( q. real) , . cosMinusOne( θ) ) ,
104- imaginary: axis * . exp( q. real) * . sin( θ)
98+ imaginary: â * . exp( q. real) * . sin( θ)
10599 )
106100 }
107101
@@ -120,23 +114,22 @@ extension Quaternion/*: ElementaryFunctions */ {
120114 // evaluation of the naive expression, so all we need to be careful
121115 // about is the behavior near the overflow boundary.
122116 //
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.
117+ // Fortunately, if |r | >= -log(ulpOfOne), cosh(r ) and sinh(r ) are
118+ // both just exp(|r |)/2, and we already know how to compute that.
125119 //
126120 // This function and sinh should stay in sync; if you make a
127121 // modification here, you should almost surely make a parallel
128122 // modification to sinh below.
129123 guard q. isFinite else { return q }
130- let θ = q. imaginary. length
131- let axis = !θ. isZero ? ( q. imaginary / θ) : . zero
124+ let ( â, θ) = q. imaginary. unitAxisAndLength
132125 guard q. real. magnitude < - RealType. log ( . ulpOfOne) else {
133- let rotation = Quaternion ( halfAngle: θ, unitAxis: axis )
126+ let rotation = Quaternion ( halfAngle: θ, unitAxis: â )
134127 let firstScale = RealType . exp ( q. real. magnitude/ 2 )
135128 return rotation. multiplied ( by: firstScale) . multiplied ( by: firstScale/ 2 )
136129 }
137130 return Quaternion (
138131 real: . cosh( q. real) * . cos( θ) ,
139- imaginary: axis * . sinh( q. real) * . sin( θ)
132+ imaginary: â * . sinh( q. real) * . sin( θ)
140133 )
141134 }
142135
@@ -150,17 +143,16 @@ extension Quaternion/*: ElementaryFunctions */ {
150143 // = sinh(r) cos(θ) + (v/θ) cosh(r) sin(θ)
151144 // ```
152145 guard q. isFinite else { return q }
153- let θ = q. imaginary. length
154- let axis = !θ. isZero ? ( q. imaginary / θ) : . zero
146+ let ( â, θ) = q. imaginary. unitAxisAndLength
155147 guard q. real. magnitude < - RealType. log ( . ulpOfOne) else {
156- let rotation = Quaternion ( halfAngle: θ, unitAxis: axis )
148+ let rotation = Quaternion ( halfAngle: θ, unitAxis: â )
157149 let firstScale = RealType . exp ( q. real. magnitude/ 2 )
158150 let secondScale = RealType ( signOf: q. real, magnitudeOf: firstScale/ 2 )
159151 return rotation. multiplied ( by: firstScale) . multiplied ( by: secondScale)
160152 }
161153 return Quaternion (
162154 real: . sinh( q. real) * . cos( θ) ,
163- imaginary: axis * . cosh( q. real) * . sin( θ)
155+ imaginary: â * . cosh( q. real) * . sin( θ)
164156 )
165157 }
166158
@@ -179,9 +171,9 @@ extension Quaternion/*: ElementaryFunctions */ {
179171 return Quaternion (
180172 real: RealType ( signOf: q. real, magnitudeOf: 1 ) ,
181173 imaginary:
182- RealType ( signOf: q. imaginary . x, magnitudeOf: 0 ) ,
183- RealType ( signOf: q. imaginary . y, magnitudeOf: 0 ) ,
184- RealType ( signOf: q. imaginary . z, magnitudeOf: 0 )
174+ RealType ( signOf: q. components . x, magnitudeOf: 0 ) ,
175+ RealType ( signOf: q. components . y, magnitudeOf: 0 ) ,
176+ RealType ( signOf: q. components . z, magnitudeOf: 0 )
185177 )
186178 }
187179 return sinh ( q) / cosh( q)
@@ -197,17 +189,16 @@ extension Quaternion/*: ElementaryFunctions */ {
197189 // = cos(r) cosh(θ) - (v/θ) sin(r) sinh(θ)
198190 // ```
199191 guard q. isFinite else { return q }
200- let θ = q. imaginary. length
201- let axis = !θ. isZero ? ( q. imaginary / θ) : . zero
192+ let ( â, θ) = q. imaginary. unitAxisAndLength
202193 guard θ. magnitude < - RealType. log ( . ulpOfOne) else {
203- let rotation = Quaternion ( halfAngle: q. real, unitAxis: axis )
194+ let rotation = Quaternion ( halfAngle: q. real, unitAxis: â )
204195 let firstScale = RealType . exp ( θ. magnitude/ 2 )
205196 let secondScale = firstScale/ 2
206197 return rotation. multiplied ( by: firstScale) . multiplied ( by: secondScale)
207198 }
208199 return Quaternion (
209200 real: . cosh( θ) * . cos( q. real) ,
210- imaginary: - axis * . sinh( θ) * . sin( q. real)
201+ imaginary: - â * . sinh( θ) * . sin( q. real)
211202 )
212203 }
213204
@@ -221,17 +212,16 @@ extension Quaternion/*: ElementaryFunctions */ {
221212 // = sin(r) cosh(θ) + (v/θ) cos(r) sinh(θ)
222213 // ```
223214 guard q. isFinite else { return q }
224- let θ = q. imaginary. length
225- let axis = !θ. isZero ? ( q. imaginary / θ) : . zero
215+ let ( â, θ) = q. imaginary. unitAxisAndLength
226216 guard θ. magnitude < - RealType. log ( . ulpOfOne) else {
227- let rotation = Quaternion ( halfAngle: q. real, unitAxis: axis )
217+ let rotation = Quaternion ( halfAngle: q. real, unitAxis: â )
228218 let firstScale = RealType . exp ( θ. magnitude/ 2 )
229219 let secondScale = RealType ( signOf: θ, magnitudeOf: firstScale/ 2 )
230220 return rotation. multiplied ( by: firstScale) . multiplied ( by: secondScale)
231221 }
232222 return Quaternion (
233223 real: . cosh( θ) * . sin( q. real) ,
234- imaginary: axis * . sinh( θ) * . cos( q. real)
224+ imaginary: â * . sinh( θ) * . cos( q. real)
235225 )
236226 }
237227
@@ -244,17 +234,17 @@ extension Quaternion/*: ElementaryFunctions */ {
244234 // tan(q) = sin(q) / cos(q)
245235 // ```
246236 guard q. isFinite else { return q }
247- let θ = q. imaginary. length
248237 // Note that when |θ| is larger than -log(.ulpOfOne),
249238 // sin(r + v) == ±cos(r + v), so tan(r + v) is just ±1.
250- guard θ. magnitude < - RealType. log ( . ulpOfOne) else {
239+ guard q. imaginary. length. magnitude < - RealType. log ( . ulpOfOne) else {
240+ let r = RealType ( signOf: q. components. w, magnitudeOf: 1 )
251241 return Quaternion (
252- real: RealType ( signOf : q . real , magnitudeOf : 1 ) ,
242+ real: r ,
253243 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 ) )
244+ RealType ( signOf: q. components . x, magnitudeOf: 0 ) ,
245+ RealType ( signOf: q. components . y, magnitudeOf: 0 ) ,
246+ RealType ( signOf: q. components . z, magnitudeOf: 0 )
247+ ) . multiplied ( by : r )
258248 }
259249 return sin ( q) / cos( q)
260250 }
@@ -276,9 +266,57 @@ extension Quaternion/*: ElementaryFunctions */ {
276266 return Quaternion ( real: . log( q. length) , imaginary: axis * q. halfAngle)
277267 }
278268
279-
280- //
281269 @inlinable
270+ public static func log( onePlus q: Quaternion ) -> Quaternion {
271+ // If either |r| or ||v||₁ is bounded away from the origin, we don't need
272+ // any extra precision, and can just literally compute log(1+z). Note
273+ // that this includes part of the sphere |1+q| = 1 where log(onePlus:)
274+ // vanishes (where r <= -0.5), but on this portion of the sphere 1+r
275+ // is always exact by Sterbenz' lemma, so as long as log( ) produces
276+ // a good result, log(1+q) will too.
277+ guard 2 * q. real. magnitude < 1 && q. imaginary. oneNorm < 1 else {
278+ return log ( . one + q)
279+ }
280+ // q is in (±0.5, ±1), so we need to evaluate more carefully.
281+ // The imaginary part is straightforward:
282+ let argument = ( . one + q) . halfAngle
283+ let ( â, _) = q. imaginary. unitAxisAndLength
284+ let imaginary = â * argument
285+ // For the real part, we _could_ use the same approach that we do for
286+ // log( ), but we'd need an extra-precise (1+r)², which can potentially
287+ // be quite painful to calculate. Instead, we can use an approach that
288+ // NevinBR suggested on the Swift forums for complex numbers:
289+ //
290+ // Re(log 1+q) = (log 1+q + log 1+q̅)/2
291+ // = log((1+q)(1+q̅)/2
292+ // = log(1 + q + q̅ + qq̅)/2
293+ // = log1p((2+r)r + x² + y² + z²)/2
294+ //
295+ // So now we need to evaluate (2+r)r + x² + y² + z² accurately. To do this,
296+ // we employ augmented arithmetic;
297+ // (2+r)r + x² + y² + z²
298+ // --↓--
299+ let rp2 = Augmented . fastTwoSum ( 2 , q. real) // Known that 2 > |r|
300+ var ( head, δ) = Augmented . twoProdFMA ( q. real, rp2. head)
301+ var tail = δ
302+ // head + x² + y² + z²
303+ // ----↓----
304+ let x ² = Augmented . twoProdFMA ( q. imaginary. x, q. imaginary. x)
305+ ( head, δ) = Augmented . twoSum ( head, x ². head)
306+ tail += ( δ + x ². tail)
307+ // head + y² + z²
308+ // ----↓----
309+ let y ² = Augmented . twoProdFMA ( q. imaginary. y, q. imaginary. y)
310+ ( head, δ) = Augmented . twoSum ( head, y ². head)
311+ tail += ( δ + y ². tail)
312+ // head + z²
313+ // ----↓----
314+ let z ² = Augmented . twoProdFMA ( q. imaginary. z, q. imaginary. z)
315+ ( head, δ) = Augmented . twoSum ( head, z ². head)
316+ tail += ( δ + z ². tail)
317+
318+ let s = ( head + tail) . addingProduct ( q. real, rp2. tail)
319+ return Quaternion ( real: . log( onePlus: s) / 2 , imaginary: imaginary)
282320 }
283321
284322 //
@@ -341,3 +379,34 @@ extension Quaternion/*: ElementaryFunctions */ {
341379 return exp ( log ( q) . divided ( by: RealType ( n) ) )
342380 }
343381}
382+
383+ extension SIMD3 where Scalar: Real {
384+
385+ /// Returns the normalized axis and the length of this vector.
386+ @usableFromInline @inline ( __always)
387+ internal var unitAxisAndLength : ( Self , Scalar ) {
388+ if self == . zero {
389+ return ( SIMD3 (
390+ Scalar ( signOf: x, magnitudeOf: 0 ) ,
391+ Scalar ( signOf: y, magnitudeOf: 0 ) ,
392+ Scalar ( signOf: z, magnitudeOf: 0 )
393+ ) , . zero)
394+ }
395+ return ( self / length, length)
396+ }
397+ }
398+
399+ extension Augmented {
400+
401+ // TODO: Move to Augmented.swift
402+ @usableFromInline @_transparent
403+ internal static func twoSum< T: Real > ( _ a: T , _ b: T ) -> ( head: T , tail: T ) {
404+ let head = a + b
405+ let x = head - b
406+ let y = head - x
407+ let ax = a - x
408+ let by = b - y
409+ let tail = ax + by
410+ return ( head, tail)
411+ }
412+ }
0 commit comments