How to Write a Generic Function for Floating Point Values in Swift

How to use FloatingPoint generic type for Float/Double

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)
}
}

Swift: Casting a generic, FloatingPoint value to Int

Simple C++ templates are nothing more than a macro-ish, type less, source code replacement mechanism. The calling code is replaced with the applied template and the compiler checks if the resulting code makes any sense. You are right, a roundedInt<T> unbounded function should work fine in C++ land.

Swift generics instead are typesafe by design, meaning the generic code must be sound on its own, independently of any particulars of a given call site. In your example, the FloatingPoint protocol is the type guiding the compilation (and not the actual T type used by the calling code).

(By the way, Java/C# generics also resembles Swift style as well.)


Regarding your actual problem, you could simply provide two overloads for the roundedInt function:

func roundedInt(_ f: Float)  -> Int { ... }
func roundedInt(_ f: Double) -> Int { ... }

which should cover most use cases. Of course, assuming you are only writing small helper functions with this... and not full blown libraries/frameworks!

Otherwise, try @Sweeper string-based solution. But please keep in mind that besides the potential loss of precision he correctly noted, there are also some nasty performance problems lurking in there as well.

How can I convert Generic type SignedNumeric to Float?

Not every SignedNumeric can be converted to a Float. Recall that to be a SignedNumeric, you need to be able to:

  • be negative and positive
  • be multiplied
  • be added and subtracted
  • have a magnitude that is Comparable and Numeric
  • can be converted from an integer

The last 4 requirements are all inherited from Numeric. We can easily build our own type that can do all those things. Here's a contrived example:

struct MyNumber: SignedNumeric, Comparable {
private var secret: [String]
private var isNegative: Bool
private var number: Int { secret.count }

static func *= (lhs: inout MyNumber, rhs: MyNumber) {
lhs = lhs * rhs
}

static func * (lhs: MyNumber, rhs: MyNumber) -> MyNumber {
MyNumber(secret: Array(repeating: "", count: lhs.number * rhs.number), isNegative: lhs.isNegative != rhs.isNegative)
}

init(integerLiteral value: Int) {
let int = value
isNegative = int < 0
secret = Array(repeating: "", count: abs(int))
}

static func < (lhs: MyNumber, rhs: MyNumber) -> Bool {
lhs.number < rhs.number
}

init?<T>(exactly source: T) where T : BinaryInteger {
guard let int = Int(exactly: source) else {
return nil
}
self.init(integerLiteral: int)
}

var magnitude: MyNumber {
MyNumber(secret: secret, isNegative: false)
}

static func - (lhs: MyNumber, rhs: MyNumber) -> MyNumber {
if lhs < rhs {
return -(rhs - lhs)
} else {
return MyNumber(secret: Array(repeating: "", count: lhs.number - rhs.number))
}
}

prefix static func - (operand: MyNumber) -> MyNumber {
MyNumber(secret: operand.secret, isNegative: !operand.isNegative)
}

mutating func negate() {
isNegative.toggle()
}

init(secret: [String], isNegative: Bool = false) {
self.secret = secret
self.isNegative = isNegative
}

typealias Magnitude = MyNumber

static func + (lhs: MyNumber, rhs: MyNumber) -> MyNumber {
MyNumber(secret: lhs.secret + rhs.secret)
}

typealias IntegerLiteralType = Int

}

How on earth is <insert whoever is doing the conversion here> going to know that, to convert MyNumber to Float, it has to do Float(aMyNumber.secret.count)!? Not to mention that secret is private.

On the other hand, Float.init does know how to convert BinaryFloatingPoints and BinaryIntegers to Float, because those protocols define the necessary properties such that Float.init can understand how they are represented. For example, BinaryIntegers are represented by words, which is a RandomAccessCollection of UInts, indexed by Ints.

I suggest that you only add the percent method to types of Series where it actually makes sense:

extension Series where T : BinaryInteger {
func percent(value: T) -> Float {
let v: Float = Float(value)
let m: Float = Float(minValue)
let r: Float = Float(range)
return (v - m) / r
}
}

IMO, it would make sense for any T : FloatingPoint, not just T : BinaryFloatingPoint:

extension Series where T : FloatingPoint {
func percent(value: T) -> T {
let v = value
let m = minValue
let r = range
return (v - m) / r
}
}

Since FloatingPoint supports division too, you don't need to convert to Float!

How to implement generic function for adding both numbers and String in Swift?

One possible solution is this. You know you want your method to be applicable to anything that has a + defined... So define an explicit protocol. Let's call it Addable:

protocol Addable {
static func +(lhs: Self, rhs: Self) -> Self
}

Now, using extensions, declare conformance to Addable for the types you care about:

extension String: Addable {}
extension Int: Addable {}
extension Double: Addable {}

And define your add function as:

func  add<T: Addable>(first: T, second: T) -> T {
return first + second
}

Swift 5: Merging Two Generic Functions with T: BinaryInteger & T:BinaryFloatingPoint with Output Type Specified

If I understand you correctly, you just want formatArrayDataforImage to accept any array of BinaryInteger or BinaryFloatingPoint without rewriting the whole thing.

To do that, write it for floating point, and then call that from the integer version.

I believe this is your floating point version:

func formatArrayDataforImage<Element>(dataSet: [Element]) -> [UInt8]
where Element: BinaryFloatingPoint {
guard let max = dataSet.max() else { fatalError("Find max fail.")}
guard let min = dataSet.min() else { fatalError("find min fail.")}

// Note this is a little dangerous since it it crash if
// min and max aren't in the range 0 - 1. I'd probably add
// assertions at least.
return dataSet.map { UInt8(255 * ($0 - min) / max) }
}

There's no particular reason to convert this to Float. It works fine for any BinaryFloatingPoint.

And then to call it for a BinaryInteger, you would just need to map the values to a floating point type (like Float), like you're currently doing.

func formatArrayDataforImage<Element>(dataSet: [Element]) -> [UInt8]
where Element: BinaryInteger // <=== Note different `where` clause
{
// Since this creates a [Float] it will call the other function
formatArrayDataforImage(dataSet: dataSet.map(Float.init))
}

As a note, your concurrentPerform is trying to do an illegal operation in parallel (you cannot modify an array on multiple threads simultaneously, not even if you are modifying different indexes). It turns out not to matter because your synchronization just makes the concurrentPerform serial again. All that code is equivalent to my dataSet.map(Float.init).



Related Topics



Leave a reply



Submit