Swift:将一个通用的FloatingPoint值转换为Int

时间:2017-04-30 11:46:46

标签: swift templates generics

考虑以下内容(从实际代码中简化):

func roundedInt<T: FloatingPoint>(_ f: T) -> Int {
    return Int(f.rounded())
}

无法使用以下错误进行编译:

  

无法使用类型的参数列表调用类型为“Int”的初始值设定项   '(T)''Int'的过载与这些部分匹配存在   参数列表:(Int64),(Word),(UInt8),(Int8),(UInt16),(Int16),   (UInt32),(Int32),(UInt64),(UInt),(Int),(Float),(Double),   (Float80),(String,radix:Int),(CGFloat),(NSNumber)

我认为这意味着FloatingPoint无法与任何Int重载匹配。

有一种简单的方法可以使这项工作吗?或者是否存在限制Swift泛型(例如,与C ++模板相比)排除这种情况?

3 个答案:

答案 0 :(得分:3)

这是我能想到的最好的:

func roundedInt<T: FloatingPoint>(_ f: T) -> Int {
    return Int(Float80("\(f.rounded())")!)
}

基本上,这是一种“欺骗”。据我所知,标准库中符合FloatingPoint的所有类型都是FloatDoubleFloat80。它们的字符串表示格式相同。这就是我将字符串表示转换为Float80的原因。然后我将此号码转换为Int

我说这是一个骗子,因为它不适用于大数字。当您将非常大的数字放入此函数时,会发生致命错误。经过一些反复试验,我发现在64位处理器上传递给此函数的最大数字是9223372036854775295,比Int.max小512。

此外,如果您在那里传递9223372036854775295,它将返回9223372036854770000而不是原始数字。这很可能是因为浮点数不准确。

答案 1 :(得分:2)

简单C ++模板只不过是 macro-ish 类型少,源代码替换机制。调用代码将替换为应用的模板,编译器会检查生成的代码是否有意义。你是对的,roundedInt<T> 无界函数应该可以在C ++版本中正常工作。

Swift泛型是设计上的类型安全,这意味着通用代码必须独立于声音,与给定呼叫站点的任何细节无关。在您的示例中,FloatingPoint协议是指导编译的类型(而不是调用代码使用的实际T类型)。

(顺便说一下,Java / C#generics也类似于Swift风格。)

关于您的实际问题,您可以简单地为roundedInt函数提供两个重载:

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

应涵盖大多数用例。当然,假设您只使用此编写小辅助函数...而不是完整的库/框架!

否则,请尝试 @Sweeper 基于字符串的解决方案。但请记住,除了他正确注意到的潜在精度损失之外,还有潜伏在那里的一些令人讨厌的性能问题。

答案 2 :(得分:0)

使用BinaryFloatingPoint这可以按照您的预期工作,在Int可以表示的范围内。

func roundedInt<T: BinaryFloatingPoint>(_ f: T) -> Int {
   return Int(f.rounded())
}

let x = roundedInt(Float(42.1)) // 42