Consider the following (simplified from actual code):
func roundedInt<T: FloatingPoint>(_ f: T) -> Int {
return Int(f.rounded())
}
This fails to compile with the following error:
Cannot invoke initializer for type 'Int' with an argument list of type '(T)' Overloads for 'Int' exist with these partially matching parameter lists: (Int64), (Word), (UInt8), (Int8), (UInt16), (Int16), (UInt32), (Int32), (UInt64), (UInt), (Int), (Float), (Double), (Float80), (String, radix: Int), (CGFloat), (NSNumber)
I take this to mean that FloatingPoint can't be matched to any of the Int overloads.
Is there a simple way to make this work? Or is there a limitation to Swift generics (e.g., compared to C++ templates) that precludes this?
Using BinaryFloatingPoint
this works as you expect, within the bounds of what Int can represent.
func roundedInt<T: BinaryFloatingPoint>(_ f: T) -> Int {
return Int(f.rounded())
}
let x = roundedInt(Float(42.1)) // 42
This is the best I can come up with:
func roundedInt<T: FloatingPoint>(_ f: T) -> Int {
return Int(Float80("\(f.rounded())")!)
}
Basically, this is kind of a "cheat". As far as I know, all the types in the standard library that conform to FloatingPoint
are Float
, Double
and Float80
. Their string representations are in the same format. This is why I converted the string representation to a Float80
. I then converted this number to an Int
.
I say this is a cheat because it does not really work for large numbers. When you put really large numbers into this function, a fatal error occurs. After some trial and error, I found out that the largest number you can pass to this function is 9223372036854775295 on a 64-bit processor, which is 512 less than Int.max
.
Also, if you pass 9223372036854775295 in there, it will return 9223372036854770000 instead of the original number. This is most likely because floating points are inaccurate.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With