Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Random class with seed support. #1540

Merged
merged 1 commit into from
Jul 22, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ Whitespace conventions:
* `Hash#to_proc`
* `Struct#dig`
- Added safe navigator (`&.`) support. (#1532)
- Added Random class with seed support. The following methods were reworked to use it:
* `Kernel.rand`
* `Kernel.srand`
* `Array#shuffle`
* `Array#shuffle!`
* `Array#sample`


### Changed
Expand Down
2 changes: 1 addition & 1 deletion opal/corelib/array.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1901,7 +1901,7 @@ def shuffle!(rng = undefined)
}
}
else {
j = Math.floor(Math.random() * i);
j = #{rand(`i`)};
}

tmp = self[--i];
Expand Down
27 changes: 17 additions & 10 deletions opal/corelib/kernel.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1061,19 +1061,24 @@ def raise(exception = undefined, string = nil, _backtrace = nil)
def rand(max = undefined)
%x{
if (max === undefined) {
return Math.random();
return #{Random::DEFAULT.rand};
}
else if (max.$$is_range) {
var min = max.begin, range = max.end - min;
if(!max.exclude) range++;

return self.$rand(range) + min;
}
else {
return Math.floor(Math.random() *
Math.abs(#{Opal.coerce_to max, Integer, :to_int}));
if (max.$$is_number) {
if (max < 0) {
max = Math.abs(max);
}

if (max % 1 !== 0) {
max = max.$to_i();
}

if (max === 0) {
max = undefined;
}
}
}
Random::DEFAULT.rand(max)
end

def respond_to?(name, include_all = false)
Expand Down Expand Up @@ -1148,7 +1153,9 @@ def sleep(seconds = nil)

alias sprintf format

alias srand rand
def srand(seed = Random.new_seed)
Random.srand(seed)
end

def String(str)
Opal.coerce_to?(str, String, :to_str) ||
Expand Down
117 changes: 117 additions & 0 deletions opal/corelib/random.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
require 'corelib/random/seedrandom.js'

class Random
attr_reader :seed, :state

def initialize(seed = Random.new_seed)
seed = Opal.coerce_to!(seed, Integer, :to_int)
@state = seed
reseed(seed)
end

def reseed(seed)
@seed = seed
`self.$rng = new Math.seedrandom(seed);`
end

`var $seed_generator = new Math.seedrandom('opal', { entropy: true });`

def self.new_seed
%x{
return Math.abs($seed_generator.int32());
}
end

def self.rand(limit = undefined)
DEFAULT.rand(limit)
end


def self.srand(n = Random.new_seed)
n = Opal.coerce_to!(n, Integer, :to_int)

previous_seed = DEFAULT.seed
DEFAULT.reseed(n)
previous_seed
end

DEFAULT = new(new_seed)

def ==(other)
return false unless Random === other

seed == other.seed && state == other.state
end

def bytes(length)
length = Opal.coerce_to!(length, Integer, :to_int)
length
.times
.map { rand(255).chr }
.join
.encode(Encoding::ASCII_8BIT)
end

def rand(limit = undefined)
%x{
function randomFloat() {
self.state++;
return self.$rng.quick();
}

function randomInt() {
return Math.floor(randomFloat() * limit);
}

function randomRange() {
var min = limit.begin,
max = limit.end;

if (min === nil || max === nil) {
return nil;
}

var length = max - min;

if (length < 0) {
return nil;
}

if (length === 0) {
return min;
}

if (max % 1 === 0 && min % 1 === 0 && !limit.exclude) {
length++;
}

return self.$rand(length) + min;
}

if (limit == null) {
return randomFloat();
} else if (limit.$$is_range) {
return randomRange();
} else if (limit.$$is_number) {
if (limit <= 0) {
#{raise ArgumentError, "invalid argument - #{limit}"}
}

if (limit % 1 === 0) {
// integer
return randomInt();
} else {
return randomFloat() * limit;
}
} else {
limit = #{Opal.coerce_to!(limit, Integer, :to_int)};

if (limit <= 0) {
#{raise ArgumentError, "invalid argument - #{limit}"}
}

return randomInt();
}
}
end
end
8 changes: 8 additions & 0 deletions opal/corelib/random/seedrandom.js.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class Random
%x{
/* jshint ignore:start */
/* seedrandom.min.js 2.4.1 https://github.com/davidbau/seedrandom + PR https://github.com/davidbau/seedrandom/pull/36 */
!function(a,b){function c(c,j,k){var n=[];j=1==j?{entropy:!0}:j||{};var s=g(f(j.entropy?[c,i(a)]:null==c?h():c,3),n),t=new d(n),u=function(){for(var a=t.g(m),b=p,c=0;a<q;)a=(a+c)*l,b*=l,c=t.g(1);for(;a>=r;)a/=2,b/=2,c>>>=1;return(a+c)/b};return u.int32=function(){return 0|t.g(4)},u.quick=function(){return t.g(4)/4294967296},u.double=u,g(i(t.S),a),(j.pass||k||function(a,c,d,f){return f&&(f.S&&e(f,t),a.state=function(){return e(t,{})}),d?(b[o]=a,c):a})(u,s,"global"in j?j.global:this==b,j.state)}function d(a){var b,c=a.length,d=this,e=0,f=d.i=d.j=0,g=d.S=[];for(c||(a=[c++]);e<l;)g[e]=e++;for(e=0;e<l;e++)g[e]=g[f=s&f+a[e%c]+(b=g[e])],g[f]=b;(d.g=function(a){for(var b,c=0,e=d.i,f=d.j,g=d.S;a--;)b=g[e=s&e+1],c=c*l+g[s&(g[e]=g[f=s&f+b])+(g[f]=b)];return d.i=e,d.j=f,c})(l)}function e(a,b){return b.i=a.i,b.j=a.j,b.S=a.S.slice(),b}function f(a,b){var c,d=[],e=typeof a;if(b&&"object"==e)for(c in a)if(a.hasOwnProperty(c))try{d.push(f(a[c],b-1))}catch(a){}return d.length?d:"string"==e?a:a+"\0"}function g(a,b){for(var c,d=a+"",e=0;e<d.length;)b[s&e]=s&(c^=19*b[s&e])+d.charCodeAt(e++);return i(b)}function h(){try{if(j)return i(j.randomBytes(l));var b=new Uint8Array(l);return(k.crypto||k.msCrypto).getRandomValues(b),i(b)}catch(b){var c=k.navigator,d=c&&c.plugins;return[+new Date,k,d,k.screen,i(a)]}}function i(a){return String.fromCharCode.apply(0,a)}var j,k=this,l=256,m=6,n=52,o="random",p=b.pow(l,m),q=b.pow(2,n),r=2*q,s=l-1;if(b["seed"+o]=c,g(b.random(),a),"object"==typeof module&&module.exports){module.exports=c;try{j=require("crypto")}catch(a){}}else"function"==typeof define&&define.amd&&define(function(){return c})}([],Math);
/* jshint ignore:end */
}
end
1 change: 1 addition & 0 deletions opal/opal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@
require 'corelib/dir'
require 'corelib/file'
require 'corelib/process'
require 'corelib/random'

require 'corelib/unsupported'
3 changes: 3 additions & 0 deletions spec/filters/unsupported/bignum.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,7 @@
fails "Marshal.load for a Integer loads an Integer -2361183241434822606847"
fails "Marshal.dump with a Bignum dumps a Bignum"
fails "Marshal.dump with a Bignum dumps a Bignum"
fails "Random.new_seed returns a Bignum"
fails "Random.new uses a random seed value if none is supplied"
fails "Random#rand with Bignum typically returns a Bignum"
end
4 changes: 4 additions & 0 deletions spec/filters/unsupported/random.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
opal_filter "Random" do
fails "Random#bytes returns the same numeric output for a given seed accross all implementations and platforms"
fails "Random#bytes returns the same numeric output for a given huge seed accross all implementations and platforms"
end
4 changes: 0 additions & 4 deletions spec/opal/core/kernel/rand_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@
r.to_a.include?(rand(r)).should == true
end

it "should convert negative number and convert to integer" do
rand(-0.1).should == 0
end

it "returns a numeric in opal" do
rand.should be_kind_of(Numeric)
rand(77).should be_kind_of(Numeric)
Expand Down
1 change: 1 addition & 0 deletions spec/ruby_specs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ ruby/core/proc
ruby/core/range
ruby/core/rational
ruby/core/regexp
ruby/core/random

ruby/core/string
!ruby/core/string/crypt_spec
Expand Down