Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Safe conversion of Float to Int?

Float to Int conversion is documented to be a simple as:

let i = Int(x)

However this alone is unsafe, as a Swift app will crash if x is too big to fit in an integer, or is a NaN of some kind.

So what is the simplest, yet safe way to convert the unknown contents of a Float or Double to an Int (Int32, UInt16, etc.), e.g. without risking a crash? Is there an Int?() type? Or equivalent "if let" statement?

like image 356
hotpaw2 Avatar asked Mar 31 '19 19:03

hotpaw2


People also ask

Can you convert a float to an int?

Since a float is bigger than int, you can convert a float to an int by simply down-casting it e.g. (int) 4.0f will give you integer 4. By the way, you must remember that typecasting just get rid of anything after the decimal point, they don't perform any rounding or flooring operation on the value.

What happens when float is converted to int?

To convert a float value to int we make use of the built-in int() function, this function trims the values after the decimal point and returns only the integer/whole number part. Example 1: Number of type float is converted to a result of type int.

Can we convert float into int in C?

The Float data type takes more bytes than the Int data type. So, we have to use explicit typecasting to convert the float value to an Int value.

Can we convert float to int in Python?

Python also has a built-in function to convert floats to integers: int() . In this case, 390.8 will be converted to 390 .


2 Answers

Int(exactly:) might be what you are looking for:

Creates an integer from the given floating-point value, if it can be represented exactly.

If the value passed as source is not representable exactly, the result is nil.

Example:

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

This will also fail if the floating point number is not integral, so you might want to round it before the conversion:

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

Rounding towards zero is what Int(x) would do, you can pick your desired rounding mode.

like image 113
Martin R Avatar answered Oct 20 '22 05:10

Martin R


Martin R's answer shows the right way, but I'm still writing this in order to teach what's going on "under the hood."

The limited precision of Double means that only that at the magnitude of Int64.max and Int64.min, Double representations are only available for every 4,096 Integers. As a result, there are a set of integers, which are valid and within range of Int64, which after (lossy) conversion to Double, end up rounded to a magnitude no longer representable as Int64. To account for these values, we need to ensure we only accept the range Double(Self.min).nextUp ... Double(Self.max).nextDown, rather than 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

Here's what that looks like, in action

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)")
}

which prints:

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
like image 21
Alexander Avatar answered Oct 20 '22 05:10

Alexander