Безопасное преобразование Float в Int? - PullRequest
4 голосов
/ 31 марта 2019

Преобразование с плавающей точкой в ​​Int задокументировано так:

let i = Int(x)

Однако это само по себе небезопасно, поскольку приложение Swift будет аварийно завершать работу, если x слишком велико, чтобы поместиться в целое число, илиNaN некоторого вида.

Так, каков самый простой, но безопасный способ преобразовать неизвестное содержимое Float или Double в Int (Int32, UInt16 и т. Д.), Например, без риска сбоя?Есть ли тип Int? ()?Или эквивалентное утверждение "если позволено"?

Ответы [ 3 ]

3 голосов
/ 31 марта 2019

Int(exactly:) может быть то, что вы ищете:

Создает целое число из заданного значения с плавающей точкой, если оно может быть точно представлено.

Если значение, переданное в качестве источника, не может быть точно представлено, результатом будет ноль.

Пример:

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), вы можете выбрать желаемый режим округления.

2 голосов
/ 01 апреля 2019

Ответ Мартина Р показывает правильный путь, но я все еще пишу это, чтобы научить тому, что происходит "под капотом".

Ограниченная точность Double означает, что только это навеличины Int64.max и Int64.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
0 голосов
/ 01 апреля 2019

Я думаю, nil будет подходящим возвращаемым значением преобразования Float.nan, Float.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)

Вы должны принять во внимание, чтопреобразование между Float и Int теряет точность с большими значениями из-за ограничения числа битов для каждого типа.Например, использование @ Martin5 @ 1015 * недостаточно точно:

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
...