2

If the only thing I know about the variables x, y is that they are of type T which is conforming to the FloatingPoint protocol, how can I calculate the value of atan2(y, x) and return it as an instance of type T?

Actually, I was trying to implement a Vector2D protocol, but I met problems with the following methods/properties:

.init(radius r:Component, angle a:Component) 
.angle

The following is my implementation:

/*
 Swift Tip: Vector Algebra with Protocols
 https://www.objc.io/blog/2018/11/29/vector-calculus-with-protocols/

 FloatingPoint
 https://developer.apple.com/documentation/swift/floatingpoint
 */

import Foundation  // cos, sin

// math constants
extension FloatingPoint {
    public static var deg: Self { .pi / 180 }
}

/// Vector2D
public protocol Vector2D: ExpressibleByArrayLiteral, CustomStringConvertible {
    // vector component type
    associatedtype Component: FloatingPoint
    // (x,y) coordinates
    var x: Component { get }
    var y: Component { get }
    // required initializer
    init(x: Component, y: Component)
}

// custom operators (declaration)
// ⭐️ can only be declared at file scope
infix operator • : MultiplicationPrecedence  // inner product
infix operator × : MultiplicationPrecedence  // outer product

// ExpressibleByArrayLiteral
public extension Vector2D {
    public init(arrayLiteral a: Component...) {
        assert(a.count >= 2, "Must initialize vector with at least 2 values.")
        self.init(x: a[0], y: a[1])
    }
}

// CustomStringConvertible
public extension Vector2D {
    public var description: String { "(\(x), \(y))" }
}

// initializers & factory methods
public extension Vector2D {

    // usage: Self(x, y)
    public init(_ x:Component, _ y:Component) {
        self.init(x: x, y: y)
    }

    // usage: Self(radius: r, angle: a)
    public init(radius r:Component, angle a:Component) {
        let r = r as! Double        // ⛔️doesn't work‼️ 
        let a = a as! Double
        let x = r * cos(a) as! Component
        let y = r * sin(a) as! Component
        self.init(x: x, y: y)
    }

    // usage: Self.polar(r, a)
    public static func polar(_ r: Component, _ a: Component) -> Self {
        Self(radius: r, angle: a)
    }

}

// constant vectors
public extension Vector2D {
    // zero vector: (0,0)
    public static var zero: Self { 
        Self(x: Component.zero, y: Component.zero) 
    }
}

// vector operations
public extension Vector2D {

    // u + v
    public static func + (u: Self, v: Self) -> Self {
        Self(x: u.x + v.x, y: u.y + v.y)
    }

    // -u (prefix)
    public static prefix func - (v: Self) -> Self {
        Self(x: -v.x, y: -v.y)
    }

    // u - v
    public static func - (u: Self, v: Self) -> Self {
        u + (-v)
    }

    // a * v, v * a, v / a  (scalar product)
    public static func * (a: Component, v: Self) -> Self {
        Self(x:a * v.x, y: a * v.y)
    }
    public static func * (v: Self, a: Component) -> Self {
        a * v
    }
    public static func / (v: Self, a: Component) -> Self {
        (1/a) * v
    }

    // u • v (dot product)
    public static func • (u: Self, v: Self) -> Component {
        u.x * v.x + u.y * v.y    // x1x2 + y1y2
    }
    // u × v (cross product)
    public static func × (u: Self, v: Self) -> Component {
        u.x * v.y - u.y * v.x    // ad - bc
    }
}

// complex numbers
public extension Vector2D {

    // z1 * z2 (complex product)
    public static func * (z1: Self, z2: Self) -> Self {
        let (a,b) = (z1.x, z1.y)                 // z1 = a + bi
        let (c,d) = (z2.x, z2.y)                 // z2 = c + di
        return Self(x: a*c - b*d, y: a*d + b*c)  // z1 * z2 = (ac-bd) + (ad+bc)i
    }

    // z.conjugate
    public var conjugate: Self { Self(x: x, y: -y) }  // a - bi

    // z1 / z2 (complex division)
    public static func / (z1: Self, z2: Self) -> Self {
        z1 * z2.conjugate / (z2 • z2)
    }
}

// vector properties
public extension Vector2D {
    public var length:    Component { sqrt(self • self) } // |v|
    public var magnitude: Component { length }            // |v|
    public var angle:     Component {                     // in radians
        atan2(y as! Double, x as! Double) as! Component    // ⛔️doesn't work‼️
    }  
    public var degrees:   Component { angle / .deg }      // in degrees
}

