安全将Float转换为Int?

时间:2019-03-31 19:24:47

标签: swift type-conversion numbers

浮点到整数转换的记录很简单:

let i = Int(x)

但是,仅此一项是不安全的,因为如果x太大而无法容纳整数,或者是某种NaN,Swift应用程序将崩溃。

那么将Float或Double的未知内容转换为Int(Int32,UInt16等)的最简单但安全的方法是什么,例如不会冒险崩溃?有Int?()类型吗?或等效的“如果让”语句?

3 个答案:

答案 0 :(得分:3)

Int(exactly:)可能就是您想要的:

  

从给定的浮点值创建一个整数(如果可以精确表示)。

     

如果作为源传递的值不能精确表示,则结果为nil。

示例:

let x = 123e20
if let i = Int(exactly: x) {
    print(i)
} else {
    print("not representable")
}

如果浮点数不是整数,这也会失败,因此您可能需要在转换前将其四舍五入:

let x = 12.3
if let i = Int(exactly: x.rounded(.towardZero)) {
    print(i)
}

Int(x)舍入为零,您可以选择所需的舍入模式。

答案 1 :(得分:2)

Martin R的答案显示了正确的方法,但是我仍然在写这篇文章,以教“幕后”的情况。

Double的有限精度意味着仅在Int64.maxInt64.min的量级上,Double表示仅可用于每个4,096整数。结果,存在一组有效且在Int64范围内的整数,在(有损)转换为Double之后,这些整数最终舍入为不再可表示为Int64的大小。为了说明这些值,我们需要确保仅接受范围Double(Self.min).nextUp ... Double(Self.max).nextDown,而不接受Double(Self.min)... Double(Self.max)

Int.min                 -9,223,372,036,854,775,808 
Float(Int.min)          -9,223,372,036,854,780,000 lower than Int.min by 4096, thus not representable by Int
Float(Int.min).nextUp   -9,223,371,487,098,960,000 greater than Int.min by 549,755,820,032, thus representable by Int
Int.max                 +9,223,372,036,854,775,807  
Float(Int.max)          +9,223,372,036,854,780,000 greater than Int.max by 4096, thus not representable by Int
Float(Int.max).nextDown +9,223,371,487,098,960,000 lower than Int.max by 549,755,820,032, thus representable by Int

实际情况就是这样

import Foundation

extension FixedWidthInteger {
    static var representableDoubles: ClosedRange<Double> {
        return Double(Self.min).nextUp ... Double(Self.max).nextDown
    }

    init?(safelyFromDouble d: Double) {
        guard Self.representableDoubles.contains(d) else { return nil }
        self.init(d)
    }
}

func formatDecimal(_ d: Double) -> String{
    let numberFormatter = NumberFormatter()
    numberFormatter.numberStyle = .decimal
    numberFormatter.positivePrefix = "+"
    return numberFormatter.string(from: NSNumber(value: d))!
}

let testCases: [Double] = [
    Double.nan,
    -Double.nan,
    Double.signalingNaN,
    -Double.signalingNaN,

    Double.infinity,
    Double(Int.max),
    Double(Int.max).nextDown,
    +1,
    +0.6,
    +0.5,
    +0.4,
    +0,
    -0,
    -0.4,
    -0.5,
    -0.6,
    -1,
    -1.5,
    Double(Int.min).nextUp,
    Double(Int.min),
    -Double.infinity,
]

for d in testCases {
    print("Double: \(formatDecimal(d)), as Int: \(Int(safelyFromDouble: d)as Any)")
}

打印:

Double: NaN, as Int: nil
Double: NaN, as Int: nil
Double: NaN, as Int: nil
Double: NaN, as Int: nil
Double: +∞, as Int: nil
Double: +9,223,372,036,854,780,000, as Int: nil
Double: +9,223,372,036,854,770,000, as Int: Optional(9223372036854774784)
Double: +1, as Int: Optional(1)
Double: +0.6, as Int: Optional(0)
Double: +0.5, as Int: Optional(0)
Double: +0.4, as Int: Optional(0)
Double: +0, as Int: Optional(0)
Double: +0, as Int: Optional(0)
Double: -0.4, as Int: Optional(0)
Double: -0.5, as Int: Optional(0)
Double: -0.6, as Int: Optional(0)
Double: -1, as Int: Optional(-1)
Double: -1.5, as Int: Optional(-1)
Double: -9,223,372,036,854,770,000, as Int: Optional(-9223372036854774784)
Double: -9,223,372,036,854,780,000, as Int: nil
Double: -∞, as Int: nil

答案 2 :(得分:0)

我认为nilFloat.nanFloat.infinity或超出范围Int.min...Int.max的转换的适当返回值。

您可以定义一个范围,其中应包含浮点数:

let validRange: ClosedRange<Float> = Float(Int.min)...Float(Int.max)

并像这样使用它:

func convert(_ f: Float) -> Int? {
    var optInteger: Int? = nil

    if  validRange.contains(f) {
        optInteger = Int(f)
    }

    return optInteger
}

print(convert(1.2))  //Optional(1)

您应考虑到FloatInt之间的转换由于提供给每种类型的位数的限制而失去了大数值时的精度。例如,使用@MartinR的Int.init(exactly:)不够精确:

let x: Float = 9223371487098961919.0
if let i = Int(exactly: x.rounded()) {
    print(i) 
}

收益

  

9223371487098961920

可能的解决方案是使用更精确的类型。例如Float80

let validRange: ClosedRange<Float80> = Float80(Int.min)...Float80(Int.max)

func convert(_ f80: Float80) -> Int? {
    var optInteger: Int? = nil

    if  validRange.contains(f80) {
        optInteger = Int(f80)
    }

    return optInteger
}

以下是一些用例:

convert(Float80(Int.max))        //Optional(9223372036854775807)
convert(Float80(Int.max) - 1.0)  //Optional(9223372036854775806)
convert(Float80(Int.max) + 1.0)  //nil
convert(Float80(Int.min)))       //Optional(-9223372036854775808)
convert(Float80(Int.min) - 1.0)) //nil
convert(Float80.infinity)        //nil
convert(Float80.nan)             //nil