@@ -58,10 +58,14 @@ extension Quaternion {
5858 /// [wiki]: https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation#Recovering_the_axis-angle_representation
5959 @inlinable
6060 public var axis : SIMD3 < RealType > {
61- guard isFinite, imaginary != . zero, real != . zero else {
62- return SIMD3 ( repeating: . nan)
63- }
64- return imaginary / . sqrt( imaginary. lengthSquared)
61+ guard isFinite, imaginary != . zero else { return SIMD3 ( repeating: . nan) }
62+
63+ // If lengthSquared computes without over/underflow, everything is fine
64+ // and the result is correct. If not, we have to do the computation
65+ // carefully and unscale the quaternion first.
66+ let lenSq = imaginary. lengthSquared
67+ guard lenSq. isNormal else { return divided ( by: magnitude) . axis }
68+ return imaginary / . sqrt( lenSq)
6569 }
6670
6771 /// The [Angle-Axis][wiki] representation.
@@ -267,7 +271,7 @@ extension Quaternion {
267271 @inlinable
268272 public init ( rotation vector: SIMD3 < RealType > ) {
269273 let angle : RealType = . sqrt( vector. lengthSquared)
270- if !angle. isZero && angle. isFinite {
274+ if !angle. isZero, angle. isFinite {
271275 self = Quaternion ( halfAngle: angle/ 2 , unitAxis: vector/ angle)
272276 } else {
273277 self = Quaternion ( angle)
@@ -404,8 +408,19 @@ extension Quaternion {
404408 /// If the quaternion is zero or non-finite, halfAngle is `nan`.
405409 @usableFromInline @inline ( __always)
406410 internal var halfAngle : RealType {
407- guard !isZero && isFinite else { return . nan }
408- return . atan2( y: . sqrt( imaginary. lengthSquared) , x: real)
411+ guard isFinite else { return . nan }
412+ guard imaginary != . zero else {
413+ // A zero quaternion does not encode transformation properties.
414+ // If imaginary is zero, real must be non-zero or nan is returned.
415+ return real. isZero ? . nan : . zero
416+ }
417+
418+ // If lengthSquared computes without over/underflow, everything is fine
419+ // and the result is correct. If not, we have to do the computation
420+ // carefully and unscale the quaternion first.
421+ let lenSq = imaginary. lengthSquared
422+ guard lenSq. isNormal else { return divided ( by: magnitude) . halfAngle }
423+ return . atan2( y: . sqrt( lenSq) , x: real)
409424 }
410425
411426 /// Creates a new quaternion from given half rotation angle about given
@@ -430,18 +445,18 @@ extension Quaternion {
430445// and *(x,y,z)* axis representations internally to the module.
431446extension SIMD3 where Scalar: FloatingPoint {
432447
433- /// Returns the squared length of this instance.
434- @usableFromInline @inline ( __always)
435- internal var lengthSquared : Scalar {
436- dot ( self )
437- }
438-
439448 /// True if all values of this instance are finite
440449 @usableFromInline @inline ( __always)
441450 internal var isFinite : Bool {
442451 x. isFinite && y. isFinite && z. isFinite
443452 }
444453
454+ /// Returns the squared length of this instance.
455+ @usableFromInline @inline ( __always)
456+ internal var lengthSquared : Scalar {
457+ dot ( self )
458+ }
459+
445460 /// Returns the scalar/dot product of this vector with `other`.
446461 @usableFromInline @inline ( __always)
447462 internal func dot( _ other: SIMD3 < Scalar > ) -> Scalar {
0 commit comments