28

I've written a simple Bag class. A Bag is filled with a fixed ratio of Temperature enums. It allows you to grab one at random and automatically refills itself when empty. It looks like this:

class Bag {
    var items = Temperature[]()

    init () {
        refill()
    }

    func grab()-> Temperature {
        if items.isEmpty {
            refill()
        }

        var i = Int(arc4random()) % items.count
        return items.removeAtIndex(i)
    }

    func refill() {
        items.append(.Normal)

        items.append(.Hot)
        items.append(.Hot)

        items.append(.Cold)
        items.append(.Cold)
    }
}

The Temperature enum looks like this:

enum Temperature: Int {
    case Normal, Hot, Cold
}

My GameScene:SKScene has a constant instance property bag:Bag. (I've tried with a variable as well.) When I need a new temperature I call bag.grab(), once in didMoveToView and when appropriate in touchesEnded.

Randomly this call crashes on the if items.isEmpty line in Bag.grab(). The error is EXC_BAD_INSTRUCTION. Checking the debugger shows items is size=1 and [0] = (AppName.Temperature) <invalid> (0x10).

Edit Looks like I don't understand the debugger info. Even valid arrays show size=1 and unrelated values for [0] =. So no help there.

I can't get it to crash isolated in a Playground. It's probably something obvious but I'm stumped.

hichris123
  • 10,145
  • 15
  • 56
  • 70
Shaun Inman
  • 1,968
  • 1
  • 19
  • 28
  • Not that I can tell. It doesn't appear to be called more frequently than expected. This is weird. I can `println("items in bag: \(items.count) (\(items.isEmpty)) \(items)")` right before the `if items.isEmpty` and it prints the expected values: `items in bag:5 (false) [(Enum Value), (Enum Value), (Enum Value), (Enum Value), (Enum Value)]` – Shaun Inman Jun 06 '14 at 18:05
  • I could be wrong, hence I am posting as comment. It seems like you are appending to the array an `Int` rather then `Temperature`, which is the type of the array. In addition, with raw value in enum, you have to do `case Normal = 1, Hot, Cold`. – Unheilig Jun 06 '14 at 18:09
  • I've tried adding `case Normal=0, Hot, Cold`. No effect. `println(items[0])` returns `(Enum Value)` not `(Int)` or an actual Int value so I'm not sure that's it. – Shaun Inman Jun 06 '14 at 18:22
  • In fact the problem still presents itself even if I change all references to `Temperature` to `Int`. – Shaun Inman Jun 06 '14 at 18:24

7 Answers7

56

Function arc4random returns an UInt32. If you get a value higher than Int.max, the Int(...) cast will crash.

Using

Int(arc4random_uniform(UInt32(items.count)))

should be a better solution.

(Blame the strange crash messages in the Alpha version...)

Sulthan
  • 128,090
  • 22
  • 218
  • 270
  • 4
    That was totally it. Thank you! Just need to cast `items.count` to `UInt32`. Debugger was highlighting the wrong line. Can I get a FFFFFFUUUUUU? – Shaun Inman Jun 06 '14 at 18:38
  • Missing closing `)` it should read `Int(arc4random_uniform((UInt32(items.count))))` – djthoms Jul 10 '14 at 22:48
  • Incidentally, if you need a UInt64 sized arc4random see [this answer](http://stackoverflow.com/a/25129039/1880199). – jstn Aug 05 '14 at 15:35
2

I found that the best way to solve this is by using rand() instead of arc4random()

the code, in your case, could be:

var i = Int(rand()) % items.count
vyudi
  • 828
  • 2
  • 9
  • 22
  • The problem with `rand()` is, that it is a much poorer random number generator (the quality of the random numbers are usually much worse, see man page, it says it's a **bad random number generator**) and you need to initialize it prior to using it (`srand()` or `radndev()`) as w/o calling that the values are not random at all but will always be the same. The replacement for `rand()` is `random()`, which also suffers by the need to initialize it first, therefor the generated numbers are much more random. But `arc4random()` is actually the best generator and doesn't need an initialization. – Mecki Sep 29 '16 at 15:16
1

This method will generate a random Int value between the given minimum and maximum

func randomInt(min: Int, max:Int) -> Int {
    return min + Int(arc4random_uniform(UInt32(max - min + 1)))
}

The crash that you were experiencing is due to the fact that Swift detected a type inconsistency at runtime. Since Int != UInt32 you will have to first type cast the input argument of arc4random_uniform before you can compute the random number.

Groot
  • 13,943
  • 6
  • 61
  • 72
1

Swift doesn't allow to cast from one integer type to another if the result of the cast doesn't fit. E.g. the following code will work okay:

let x = 32
let y = UInt8(x)

Why? Because 32 is a possible value for an int of type UInt8. But the following code will fail:

let x = 332
let y = UInt8(x)

That's because you cannot assign 332 to an unsigned 8 bit int type, it can only take values 0 to 255 and nothing else.

When you do casts in C, the int is simply truncated, which may be unexpected or undesired, as the programmer may not be aware that truncation may take place. So Swift handles things a bit different here. It will allow such kind of casts as long as no truncation takes place but if there is truncation, you get a runtime exception. If you think truncation is okay, then you must do the truncation yourself to let Swift know that this is intended behavior, otherwise Swift must assume that is accidental behavior.

This is even documented (documentation of UnsignedInteger):

Convert from Swift's widest unsigned integer type, trapping on overflow.

And what you see is the "overflow trapping", which is poorly done as, of course, one could have made that trap actually explain what's going on.

Assuming that items never has more than 2^32 elements (a bit more than 4 billion), the following code is safe:

var i = Int(arc4random() % UInt32(items.count))

If it can have more than 2^32 elements, you get another problem anyway as then you need a different random number function that produces random numbers beyond 2^32.

Mecki
  • 125,244
  • 33
  • 244
  • 253
1

This crash is only possible on 32-bit systems. Int changes between 32-bits (Int32) and 64-bits (Int64) depending on the device architecture (see the docs).

UInt32's max is 2^32 − 1. Int64's max is 2^63 − 1, so Int64 can easily handle UInt32.max. However, Int32's max is 2^31 − 1, which means UInt32 can handle numbers greater than Int32 can, and trying to create an Int32 from a number greater than 2^31-1 will create an overflow.

I confirmed this by trying to compile the line Int(UInt32.max). On the simulators and newer devices, this compiles just fine. But I connected my old iPod Touch (32-bit device) and got this compiler error:

Integer overflows when converted from UInt32 to Int

Xcode won't even compile this line for 32-bit devices, which is likely the crash that is happening at runtime. Many of the other answers in this post are good solutions, so I won't add or copy those. I just felt that this question was missing a detailed explanation of what was going on.

keithbhunter
  • 12,258
  • 4
  • 33
  • 58
0

This will automatically create a random Int for you:

var i = random() % items.count

i is of Int type, so no conversion necessary!

NatashaTheRobot
  • 6,879
  • 4
  • 32
  • 27
0

You can use

Int(rand())

To prevent same random numbers when the app starts, you can call srand()

srand(UInt32(NSDate().timeIntervalSinceReferenceDate))
let randomNumber: Int = Int(rand()) % items.count
Ivan
  • 305
  • 4
  • 9
  • If anything, use at least `random()` and not `rand()`. To quote from the man page of `rand`: "**bad random number generator**" Why would use a bad one if you can use a good one (`random()` is much better, yet `arc4random()` is far better than either one). – Mecki Sep 29 '16 at 15:18