Common half function in Swift

I am trying to write a common half function for all numeric types:

func half<T where T: FloatingPointType>(value: T) -> T { return value * 0.5 } 

And I see this error:

No '*' candidates call the expected type of context result 'T'

Is it possible to write a general function in this case?

+7
swift
source share
2 answers

You write "all numeric types", but based on a 0.5 multiplication, this answer is based on the assumption that you are referring to commonly used floating point types ( Double , Float , CGFloat ).

You can create a custom protocol that displays the * function (for multiplication by Self values), as well as an initializer on FloatLiteralType and matches Double , Float and CGFloat this protocol. The * function, as well as the initializer, are already implemented for these three types, so the correspondence simply explicitly tells the compiler that these types really match your user protocol.

eg.

 protocol MyFloatingPointTypes { func *(lhs: Self, rhs: Self) -> Self init(_: FloatLiteralType) } extension Double : MyFloatingPointTypes {} extension Float : MyFloatingPointTypes {} extension CGFloat : MyFloatingPointTypes {} func half<T: MyFloatingPointTypes>(value: T) -> T { return value * T(0.5) } /* example usage */ var dVal: Double = 10.5 var fVal: Float = 10.5 var cfVal: CGFloat = 10.5 dVal = half(dVal) fVal = half(fVal) cfVal = half(cfVal) print(dVal, fVal, cfVal) // 5.25 5.25 5.25 

Alternative: complex protocol restriction using FloatLiteralConvertible

As @Hamish writes in his comments below, instead of drawing the initializer using Double ( init(_: FloatLiteralType) in MyFloatingPointTypes , it is better to use an additional type restriction for the general half(...) function instead, holding back the general one (in addition to MyFloatingPointTypes ) before FloatLiteralConvertible :

 // lets rename this protocol here, since it no longer has any direct association with floats protocol Multiplicable { func *(lhs: Self, rhs: Self) -> Self } extension Double : Multiplicable {} extension Float : Multiplicable {} extension CGFloat : Multiplicable {} func half<T: protocol<Multiplicable, FloatLiteralConvertible>>(value: T) -> T { return value * 0.5 } /* example usage */ // ... same as above 

Another (more general) alternative: a complex protocol restriction using IntegerLiteralConvertible

Or, if you want the half function to extend its general "scope" to integer types (as @Hamish also noted, thanks!), You can use the same protocol protocol restriction method as above, but against IntegerLiteralConvertible :

 protocol Divisable { func /(lhs: Self, rhs: Self) -> Self } extension Double : Divisable {} extension Float : Divisable {} extension CGFloat : Divisable {} extension Int: Divisable {} func half<T: protocol<Divisable, IntegerLiteralConvertible>>(value: T) -> T { return value / 2 } /* example usage */ var dVal: Double = 10.5 var fVal: Float = 10.5 var cfVal: CGFloat = 10.5 var iVal: Int = 11 dVal = half(dVal) fVal = half(fVal) cfVal = half(cfVal) iVal = half(iVal) // truncates decimal part print(dVal, fVal, cfVal, iVal) // 5.25 5.25 5.25 5 
+4
source share

Not in Swift 2.2. FloatingPointType not a very powerful type. In Swift 3, this is possible if you limit yourself to BinaryFloatingPoint (which covers most of the floating point types that you probably think of, especially Float and Double ).

 func half<T where T: BinaryFloatingPoint>(_ value: T) -> T { return value * 0.5 } 

In the most general case of a custom FloatingPointType in Swift 2.2, a system may have no way to convert this to a Double multiplication, and then convert it back to an arbitrary FloatingPointType . The BinaryFloatingPoint protocol adds ExpressibleByFloatLiteral , which means that 0.5 can be converted to an arbitrary type.

It is important to remember that 0.5 in this code above is of type T This is not a Double . The following code will still not work:

 func half<T where T: BinaryFloatingPoint>(_ value: T) -> T { let oneHalf = 0.5 return value * oneHalf // error: binary operator '*' cannot be applied to operands of type 'T' and 'Double' } 

You cannot multiply two floating point numbers. Swift intentionally avoids a lot of automatic coercion of the type that was the source of subtle errors in C. In the general case of custom FloatingPoint it is impossible to say that one type is "more accurate" than the other. They can have different accuracy in different ranges. And of course, you can create a FloatingPoint that is much more than double. Thus, there is no obvious โ€œjust push everything to doubleโ€ rule that you could use.

+7
source share

All Articles