39

I've been using SQLite.swift lately to build my app database. And I'm defining all my INTEGER columns with a Int64 type, like the documentation explains.

But every once in a while I need that Int64 to be just Int. So my question is, if I do this:

//Create a table with Int instead of Int64
let test_id = Expression<Int>("test_id")
let tests = db["tests"]

db.create(table: tests, ifNotExists: true){ t in
    t.column(test_id)
}


class func insertTest(t: Int) -> Int{
    //insert.rowid returns an Int64 type
    let insert = tests.insert(test_id <- t)
    if let rowid = insert.rowid{
        //directly cast Int64 into Int
        return Int(rowid)
    }
    return 0
}

Will it be correct?

Of course I tested it. And it does works, but I was reading this question in Stackoverflow

And it seems that I could have a problem with 32 bits devices...

If this is wrong, how can I cast Int64 into Int?

Community
  • 1
  • 1
Renato Parreira
  • 838
  • 1
  • 7
  • 23
  • If the value of the Int64 is greater than 2^31 and Int is 32-bits what do you think would happen? – zaph Sep 26 '15 at 03:59
  • @zaph overflow? I don't know... – Renato Parreira Sep 26 '15 at 04:03
  • @zaph can you explain to me? – Renato Parreira Sep 26 '15 at 04:14
  • 1
    If the INT64 has a clue that used more than 32 bits and you assign it to a Int which only has 32 bits the extra bits are lost and the value is incorrect. There are resources to read about how values are stored in memory or you can just learn the rules. Understanding it arguably better but everyone has a level of abstraction they don't go below. There is also a level of abstraction that really can not practically be gone below, an example is knowing the exact physical address in RAM memory that a value resides at the current generally used coding level in with today's allocation schemes. – zaph Sep 26 '15 at 04:50
  • 2
    It is like trying to pour the contents a 36 oz bottle of water into a 32 oz bottle, the last 4 oz will just spill on the floor and be lost. – zaph Sep 26 '15 at 04:52
  • @zaph yes, I understand now, thank you too! – Renato Parreira Sep 26 '15 at 05:00

4 Answers4

62

Converting an Int64 to Int by passing the Int64 value to the Int initializer will always work on a 64-bit machine, and it will crash on a 32-bit machine if the integer is outside of the range Int32.min ... Int32.max.

For safety use the init(truncatingIfNeeded:) initializer (formerly known as init(truncatingBitPattern:) in earlier Swift versions) to convert the value:

return Int(truncatingIfNeeded: rowid)

On a 64-bit machine, the truncatingIfNeeded will do nothing; you will just get an Int (which is the same size as an Int64 anyway).

On a 32-bit machine, this will throw away the top 32 bits, but it they are all zeroes, then you haven't lost any data. So as long as your value will fit into a 32-bit Int, you can do this without losing data. If your value is outside of the range Int32.min ... Int32.max, this will change the value of the Int64 into something that fits in a 32-bit Int, but it will not crash.


You can see how this works in a Playground. Since Int in a Playground is a 64-bit Int, you can explicitly use an Int32 to simulate the behavior of a 32-bit system.

let i: Int64 = 12345678901  // value bigger than maximum 32-bit Int

let j = Int32(truncatingIfNeeded: i)  // j = -539,222,987
let k = Int32(i)                        // crash!

Update for Swift 3/4

In addition to init(truncatingIfNeeded:) which still works, Swift 3 introduces failable initializers to safely convert one integer type to another. By using init?(exactly:) you can pass one type to initialize another, and it returns nil if the initialization fails. The value returned is an optional which must be unwrapped in the usual ways.

For example:

let i: Int64 = 12345678901

if let j = Int32(exactly: i) {
    print("\(j) fits into an Int32")
} else {
    // the initialization returned nil
    print("\(i) is too large for Int32")
}

This allows you to apply the nil coalescing operator to supply a default value if the conversion fails:

// return 0 if rowid is too big to fit into an Int on this device
return Int(exactly: rowid) ?? 0
vacawama
  • 150,663
  • 30
  • 266
  • 294
  • 2
    In current Swift 3 and 4 versions, `Int(truncatingBitPattern:)` doesn't exist, and `Int(truncating:)` takes an `NSNumber` so isn't applicable to `Int64`. The correct call is now `Int(truncatingIfNeeded:)`. I've added a separate answer to clarify. – jedwidz Sep 13 '18 at 04:56
  • @jedwidz, thanks for the heads up. I fixed the answer. – vacawama Sep 13 '18 at 11:23
5

If you're confident that the Int64 value can be represented exactly as an Int, use Int(truncatingIfNeeded:), e.g.:

let salary: Int64 = 100000
let converted = Int(truncatingIfNeeded: salary)

For builds targeting 32-bit devices, the range for Int is limited to -2147483648 through 2147483647, the same as Int32. Values outside of that range will quietly have their high-order bits discarded. This results in garbage, often of the opposite sign.

If the value might be out of range, and you want to handle that condition, use Int(exactly:), e.g.:

if let converted = Int(exactly: salary) {
    // in range
    ... converted ...
} else {
    // out-of-range
    ...
}

In the specific case of rowids, using Int64 rather than Int was a deliberate API design choice, and truncating to Int could be a bug.

jedwidz
  • 384
  • 5
  • 7
2

It's a pain in the ass. It's monumentally stupider than in Objective-C. But here is how you do it.

If you are using a UInt64, you do it like this.

let thatVarYouWantToConvert: UInt64 = 1
let myInt: Int = Int(exactly:thatVarYouWantToConvert ?? 0)

If you are using a UInt64?, you do it like this.

let thatVarYouWantToConvert: UInt64? = 1
let myInt: Int = Int(exactly:thatVarYouWantToConvert ?? 0) ?? 0

Like I said, "It's monumentally stupider than in Objective-C."

Tested in Swift 4.2, Xcode 10.2.1

Alex Zavatone
  • 4,106
  • 36
  • 54
-1

As a matter of fact I've been working with this framework too, and basically I just used the opposite solution. Whenever you see that types don't match just do

Int64(yourInt) 

(tested with Xcode 7, Swift 2.0)

Nic
  • 6,211
  • 10
  • 46
  • 69
  • This may result in slower performance on older phones and reduced library compatibility. If you're converting back and forth for no reason, that'll introduce a lot of unnecessary overheard, memory usage and potential bugs to your code. It's probably best to just convert to an `Int` *if* you know there's no possible scenario in which your app will have to deal with numbers larger than 2^31-1. If you might have to use numbers larger then that, use `Int64`. Just don't convert at runtime unless you have to. – Ben Aubin Jul 04 '18 at 21:36