0

I'm playing with an algorithm which uses random numbers. It would be nice to be able to maximize the randomness I can get while keeping the number a nice reasonably-performant integer, so ideally they'd be in the range Fixnum::MIN .. Fixnum::MAX, but 0..Fixnum::MAX ought to be fine too.

OH WAIT. Those constants aren't actually things that exist. So when you read that Random.rand returns a float unless you pass it an integer argument the only obvious course of action is to resort to terrible hacks like these.

Is there any more-idiomatic way to get a random integer in Ruby, or does Yukihiro just expect me to make my code hideous and duplicate dubious integer-size exponentiation if I want this sort of capability?

Community
  • 1
  • 1
  • 3
    You mean a random integer of unbounded size? That's almost never what you actually need. What is your actual upper bound? – Robert Harvey Sep 22 '14 at 18:59
  • @RobertHarvey - that's **exactly** what the textbook implementation calls for in this algorithm: a bunch of elements get random weights which are compared to each other for Reasons (no other math is performed on these objects). They could be strings for all I care, but it'd be nice if they're fast. Collisions do not impact the correctness of the algorithm and occasional collisions only impact performance trivially, but at the same time there's no need to actually make the pool of random comparables any smaller than necessary, and there may be ALL_YOUR_RAM numbers of elements involved. –  Sep 22 '14 at 19:51
  • So what is your actual upper bound? It can't be infinity. – Robert Harvey Sep 22 '14 at 19:53
  • @fennec Just use `rand` - Ruby floats are double precision, so you'll have far more entropy than you would in the range of valid fixnums. – Chris Heald Sep 22 '14 at 20:01
  • @RobertHarvey The upper bound can't be larger than the size of Fixnum on the platform if it's to be a Fixnum rather than a Bignum. So, there *is* a platform-dependent minimum and maximum size for Fixnums (tied to the size of longs, I believe), but Ruby doesn't currently make that value directly-accessible AFAIK. – Todd A. Jacobs Sep 22 '14 at 20:04
  • @RobertHarvey My theoretical upper bound is INFINITY. But getting bignums would impact performance so I'd like to use an upper bound of `Fixnum::MAX`. –  Sep 22 '14 at 20:07
  • 2
    I suspect the actual answer here is "use the terrible hack, and get on with life." – Robert Harvey Sep 22 '14 at 20:07
  • Why don't you use the constants defined in http://stackoverflow.com/questions/535721/ruby-max-integer and then use rand(Fixnum::MIN..Fixnum::MAX) ? There's been a feature request to add FixNum::MIN and MAX in Ruby: https://www.ruby-forum.com/topic/4408751 but it seems there were some issues... – daremkd Sep 22 '14 at 20:21
  • 1
    @daremkd AFAIK, Kernel#rand didn't always take a Range object as an argument, but it seems to work now on Ruby 2.1.2. I went a slightly different way with my answer, but your range seems to work fine over several million trials. See [this gist](https://gist.github.com/CodeGnome/ebfaeaba98f7a2569d54) for the test code. – Todd A. Jacobs Sep 22 '14 at 20:28

2 Answers2

1

This should get you a fairly large number of sample integers:

require "securerandom"
exponent = rand(1..15)
puts (SecureRandom.random_number * 10**exponent).to_i

a faster working algo that produces same or possibly better randomness:

r = Random.new
exponent = rand(1..15)
puts (r.rand * 10**exponent).to_i

or even a simpler way:

FIXNUM_MAX = (2**(0.size * 8 -2) -1)
FIXNUM_MIN = -(2**(0.size * 8 -2))
p rand(FIXNUM_MIN..FIXNUM_MAX)
Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
daremkd
  • 8,244
  • 6
  • 40
  • 66
  • So your last example is wrong and frequently returns `Bignum` objects. `p (1..5).map { rand(0..2**64-1).class }` => `[Fixnum, Bignum, Bignum, Bignum, Bignum]` –  Sep 22 '14 at 19:54
  • Sorry, didn't read you were only looking for fixnums, edited this question so now the last example will give you all numbers from the minimum to the maximum fixnum. – daremkd Sep 22 '14 at 20:01
1

Random Values from 0..FIXNUM_MAX

When Fixnum overflows, Ruby will just convert to Bignum. However, this related answer shows how to calculate the minimum and maximum values of Fixnum for your platform. Using that as a starting point, you can get a positive integer in the desired range with:

FIXNUM_MAX = (2**(0.size * 8 -2) -1)
Random.rand FIXNUM_MAX

Negative Integers

If you insist on having negative numbers too, then the following may be "close enough" for your purposes, even though FIXNUM_MIN.abs == FIXNUM_MAX may be false on your platform:

FIXNUM_MAX = (2**(0.size * 8 -2) -1)
random_num = Random.rand FIXNUM_MAX
random_num.even? ? random_num : random_num * -1

See Also

Community
  • 1
  • 1
Todd A. Jacobs
  • 81,402
  • 15
  • 141
  • 199