3

I would like to write some highly optimized generic functions in Swift 3 that work on any floating point type, so I came out with something like this, which works:

@inline(__always) public func radsToDegs<T: BinaryFloatingPoint>(_ angle: T) -> T {
    let radsToDegsConstant: T = 180/T(M_PI)
    return angle*radsToDegsConstant
}

let a: CGFloat = CGFloat(M_PI)
let b = 3.14
let c: Float = 3.14
let d: Double = 3.14

radsToDegs(a) // -> 180.0
radsToDegs(b) // -> 179.9087476710785
radsToDegs(c) // -> 179.9087
radsToDegs(d) // -> 179.9087476710785

But I would like to have the radsToDegsConstant precomputed, and the following code fails to compile for a good reason: it's due to the fact that radsToDegs is generic:

// error: type 'OptimizedConstants' cannot be nested in generic function 'radsToDegs'
@inline(__always) public func radsToDegs<T: BinaryFloatingPoint>(_ angle: T) -> T {
    struct OptimizedConstants {
        static let radsToDegsConstant: T = 180/T(M_PI)
    }
    return angle*OptimizedConstants.radsToDegsConstant
}

So my only remaining solution is to not go for a generic function, but instead extend just the type that I know I'm most interested in, and declare the computed constant outside the function:

private let radsToDegsConstant = 180/CGFloat(M_PI)
public extension CGFloat {
    @inline(__always) public func radsToDegs() -> CGFloat {
        return self*radsToDegsConstant
    }
}

let x: CGFloat = CGFloat(M_PI)
x.radsToDegs() // 180.0

But I still wonder if the Swift compiler would be smart enough to avoid computing radsToDegsConstant each time the function it's called, on the former generic implementation.

So that's my question: is that optimized? Or is there a trick, or a compiler directive that I may use to still get both the benefits from the generic function and the precomputed value that the extension form delivers?

nbloqs
  • 3,152
  • 1
  • 28
  • 49
  • 1
    you might be interested in this http://stackoverflow.com/a/29179878/2303865 – Leo Dabus Jan 11 '17 at 00:14
  • Thanks, although those on that link are less optimized, I like the trick there with the DoubleConvertible. But it comes to my mind that with that implementation, the function will always use a Double, so the efficiency is less for smaller floating point types. Or there is something I'm not seeing? – nbloqs Jan 11 '17 at 00:21
  • 1
    The Swift 3 method that extends FloatingPoint protocol does it using the correct type – Leo Dabus Jan 11 '17 at 00:22
  • I will give it a try on a Playground and let you know if I can precompute the value with that method, which is my goal here. Thanks. – nbloqs Jan 11 '17 at 00:23
  • This is the only way it compiles, but I don't like the forced cast: private let radsToDegsConstant = 180.0/M_PI // Optimization: precomputed value. public extension FloatingPoint { var degreesToRadians: Self { return self * (degsToRadsConstant as! Self) } @inline(__always) public func radsToDegs() -> Self { return self*(radsToDegsConstant as! Self) } } – nbloqs Jan 11 '17 at 00:38
  • I don't understand what are you trying to accomplish. Just use it as it is. It will return the type based on the type of the object you call it. BTW comment area it is not meant for codes. edit your question and add it there if you think it is worthy. – Leo Dabus Jan 11 '17 at 00:39
  • I want the functions not to compute anything inside that can be computed outside, as it's stated in the question. Please let me know if I'm not clear enough so I can edit the question to clarify. Your solution does not deal with the precomputed values except if a forced cast is done, but using the "as!" may be unsafe. Thanks anyway! – nbloqs Jan 11 '17 at 00:41
  • in Swift 3 you are supposed to use .pi on the type instead of M_PI https://developer.apple.com/reference/swift/floatingpoint/1845454-pi – Leo Dabus Jan 11 '17 at 03:04
  • 1
    It's possible that if/when Swift supports [generic constants](https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md#generic-constants), the compiler could optimise by specialising such constants for each concrete type that they're applied with. However, I don't *believe* there's currently an easy way to achieve such optimisation in your case. – Hamish Jan 11 '17 at 23:42

1 Answers1

1

I came to the conclusion that precomputing these kind of constants can not be done for generic functions in Swift 3. Here is the path that I have followed: I started by trying the suggestions from @Leo Dabus in the previous comments (see this question: How can I convert from degrees to radians?), and came up with the following code, that fails at runtime:

    private let radsToDegsConstant = 180.0 / .pi
    public extension FloatingPoint {
        var degsToRads: Self { return self * (radsToDegsConstant as! Self) }

        @inline(__always) public func radsToDegs() -> Self {
            return self*(radsToDegsConstant as! Self)
        }
    }

    Double.pi.degsToRads
    Double.pi.radsToDegs()
    Float(M_PI).radsToDegs() // Error: Could not cast value of type 'Swift.Double'to 'Swift.Float'
    Float.pi.radsToDegs() // Error: Could not cast value of type 'Swift.Double'to 'Swift.Float'
    CGFloat.pi.radsToDegs() // Error: Could not cast value of type 'Swift.Double'to 'CoreGraphics.CGFloat'

The problem here comes from the fact that the constant radsToDegsConstant is assumed to be a Double by the compiler, and then the forced cast as! fails at runtime. Note also that I tried both the function and the property (var) syntax, just for science.

Of course, stored properties can not be added to extensions, so the following code is also invalid:

    public extension FloatingPoint {
        let radsToDegsConstant: Self = Self(180.0 / .pi) // Error: extensions may not contain stored properties

        var degsToRads: Self { return self * (radsToDegsConstant as! Self) }

        @inline(__always) public func radsToDegs() -> Self {
            return self*(radsToDegsConstant as! Self)
        }
    }

Note: Swapping the let by a var changes nothing, as it should be.

So finally I decided to get the property syntax from the mentioned link, but focused on CGFloat only, since I just need that one working as fast as possible. So here is the optimized extension:

import CoreGraphics

public let CG_PI = CGFloat(M_PI)
public let CG_PI_2 = CGFloat(M_PI_2)
public let CG_PI_4 = CGFloat(M_PI_4)
public let CG_2PI = CGFloat(2*M_PI)

private let degsToRadsConstant = CG_PI/180 // Optimization: precomputed value.
private let radsToDegsConstant = 180.0/CG_PI // Optimization: precomputed value.

public extension CGFloat {
    public var radsToDegs: CGFloat {
        @inline(__always) get { return self*radsToDegsConstant }
    }

    public var degsToRads: CGFloat {
        @inline(__always) get { return self*degsToRadsConstant }
    }
}

A few more notes:

  • final methods are not supported in extensions (that could help a bit).
  • .pi in Swift 3: No, it's not a mistake to not have used that one, since in the library I'm building I use a lot things like M_PI_2 and the other π constants with CGFloat. So casting them to CGFloat in compile time, and using them with a similar naming convention to the one provided by the M_XX constants is perfectly okay on this specific case. And the problem with .pi is that, as far as I know, Swift libraries do not provide these other (absolutely needed for me) values. So for consistency, I just keep using this, and it works fast and with the proper types. I just don't buy all the style stuff from the Swift guys.

Finally, if someone still answers this with a trick I'm not seeing (that allows to precompute the constants and still use generic functions, as I did ask), that will be great, and accepting the new solution as the correct answer will be a pleasure...

Community
  • 1
  • 1
nbloqs
  • 3,152
  • 1
  • 28
  • 49