Go: proposal: math/rand: add function for random bool

Created on 13 Feb 2018  路  11Comments  路  Source: golang/go

I want to be able to call rand.Bool() and receive a pseudo-random bool when I import math/random

Proposal Proposal-Hold

Most helpful comment

I don't have an opinion on this, but I have a small argument in favour.

The way the rand package uses the randomness source is quite intricate. A lot of the functions do not use the generator directly, but call other functions and then manipulate the input in some way to build a value of the requested type. For this reason, a lot of the "obvious" ways to get a random bool result in very deep call stacks.

Take for example the simple method Ian proposed above: rand.Intn(2) == 0. This is what happens:

We call r.Intn(2), which checks that the argument fits an int32, and then calls r.Int31n(2), which checks that the argument is a power of two, and then calls r.Int31(), which calls r.Int63() and then shifts the result and cast it into an int32.

This is what the call stack looks like:

  math/rand.(*Rand).Int63(0xc42007a1e0, 0xff)
    math/rand/rand.go:84 +0x22
  math/rand.(*Rand).Int31(0xc42007a1e0, 0xc42002def0)
    math/rand/rand.go:101 +0x2b
  math/rand.(*Rand).Int31n(0xc42007a1e0, 0x13b2c00000002, 0x390ffd3f)
    math/rand/rand.go:133 +0xb4
  math/rand.(*Rand).Intn(0xc42007a1e0, 0x2, 0x5a832bc1)
    math/rand/rand.go:174 +0x45
  math/rand.Intn(0x2, 0x4a91e1)
    math/rand/rand.go:331 +0x37 

An implementation that directly calls the function that is the nearest to the generator: Int63() % 2 == 0, on the other hand, has a much smaller call stack, and it about 20% faster on my machine in a quick benchmark I wrote.

So while it's true that it is easy to write an expression that correctly returns a random boolean, doing so in a way that is performant and does not result in a deep call stack is not as immediate as it looks.

All 11 comments

Change https://golang.org/cl/93517 mentions this issue: add function for bool in math/rand

How about math.IntN(2) == 0? Is it really worth adding a new function for this?

Yes I believe it is worth it. rand.Intn(2) == 0 is not as expressive as rand.Bool(). This is more clear

How about math.IntN(2) == 0? Is it really worth adding a new function for this?

I'd argue that it's worth doing. It's not solving a hard problem but it's making it easier to read. Similar to how sync.Waitgroup.Done() is an just an extra 1 line function but makes it more expressive to read for the programmer.

I don't have an opinion on this, but I have a small argument in favour.

The way the rand package uses the randomness source is quite intricate. A lot of the functions do not use the generator directly, but call other functions and then manipulate the input in some way to build a value of the requested type. For this reason, a lot of the "obvious" ways to get a random bool result in very deep call stacks.

Take for example the simple method Ian proposed above: rand.Intn(2) == 0. This is what happens:

We call r.Intn(2), which checks that the argument fits an int32, and then calls r.Int31n(2), which checks that the argument is a power of two, and then calls r.Int31(), which calls r.Int63() and then shifts the result and cast it into an int32.

This is what the call stack looks like:

  math/rand.(*Rand).Int63(0xc42007a1e0, 0xff)
    math/rand/rand.go:84 +0x22
  math/rand.(*Rand).Int31(0xc42007a1e0, 0xc42002def0)
    math/rand/rand.go:101 +0x2b
  math/rand.(*Rand).Int31n(0xc42007a1e0, 0x13b2c00000002, 0x390ffd3f)
    math/rand/rand.go:133 +0xb4
  math/rand.(*Rand).Intn(0xc42007a1e0, 0x2, 0x5a832bc1)
    math/rand/rand.go:174 +0x45
  math/rand.Intn(0x2, 0x4a91e1)
    math/rand/rand.go:331 +0x37 

An implementation that directly calls the function that is the nearest to the generator: Int63() % 2 == 0, on the other hand, has a much smaller call stack, and it about 20% faster on my machine in a quick benchmark I wrote.

So while it's true that it is easy to write an expression that correctly returns a random boolean, doing so in a way that is performant and does not result in a deep call stack is not as immediate as it looks.

We call r.Intn(2), which checks that the argument fits an int32, and then call calls r.Int31n(2), which checks that the argument is a power of two, and then calls r.Int31(), which calls r.Int63() and then shifts the result and cast it into an int32.

All of those checks and conversions should be easy to inline and constant-fold away.
If they are not inlined and constant-folded, that is arguably a compiler bug, not an API bug.

If you're used to rand.Intn(2) == 0, which you are after you've used it 2-3x, it's as expressive as rand.Bool() would be.

Leaving this for math/rand v2 discussions. Please just use r.Intn(2) == 0 for now.

/cc @josharian too

Whereas I agree that as defined this does not add much (except, maybe, avoid biases in known bad bits of the PRNG output), I would suggest considering adding a probability to that function:

// Bool returns the outcome of a Bernoulli random variable
// with the specified probability of returning true.
// Bool panics if p < 0 or p > 1.
func Bool(pTrue float64) bool

while this has an obvious implementation (return Float64() < p), this could benefit from careful construction to enable constant propagation to completely avoid using floating points at runtime (e.g. convert the constant from the float64 range [0 1] to [0 MaxUint64] and then return Uint64() < pUint64)

This would be useful for e.g. sampling events and approximate counting. Obviously, Bool(0.5) would be equivalent to the OP.

Was this page helpful?
0 / 5 - 0 ratings