Skip to content

Commit 442f1c6

Browse files
committed
Fix todos and update comments
1 parent 6351289 commit 442f1c6

File tree

2 files changed

+57
-68
lines changed

2 files changed

+57
-68
lines changed

Sources/QuaternionModule/ElementaryFunctions.swift

Lines changed: 34 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -21,23 +21,16 @@ extension Quaternion/*: ElementaryFunctions */ {
2121

2222
// MARK: - exp-like functions
2323

24-
// Mathematically, this operation can be expanded in terms of the `Real`
25-
// operations `exp`, `cos` and `sin` as follows:
26-
// ```
2724
// exp(r + xi + yj + zk) = exp(r + v) = exp(r) exp(v)
2825
// = exp(r) (cos(||v||) + (v/||v||) sin(||v||))
29-
// ```
30-
// Note that naive evaluation of this expression in floating-point would be
31-
// prone to premature overflow, since `cos` and `sin` both have magnitude
32-
// less than 1 for most inputs (i.e. `exp(r)` may be infinity when
33-
// `exp(r) cos(arg)` would not be).
26+
//
27+
// See exp on complex numbers for algorithm details.
3428
@inlinable
3529
public static func exp(_ q: Quaternion) -> Quaternion {
3630
guard q.isFinite else { return q }
3731
// For real quaternions we can skip phase and axis calculations
38-
// TODO: Replace q.imaginary == .zero with `q.isReal`
39-
let argument = q.imaginary == .zero ? .zero : q.imaginary.length
40-
let axis = q.imaginary == .zero ? .zero : (q.imaginary / argument)
32+
let argument = q.isReal ? .zero : q.imaginary.length
33+
let axis = q.isReal ? .zero : (q.imaginary / argument)
4134
// If real < log(greatestFiniteMagnitude), then exp(q.real) does not overflow.
4235
// To protect ourselves against sketchy log or exp implementations in
4336
// an unknown host library, or slight rounding disagreements between
@@ -55,11 +48,10 @@ extension Quaternion/*: ElementaryFunctions */ {
5548
// Note that the imaginary part is just the usual exp(r) sin(argument);
5649
// the only trick is computing the real part, which allows us to borrow
5750
// the derivative of real part for this function from complex numbers.
58-
// See `expMinusOne` in the ComplexModule for implementation details.
51+
// See `expMinusOne` in the ComplexModule for implementation details.
5952
guard q.isFinite else { return q }
60-
// TODO: Replace q.imaginary == .zero with `q.isReal`
61-
let argument = q.imaginary == .zero ? .zero : q.imaginary.length
62-
let axis = q.imaginary == .zero ? .zero : (q.imaginary / argument)
53+
let argument = q.isReal ? .zero : q.imaginary.length
54+
let axis = q.isReal ? .zero : (q.imaginary / argument)
6355
// If exp(q) is close to the overflow boundary, we don't need to
6456
// worry about the "MinusOne" part of this function; we're just
6557
// computing exp(q). (Even when q.y is near a multiple of π/2,
@@ -78,28 +70,26 @@ extension Quaternion/*: ElementaryFunctions */ {
7870
}
7971

8072
// cosh(r + xi + yj + zk) = cosh(r + v)
81-
// cosh(r + v) = cosh(r) cos(||v||) + (v/||v||) sinh(r) sin(||v||).
73+
// = cosh(r) cos(||v||) + (v/||v||) sinh(r) sin(||v||)
8274
//
8375
// See cosh on complex numbers for algorithm details.
8476
@inlinable
8577
public static func cosh(_ q: Quaternion) -> Quaternion {
8678
guard q.isFinite else { return q }
87-
// TODO: Replace q.imaginary == .zero with `q.isReal`
88-
let argument = q.imaginary == .zero ? .zero : q.imaginary.length
89-
let axis = q.imaginary == .zero ? .zero : (q.imaginary / argument)
79+
let argument = q.isReal ? .zero : q.imaginary.length
80+
let axis = q.isReal ? .zero : (q.imaginary / argument)
9081
return cosh(q.real, argument, axis: axis)
9182
}
9283

9384
// sinh(r + xi + yj + zk) = sinh(r + v)
94-
// sinh(r + v) = sinh(r) cos(||v||) + (v/||v||) cosh(r) sin(||v||)
85+
// = sinh(r) cos(||v||) + (v/||v||) cosh(r) sin(||v||)
9586
//
96-
// See cosh on complex numbers for algorithm details.
87+
// See sinh on complex numbers for algorithm details.
9788
@inlinable
9889
public static func sinh(_ q: Quaternion) -> Quaternion {
9990
guard q.isFinite else { return q }
100-
// TODO: Replace q.imaginary == .zero with `q.isReal`
101-
let argument = q.imaginary == .zero ? .zero : q.imaginary.length
102-
let axis = q.imaginary == .zero ? .zero : (q.imaginary / argument)
91+
let argument = q.isReal ? .zero : q.imaginary.length
92+
let axis = q.isReal ? .zero : (q.imaginary / argument)
10393
guard q.real.magnitude < -RealType.log(.ulpOfOne) else {
10494
let rotation = Quaternion(halfAngle: argument, unitAxis: axis)
10595
let firstScale = RealType.exp(q.real.magnitude/2)
@@ -133,25 +123,26 @@ extension Quaternion/*: ElementaryFunctions */ {
133123
}
134124

135125
// cos(r + xi + yj + zk) = cos(r + v)
136-
// cos(r + v) = cos(r) cosh(||v||) - (v/||v||) sin(r) sinh(||v||).
126+
// = cos(r) cosh(||v||) - (v/||v||) sin(r) sinh(||v||)
137127
//
138128
// See cosh for algorithm details.
139129
@inlinable
140130
public static func cos(_ q: Quaternion) -> Quaternion {
141131
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)
132+
let argument = q.isReal ? .zero : q.imaginary.length
133+
let axis = q.isReal ? .zero : (q.imaginary / argument)
145134
return cosh(-argument, q.real, axis: axis)
146135
}
147136

148-
// See sinh on complex numbers for algorithm details.
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.
149141
@inlinable
150142
public static func sin(_ q: Quaternion) -> Quaternion {
151143
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)
144+
let argument = q.isReal ? .zero : q.imaginary.length
145+
let axis = q.isReal ? .zero : (q.imaginary / argument)
155146
let (x, y) = sinh(-argument, q.real)
156147
return Quaternion(real: y, imaginary: axis * -x)
157148
}
@@ -182,42 +173,36 @@ extension Quaternion/*: ElementaryFunctions */ {
182173
}
183174

184175
// MARK: - pow-like functions
176+
177+
// pow(q, p) = exp(log(pow(q, p))) = exp(p * log(q))
178+
//
179+
// See pow on complex numbers for algorithm details.
185180
@inlinable
186181
public static func pow(_ q: Quaternion, _ p: Quaternion) -> Quaternion {
187-
// pow(q, p) = exp(log(q^p)) = exp(p * log(q))
188182
return exp(p * log(q))
189183
}
190184

185+
// pow(q, n) = exp(log(q) * n)
186+
//
187+
// See pow on complex numbers for algorithm details.
191188
@inlinable
192189
public static func pow(_ q: Quaternion, _ n: Int) -> Quaternion {
193190
if q.isZero { return .zero }
194-
// TODO: this implementation is not quite correct, because n may be
195-
// rounded in conversion to RealType. This only effects very extreme
196-
// cases, so we'll leave it alone for now.
197-
//
198-
// Note that this does not have the same problems that a similar
199-
// implementation for a real type would have, because there's no
200-
// parity/sign interaction in the complex plane.
201191
return exp(log(q).multiplied(by: RealType(n)))
202192
}
203193

204194
@inlinable
205195
public static func sqrt(_ q: Quaternion) -> Quaternion<RealType> {
206196
if q.isZero { return .zero }
207-
// TODO: This is not the fastest implementation available
208197
return exp(log(q).divided(by: 2))
209198
}
210199

200+
// root(q, n) = exp(log(q) / n)
201+
//
202+
// See root on complex numbers for algorithm details.
211203
@inlinable
212204
public static func root(_ q: Quaternion, _ n: Int) -> Quaternion {
213205
if q.isZero { return .zero }
214-
// TODO: this implementation is not quite correct, because n may be
215-
// rounded in conversion to RealType. This only effects very extreme
216-
// cases, so we'll leave it alone for now.
217-
//
218-
// Note that this does not have the same problems that a similar
219-
// implementation for a real type would have, because there's no
220-
// parity/sign interaction in the complex plane.
221206
return exp(log(q).divided(by: RealType(n)))
222207
}
223208
}
@@ -244,7 +229,7 @@ extension Quaternion {
244229
)
245230
}
246231

247-
// See sinh of complex numbers for algorithm details.
232+
// See sinh of complex numbers for algorithm details.
248233
@usableFromInline @_transparent
249234
internal static func sinh(
250235
_ x: RealType,

Tests/QuaternionTests/ElementaryFunctionTests.swift

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import _TestSupport
1717

1818
final class ElementaryFunctionTests: XCTestCase {
1919

20+
// MARK: - exp-like functions
21+
2022
func testExp<T: Real & FixedWidthFloatingPoint & SIMDScalar>(_ type: T.Type) {
2123
// exp(0) = 1
2224
XCTAssertEqual(1, Quaternion<T>.exp(Quaternion(real: 0, imaginary: 0, 0, 0)))
@@ -209,24 +211,6 @@ final class ElementaryFunctionTests: XCTestCase {
209211
}
210212
}
211213

212-
func testLog<T: Real & FixedWidthFloatingPoint & SIMDScalar>(_ type: T.Type) {
213-
// log(0) = undefined/infinity
214-
XCTAssertFalse(Quaternion<T>.log(Quaternion(real: 0, imaginary: 0, 0, 0)).isFinite)
215-
XCTAssertFalse(Quaternion<T>.log(Quaternion(real:-0, imaginary: 0, 0, 0)).isFinite)
216-
XCTAssertFalse(Quaternion<T>.log(Quaternion(real:-0, imaginary:-0,-0,-0)).isFinite)
217-
XCTAssertFalse(Quaternion<T>.log(Quaternion(real: 0, imaginary:-0,-0,-0)).isFinite)
218-
219-
var g = SystemRandomNumberGenerator()
220-
let values: [Quaternion<T>] = (0..<100).map { _ in
221-
Quaternion(
222-
real: T.random(in: -1 ... 1, using: &g),
223-
imaginary: SIMD3(repeating: T.random(in: -.pi ... .pi, using: &g) / 3))
224-
}
225-
for q in values {
226-
XCTAssertTrue(q.isApproximatelyEqual(to: .log(.exp(q))))
227-
}
228-
}
229-
230214
func testCos<T: Real & FixedWidthFloatingPoint & SIMDScalar>(_ type: T.Type) {
231215
var g = SystemRandomNumberGenerator()
232216
let values: [Quaternion<T>] = (0..<1000).map { _ in
@@ -264,7 +248,7 @@ final class ElementaryFunctionTests: XCTestCase {
264248
let s = Quaternion.sin(q)
265249

266250
// For randomly-chosen well-scaled finite values, we expect to have
267-
// cos ≈ (e^(q*||v||)+e^(-q*||v||)) / 2
251+
// sin ≈ (e^(q*||v||)+e^(-q*||v||)) / 2
268252
let p = Quaternion(imaginary: q.imaginary / q.imaginary.length)
269253
let e = (.exp(p * q) - .exp(-p * q)) / (p * 2)
270254
XCTAssert(s.isApproximatelyEqual(to: e))
@@ -276,6 +260,26 @@ final class ElementaryFunctionTests: XCTestCase {
276260
}
277261
}
278262

263+
// MARK: - log-like functions
264+
265+
func testLog<T: Real & FixedWidthFloatingPoint & SIMDScalar>(_ type: T.Type) {
266+
// log(0) = undefined/infinity
267+
XCTAssertFalse(Quaternion<T>.log(Quaternion(real: 0, imaginary: 0, 0, 0)).isFinite)
268+
XCTAssertFalse(Quaternion<T>.log(Quaternion(real:-0, imaginary: 0, 0, 0)).isFinite)
269+
XCTAssertFalse(Quaternion<T>.log(Quaternion(real:-0, imaginary:-0,-0,-0)).isFinite)
270+
XCTAssertFalse(Quaternion<T>.log(Quaternion(real: 0, imaginary:-0,-0,-0)).isFinite)
271+
272+
var g = SystemRandomNumberGenerator()
273+
let values: [Quaternion<T>] = (0..<100).map { _ in
274+
Quaternion(
275+
real: T.random(in: -1 ... 1, using: &g),
276+
imaginary: SIMD3(repeating: T.random(in: -.pi ... .pi, using: &g) / 3))
277+
}
278+
for q in values {
279+
XCTAssertTrue(q.isApproximatelyEqual(to: .log(.exp(q))))
280+
}
281+
}
282+
279283
func testFloat() {
280284
testExp(Float32.self)
281285
testExpMinusOne(Float32.self)

0 commit comments

Comments
 (0)