|
| 1 | +//===--- ElementaryFunctionTests.swift ------------------------*- swift -*-===// |
| 2 | +// |
| 3 | +// This source file is part of the Swift Numerics open source project |
| 4 | +// |
| 5 | +// Copyright (c) 2019 - 2021 Apple Inc. and the Swift Numerics project authors |
| 6 | +// Licensed under Apache License v2.0 with Runtime Library Exception |
| 7 | +// |
| 8 | +// See https://swift.org/LICENSE.txt for license information |
| 9 | +// |
| 10 | +//===----------------------------------------------------------------------===// |
| 11 | + |
| 12 | +import XCTest |
| 13 | +import RealModule |
| 14 | +import _TestSupport |
| 15 | + |
| 16 | +@testable import QuaternionModule |
| 17 | + |
| 18 | +final class ElementaryFunctionTests: XCTestCase { |
| 19 | + |
| 20 | + func testExp<T: Real & FixedWidthFloatingPoint & SIMDScalar>(_ type: T.Type) { |
| 21 | + // exp(0) = 1 |
| 22 | + XCTAssertEqual(1, Quaternion<T>.exp(Quaternion(real: 0, imaginary: 0, 0, 0))) |
| 23 | + XCTAssertEqual(1, Quaternion<T>.exp(Quaternion(real:-0, imaginary: 0, 0, 0))) |
| 24 | + XCTAssertEqual(1, Quaternion<T>.exp(Quaternion(real:-0, imaginary:-0,-0,-0))) |
| 25 | + XCTAssertEqual(1, Quaternion<T>.exp(Quaternion(real: 0, imaginary:-0,-0,-0))) |
| 26 | + // In general, exp(Quaternion(r, 0, 0, 0)) should be exp(r), but that breaks |
| 27 | + // down when r is infinity or NaN, because we want all non-finite |
| 28 | + // quaternions to be semantically a single point at infinity. This is fine |
| 29 | + // for most inputs, but exp(Quaternion(-.infinity, 0, 0, 0)) would produce |
| 30 | + // 0 if we evaluated it in the usual way. |
| 31 | + XCTAssertFalse(Quaternion<T>.exp(Quaternion(real: .infinity, imaginary: .zero)).isFinite) |
| 32 | + XCTAssertFalse(Quaternion<T>.exp(Quaternion(real: .infinity, imaginary: .infinity)).isFinite) |
| 33 | + XCTAssertFalse(Quaternion<T>.exp(Quaternion(real: 0, imaginary: .infinity)).isFinite) |
| 34 | + XCTAssertFalse(Quaternion<T>.exp(Quaternion(real: -.infinity, imaginary: .infinity)).isFinite) |
| 35 | + XCTAssertFalse(Quaternion<T>.exp(Quaternion(real: -.infinity, imaginary: .zero)).isFinite) |
| 36 | + XCTAssertFalse(Quaternion<T>.exp(Quaternion(real: -.infinity, imaginary: -.infinity)).isFinite) |
| 37 | + XCTAssertFalse(Quaternion<T>.exp(Quaternion(real: 0, imaginary: -.infinity)).isFinite) |
| 38 | + XCTAssertFalse(Quaternion<T>.exp(Quaternion(real: .infinity, imaginary: -.infinity)).isFinite) |
| 39 | + XCTAssertFalse(Quaternion<T>.exp(Quaternion(real: .nan, imaginary: .nan)).isFinite) |
| 40 | + XCTAssertFalse(Quaternion<T>.exp(Quaternion(real: .infinity, imaginary: .nan)).isFinite) |
| 41 | + XCTAssertFalse(Quaternion<T>.exp(Quaternion(real: .nan, imaginary: .infinity)).isFinite) |
| 42 | + XCTAssertFalse(Quaternion<T>.exp(Quaternion(real: -.infinity, imaginary: .nan)).isFinite) |
| 43 | + XCTAssertFalse(Quaternion<T>.exp(Quaternion(real: .nan, imaginary: -.infinity)).isFinite) |
| 44 | + // Find a value of x such that exp(x) just overflows. Then exp((x, π/4)) |
| 45 | + // should not overflow, but will do so if it is not computed carefully. |
| 46 | + // The correct value is: |
| 47 | + // |
| 48 | + // exp((log(gfm) + log(9/8), π/4) = exp((log(gfm*9/8), π/4)) |
| 49 | + // = gfm*9/8 * (1/sqrt(2), 1/(sqrt(2)) |
| 50 | + let x = T.log(.greatestFiniteMagnitude) + T.log(9/8) |
| 51 | + let huge = Quaternion<T>.exp(Quaternion(real: x, imaginary: SIMD3(.pi/4, 0, 0))) |
| 52 | + let mag = T.greatestFiniteMagnitude/T.sqrt(2) * (9/8) |
| 53 | + XCTAssert(huge.real.isApproximatelyEqual(to: mag)) |
| 54 | + XCTAssert(huge.imaginary.x.isApproximatelyEqual(to: mag)) |
| 55 | + XCTAssertEqual(huge.imaginary.y, .zero) |
| 56 | + XCTAssertEqual(huge.imaginary.z, .zero) |
| 57 | + // For randomly-chosen well-scaled finite values, we expect to have the |
| 58 | + // usual identities: |
| 59 | + // |
| 60 | + // exp(z + w) = exp(z) * exp(w) |
| 61 | + // exp(z - w) = exp(z) / exp(w) |
| 62 | + var g = SystemRandomNumberGenerator() |
| 63 | + let values: [Quaternion<T>] = (0..<100).map { _ in |
| 64 | + Quaternion( |
| 65 | + real: T.random(in: -1 ... 1, using: &g), |
| 66 | + imaginary: SIMD3(repeating: T.random(in: -.pi ... .pi, using: &g) / 3)) |
| 67 | + } |
| 68 | + for z in values { |
| 69 | + for w in values { |
| 70 | + let p = Quaternion.exp(z) * Quaternion.exp(w) |
| 71 | + let q = Quaternion.exp(z) / Quaternion.exp(w) |
| 72 | + XCTAssert(Quaternion.exp(z + w).isApproximatelyEqual(to: p)) |
| 73 | + XCTAssert(Quaternion.exp(z - w).isApproximatelyEqual(to: q)) |
| 74 | + } |
| 75 | + } |
| 76 | + } |
| 77 | + |
| 78 | + func testFloat() { |
| 79 | + testExp(Float32.self) |
| 80 | + } |
| 81 | + |
| 82 | + func testDouble() { |
| 83 | + testExp(Float64.self) |
| 84 | + } |
| 85 | +} |
0 commit comments