lochiwei
  • 1,240
  • 9
  • 16
  • 3
    Have a look at [SE-0246 Generic Math(s) Functions](https://github.com/apple/swift-evolution/blob/master/proposals/0246-mathable.md) (which is accepted, but not yet implemented in Swift), and the [Swift Numerics](https://github.com/apple/swift-numerics) project. – Martin R Dec 02 '19 at 16:13
  • @MartinR Do you have any workarounds now? – lochiwei Dec 02 '19 at 21:57
  • Do you need atan2() for all possible FloatingPoint types, or just for Float/Double/CGFloat? – Martin R Dec 02 '19 at 21:59
  • @MartinR I added more details to my problem, can you have a look? – lochiwei Dec 02 '19 at 22:12
  • The problem is that sin/cos/atan2/... are available for Float and Double in the standard libraries. For generic FloatingPoint types one would have to re-implement those functions (which is not trivial). Note that the Swift Numerics project defines a Real type to which only Float/Double/Float80 conform. – Martin R Dec 02 '19 at 22:18

1 Answers1

1

My workaround: (including 2 files - Vector2D.swift and CGPoint+Vector2D.swift)

Vector2D.swift

import Foundation

// math constants
extension FloatingPoint {
    public static var deg: Self { .pi / 180 }
}

// protocol for Vector2D.Component
public protocol VectorComponent: FloatingPoint {
    static func cos(_ x: Self) -> Self
    static func sin(_ x: Self) -> Self
    static func atan2(_ y: Self, _ x: Self) -> Self
}

/// Vector2D
public protocol Vector2D: ExpressibleByArrayLiteral, CustomStringConvertible {
    // vector component type
    associatedtype Component: VectorComponent
    // (x,y) coordinates
    var x: Component { get }
    var y: Component { get }
    // required initializer
    init(x: Component, y: Component)
}

// custom operators (declaration)
// ⭐️ can only be declared at file scope
infix operator • : MultiplicationPrecedence  // inner product
infix operator × : MultiplicationPrecedence  // outer product

// ExpressibleByArrayLiteral
public extension Vector2D {
    public init(arrayLiteral elements: Component...) {
        let a = elements + [0, 0]           // make sure that a.count >= 2
        self.init(x: a[0], y: a[1])
    }
}

// CustomStringConvertible
public extension Vector2D {
    public var description: String { "(\(x), \(y))" }
}

// initializers & factory methods
public extension Vector2D {

    // usage: Self(x, y)
    public init(_ x:Component, _ y:Component) {
        self.init(x: x, y: y)
    }

    // usage: Self(radius: r, angle: a)
    public init(radius r: Component, angle a: Component) {
        self.init(x: r * Component.cos(a), y: r * Component.sin(a))
    }

    // usage: Self.polar(r, a)
    public static func polar(_ r: Component, _ a: Component) -> Self {
        Self(radius: r, angle: a)
    }

}

// constant vectors
public extension Vector2D {
    // zero vector: (0,0)
    public static var zero: Self { 
        Self(x: Component.zero, y: Component.zero) 
    }
}

// vector operations
public extension Vector2D {

    // u + v
    public static func + (u: Self, v: Self) -> Self {
        Self(x: u.x + v.x, y: u.y + v.y)
    }

    // -u (prefix)
    public static prefix func - (v: Self) -> Self {
        Self(x: -v.x, y: -v.y)
    }

    // u - v
    public static func - (u: Self, v: Self) -> Self {
        u + (-v)
    }

    // a * v, v * a, v / a  (scalar product)
    public static func * (a: Component, v: Self) -> Self {
        Self(x:a * v.x, y: a * v.y)
    }
    public static func * (v: Self, a: Component) -> Self {
        a * v
    }
    public static func / (v: Self, a: Component) -> Self {
        (1/a) * v
    }

    // u • v (dot product)
    public static func • (u: Self, v: Self) -> Component {
        u.x * v.x + u.y * v.y    // x1x2 + y1y2
    }
    // u × v (cross product)
    public static func × (u: Self, v: Self) -> Component {
        u.x * v.y - u.y * v.x    // ad - bc
    }
}

// complex numbers
public extension Vector2D {

    // z1 * z2 (complex product)
    public static func * (z1: Self, z2: Self) -> Self {
        let (a,b) = (z1.x, z1.y)                 // z1 = a + bi
        let (c,d) = (z2.x, z2.y)                 // z2 = c + di
        return Self(x: a*c - b*d, y: a*d + b*c)  // z1 * z2 = (ac-bd) + (ad+bc)i
    }

    // z.conjugate
    public var conjugate: Self { Self(x: x, y: -y) }  // a - bi

    // z1 / z2 (complex division)
    public static func / (z1: Self, z2: Self) -> Self {
        z1 * z2.conjugate / (z2 • z2)
    }
}

// vector properties
public extension Vector2D {
    public var length:    Component { sqrt(self • self) } // |v|
    public var magnitude: Component { length }            // |v|
    public var angle:     Component { Component.atan2(y, x) }  // in radians
    public var degrees:   Component { angle / .deg }      // in degrees
}

// vector functions
public func abs<T: Vector2D>(_ v: T) -> T.Component {
    v.length
}

CGPoint+Vector2D.swift

import CoreGraphics  // for CGPoint, cos, sin, atan2

// CGFloat (protocol conformance)
extension CGFloat: VectorComponent {
    public static func cos(_ x: CGFloat) -> CGFloat { CoreGraphics.cos(x) }
    public static func sin(_ x: CGFloat) -> CGFloat { CoreGraphics.sin(x) }
    public static func atan2(_ y: CGFloat, _ x: CGFloat) -> CGFloat { CoreGraphics.atan2(y,x) }
}

// CGPoint (protocol conformance)
extension CGPoint: Vector2D {}
lochiwei
  • 1,240
  • 9
  • 16