hyperknot
hyperknot

Reputation: 13996

How to use FloatingPoint generic type for Float/Double

I'd like to make the function below work both with Float and Double values:

func srgb2linear(_ S: Float) -> Float {
  if S <= 0.04045 {
    return S / 12.92
  } else {
    return pow((S + 0.055) / 1.055, 2.4)
  }
}

The Swift 4 Documentation says that what I need is a FloatingPoint generic, to represent both Float and Double classes, like:

func srgb2linear<T: FloatingPoint>(_ S: T) -> T

However when I try to do this, it doesn't compile with the following errors:

Error: binary operator '<=' cannot be applied to operands of type 'T' and 'Double'
Error: binary operator '/' cannot be applied to operands of type 'T' and 'Double'
Error: binary operator '+' cannot be applied to operands of type 'T' and 'Double'

How is it possible that for a generic representing floating point numbers such operators are not implemented? And if not like this, how can I write this function in Swift?

Upvotes: 0

Views: 1907

Answers (2)

rob mayoff
rob mayoff

Reputation: 386058

One problem is that FloatingPoint is not a subprotocol of ExpressibleByFloatLiteral, so your floating-point literals cannot necessarily be converted to T. You can solve this either by changing FloatingPoint to BinaryFloatingPoint (which is a subprotocol of ExpressibleByFloatLiteral) or by adding ExpressibleByFloatLiteral as a separate requirement.

Then you will run into the problem that there is no pow function that is generic over FloatingPoint, and no member of FloatingPoint or BinaryFloatingPoint that performs exponentiation. You can solve this by creating a new protocol and conforming the existing floating-point types to it:

protocol Exponentiatable {
    func toPower(_ power: Self) -> Self
}

extension Float: Exponentiatable {
    func toPower(_ power: Float) -> Float { return pow(self, power) }
}

extension Double: Exponentiatable {
    func toPower(_ power: Double) -> Double { return pow(self, power) }
}

extension CGFloat: Exponentiatable {
    func toPower(_ power: CGFloat) -> CGFloat { return pow(self, power) }
}

Note that there is also a Float80 type, but the standard library doesn't provide a pow function for it.

Now we can write a working generic function:

func srgb2linear<T: FloatingPoint>(_ S: T) -> T
    where T: ExpressibleByFloatLiteral, T: Exponentiatable
{
    if S <= 0.04045 {
        return S / 12.92
    } else {
        return ((S + 0.055) / 1.055).toPower(2.4)
    }
}

Upvotes: 3

Alain T.
Alain T.

Reputation: 42129

You could define a second one with Double arguments:

func srgb2linear(_ S: Double) -> Double {
  if S <= 0.04045 {
    return S / 12.92
  } else {
    return pow((S + 0.055) / 1.055, 2.4)
  }
}

or, if Float<->Double conversions are not a concern :

func srgb2linear(_ S: Double) -> Double {
  return Double(srgb2linear(Float(S)))
}

Upvotes: 0

Related Questions