Search code examples
swiftswift-protocolsmath-functions

Swift: calculating atan2(y,x) for FloatingPoint numbers?


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
}


Solution

  • 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 {}