Skip to content

Commit

Permalink
Implement Random for BigInt (#6687)
Browse files Browse the repository at this point in the history
* Implement Random for BigInt

Fixes #5647

* spec typo fix

* Move to big_int.cr

* Use to_s in spec

* Remove special case of zero because it will be removed in #6686
  • Loading branch information
oprypin authored and RX14 committed Sep 10, 2018
1 parent c772db8 commit ccbfd8b
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 0 deletions.
32 changes: 32 additions & 0 deletions spec/std/random_spec.cr
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
require "big"
require "spec"

private class TestRNG(T)
Expand Down Expand Up @@ -38,6 +39,21 @@ describe "Random" do
end
end

it "limited BigInt" do
rand(1.to_big_i).should eq(0.to_big_i)

x = rand(2.to_big_i)
x.should be >= 0
x.should be < 2
end

it "limited large BigInt" do
max = "1234567890123456789012345".to_big_i
x = rand(max)
x.should be >= 0
x.should be < max
end

it "float number" do
x = rand
x.should be >= 0
Expand Down Expand Up @@ -86,6 +102,16 @@ describe "Random" do
end
end

it "does with BigInt range" do
[1.to_big_i...2.to_big_i,
-"1234567890123456789012345".to_big_i...7.to_big_i,
-7.to_big_i..."1234567890123456789012345".to_big_i].each do |range|
x = rand(range)
x.should be >= range.begin
x.should be < range.end
end
end

it "does with inclusive range of floats" do
rand(1.0..1.0).should eq(1.0)
x = rand(1.8..3.2)
Expand Down Expand Up @@ -115,6 +141,12 @@ describe "Random" do
expect_raises ArgumentError, "Invalid range for rand: 1..0" do
rand(1..0)
end
expect_raises ArgumentError, "Invalid range for rand: #{1.to_big_i...1.to_big_i}" do
rand(1.to_big_i...1.to_big_i)
end
expect_raises ArgumentError, "Invalid range for rand: #{1.to_big_i..0.to_big_i}" do
rand(1.to_big_i..0.to_big_i)
end
expect_raises ArgumentError, "Invalid range for rand: 1.0...1.0" do
rand(1.0...1.0)
end
Expand Down
43 changes: 43 additions & 0 deletions src/big/big_int.cr
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require "c/string"
require "big"
require "random"

# A `BigInt` can represent arbitrarily large integers.
#
Expand Down Expand Up @@ -573,6 +574,48 @@ module Math
end
end

module Random
private def rand_int(max : BigInt) : BigInt
# This is a copy of the algorithm in random.cr but with fewer special cases.
unless max > 0
raise ArgumentError.new "Invalid bound for rand: #{max}"
end

rand_max = BigInt.new(1) << (sizeof(typeof(next_u))*8)
needed_parts = 1
while rand_max < max && rand_max > 0
rand_max <<= sizeof(typeof(next_u))*8
needed_parts += 1
end

limit = rand_max / max * max

loop do
result = BigInt.new(next_u)
(needed_parts - 1).times do
result <<= sizeof(typeof(next_u))*8
result |= BigInt.new(next_u)
end

# For a uniform distribution we may need to throw away some numbers.
if result < limit
return result % max
end
end
end

private def rand_range(range : Range(BigInt, BigInt)) : BigInt
span = range.end - range.begin
unless range.excludes_end?
span += 1
end
unless span > 0
raise ArgumentError.new "Invalid range for rand: #{range}"
end
range.begin + rand_int(span)
end
end

# :nodoc:
struct Crystal::Hasher
private HASH_MODULUS_INT_P = BigInt.new((1_u64 << HASH_BITS) - 1)
Expand Down

0 comments on commit ccbfd8b

Please sign in to comment.