class Rubinius::Randomizer def self.allocate Rubinius.primitive :randomizer_allocate raise PrimitiveFailure, "Randomizer.allocate primitive failed" end def initialize self.seed = generate_seed end private :initialize attr_reader :seed def seed=(new_seed) set_seed new_seed @seed = new_seed end def set_seed(new_seed) Rubinius.primitive :randomizer_seed raise PrimitiveFailure, "Randomizer#seed primitive failed" end def swap_seed(new_seed) old_seed = self.seed self.seed = new_seed old_seed end def random(limit) if undefined.equal?(limit) random_float else if limit.kind_of?(Range) if time_value?(limit.min) random_time_range(limit) else random_range(limit) end elsif limit.kind_of?(Float) raise ArgumentError, "invalid argument - #{limit}" if limit <= 0 random_float * limit elsif limit.is_a?(Integer) raise ArgumentError, "invalid argument - #{limit}" if limit <= 0 random_integer(limit - 1) elsif limit.respond_to?(:to_f) raise ArgumentError, "invalid argument - #{limit}" if limit <= 0 random_float * limit else limit_int = Rubinius::Type.coerce_to limit, Integer, :to_int raise ArgumentError, "invalid argument - #{limit}" if limit_int <= 0 random_integer(limit_int - 1) end end end # Generate a random Float, in the range 0...1.0 def random_float Rubinius.primitive :randomizer_rand_float raise PrimitiveFailure, "Randomizer#rand_float primitive failed" end # Generate a random Integer, in the range 0...limit def random_integer(limit) Rubinius.primitive :randomizer_rand_int raise PrimitiveFailure, "Randomizer#rand_int primitive failed" end def random_range(limit) return nil if limit.first.nil? || limit.last.nil? min, max = limit.last.coerce(limit.first) diff = max - min return if diff < 0 return min if diff == 0 diff += 1 if max.kind_of?(Integer) && !limit.exclude_end? random(diff) + min end def generate_seed Rubinius.primitive :randomizer_gen_seed raise PrimitiveFailure, "Randomizer#gen_seed primitive failed" end ## # Returns a random value from a range made out of Time, Date or DateTime # instances. # # @param [Range] range # @return [Time|Date|DateTime] # def random_time_range(range) min = time_to_float(range.min) max = time_to_float(range.max) time = Time.at(random(min..max)) if Object.const_defined?(:DateTime) && range.min.is_a?(DateTime) time = time.to_datetime elsif Object.const_defined?(:DateTime) && range.min.is_a?(Date) time = time.to_date end return time end ## # Casts a Time/Date/DateTime instance to a Float. # # @param [Time|Date|DateTime] input # @return [Float] # def time_to_float(input) return input.respond_to?(:to_time) ? input.to_time.to_f : input.to_f end ## # Checks if a given value is a Time, Date or DateTime object. # # @param [Mixed] input # @return [TrueClass|FalseClass] # def time_value?(input) return input.is_a?(Time) || (Object.const_defined?(:Date) && input.is_a?(Date)) end end class Random def self.new_seed Thread.current.randomizer.generate_seed end def self.srand(seed=undefined) if undefined.equal? seed seed = Thread.current.randomizer.generate_seed end seed = Rubinius::Type.coerce_to seed, Integer, :to_int Thread.current.randomizer.swap_seed seed end def self.rand(limit=undefined) Thread.current.randomizer.random(limit) end def initialize(seed=undefined) @randomizer = Rubinius::Randomizer.new if !undefined.equal?(seed) @randomizer.swap_seed seed.to_int end end private :initialize def rand(limit=undefined) @randomizer.random(limit) end def seed @randomizer.seed end def state @randomizer.seed end private :state def ==(other) return false unless other.kind_of?(Random) seed == other.seed end # Returns a random binary string. # The argument size specified the length of the result string. def bytes(length) length = Rubinius::Type.coerce_to length, Integer, :to_int s = '' i = 0 while i < length s << @randomizer.random_integer(255).chr i += 1 end s end end