# mruby-random

mruby-random is an mrbgem that provides pseudo-random number generation facilities for mruby.

## Features

- Provides `Kernel#rand` method for generating pseudo-random numbers.
- Supports generating random numbers within a specific range.
- Allows setting a seed for reproducible random number sequences using `Kernel#srand`.

## Global Random Number Generation

### Generating a random number

To generate a pseudo-random floating-point number between 0.0 (inclusive) and 1.0 (exclusive):

```ruby
r = rand
p r # => 0.31415926535
```

To generate a pseudo-random integer number between 0 (inclusive) and a given maximum integer (exclusive):

```ruby
r = rand(100)
p r # => 42
```

To generate a pseudo-random integer within a given `Range`:

```ruby
r = rand(10..20) # or rand(10...20)
p r # => 15 (e.g., between 10 and 20, or 10 and 19)
```

### Seeding the random number generator

To initialize the pseudo-random number generator with a specific seed:

```ruby
srand(12345)
p rand(100) # => 81
p rand(100) # => 81 (if you re-seed with srand(12345) again)

# Using the same seed will produce the same sequence of random numbers
srand(12345)
p rand(100) # => 81
srand(12345)
p rand(100) # => 81
```

## The `Random` Class

Besides the global `Kernel#rand` and `Kernel#srand` methods, `mruby-random` also provides a `Random` class for managing separate random number generators.

### Creating an Instance

You can create a new instance of the `Random` class with a system-generated seed:

```ruby
rng = Random.new
p rng.rand(100)
```

Or you can provide a specific seed:

```ruby
rng = Random.new(12345)
p rng.rand(100) # => 81
```

### Instance Methods

#### `rand`

The `rand` instance method behaves similarly to `Kernel#rand`, but operates on the specific `Random` instance.

- Called with no arguments, it returns a pseudo-random floating-point number between 0.0 (inclusive) and 1.0 (exclusive).

  ```ruby
  rng = Random.new
  p rng.rand # => 0.123456789
  ```

- Called with an integer `max` argument, it returns a pseudo-random integer between 0 (inclusive) and `max` (exclusive).

  ```ruby
  rng = Random.new
  p rng.rand(50) # => 23
  ```

- Called with a `Range` argument (`min..max` or `min...max`), it returns a pseudo-random integer within that range (inclusive of `min`, and inclusive or exclusive of `max` depending on the range type).

  ```ruby
  rng = Random.new
  p rng.rand(10..20) # => 15 (between 10 and 20, inclusive)
  p rng.rand(10...20) # => 12 (between 10 and 19, inclusive)
  ```

#### `srand`

The `srand` instance method is used to seed the specific `Random` instance. It allows you to re-initialize the random number generator for that instance with a specific seed, making its sequence of generated numbers predictable.

```ruby
rng = Random.new(111)
p rng.rand(1000) # => 100
p rng.rand(1000) # => 283

rng.srand(111) # Re-seed the same instance
p rng.rand(1000) # => 100 (sequence repeats for this instance)

rng2 = Random.new(111) # A different instance with the same seed
p rng2.rand(1000) # => 100
```

It is important to note that `Random#srand` is an alias for `Random#initialize`. Re-seeding an existing `Random` object will reset its internal state.

#### `bytes(n)`

The `bytes` method returns a string containing `n` pseudo-random bytes.

```ruby
rng = Random.new
p rng.bytes(5) # => "\xAB\xCD\xEF\x12\x34" (example output)
```

### Class Methods (Using the Default Generator)

The `Random` class also provides class methods that operate on a global, default random number generator. This is the same generator used by `Kernel#rand` and `Kernel#srand`.

#### `Random.rand`

This method is equivalent to `Kernel.rand` (or simply `rand`).

- With no arguments, returns a float between 0.0 and 1.0 (exclusive of 1.0):

  ```ruby
  p Random.rand # => 0.7654321
  ```

- With an integer `max` argument, returns an integer between 0 and `max` (exclusive of `max`):

  ```ruby
  p Random.rand(10) # => 7
  ```

- With a `Range` argument, returns an integer within the range:

  ```ruby
  p Random.rand(50..60) # => 53
  ```

#### `Random.srand(seed)`

This method is equivalent to `Kernel.srand(seed)` (or simply `srand(seed)`). It seeds the global default random number generator.

```ruby
Random.srand(123)
p Random.rand(100) # => 13
p rand(100)        # => 80 (uses the same seeded generator)

Random.srand(123)
p Random.rand(100) # => 13 (sequence repeats)
```

#### `Random.bytes(n)`

This method returns a string containing `n` pseudo-random bytes, generated by the global default random number generator.

```ruby
p Random.bytes(3) # => "\xDE\xAD\xBE" (example output)
```

## Array Methods

`mruby-random` extends the `Array` class with methods for shuffling elements and sampling random elements. These methods can optionally accept a `Random` instance to use a specific random number generator.

### `shuffle`

The `shuffle` method returns a _new_ array with the elements of the original array in a random order.

```ruby
a = [1, 2, 3, 4, 5]
p a.shuffle # => [3, 1, 5, 2, 4] (example output)
p a         # => [1, 2, 3, 4, 5] (original array is unchanged)
```

You can provide a specific `Random` instance using the `random:` keyword argument. This is useful for reproducible shuffling.

```ruby
rng = Random.new(123)
a = [1, 2, 3, 4, 5]
p a.shuffle(random: rng) # => [1, 5, 3, 2, 4] (example output, will be consistent with seed 123)

rng2 = Random.new(123) # Same seed
p a.shuffle(random: rng2) # => [1, 5, 3, 2, 4] (same shuffled order)
```

### `shuffle!`

The `shuffle!` method shuffles the elements of the array in-place. It modifies the original array.

```ruby
a = [1, 2, 3, 4, 5]
p a.shuffle! # => [4, 2, 1, 5, 3] (example output)
p a          # => [4, 2, 1, 5, 3] (original array is modified)
```

Similarly to `shuffle`, you can provide a specific `Random` instance using the `random:` keyword argument.

```ruby
rng = Random.new(456)
a = [:a, :b, :c, :d, :e]
p a.shuffle!(random: rng) # => [:c, :a, :e, :d, :b] (example output, consistent with seed 456)
p a                       # => [:c, :a, :e, :d, :b] (original array is modified)
```

### `sample`

The `sample` method chooses one or more random elements from the array.

- When called with no arguments, it returns a single random element from the array. If the array is empty, it returns `nil`.

  ```ruby
  a = ["apple", "banana", "cherry", "date"]
  p a.sample # => "cherry" (example output)

  empty_array = []
  p empty_array.sample # => nil
  ```

- When called with an integer `n` as an argument, it returns a new array containing `n` unique random elements from the original array. If the array does not have enough unique elements, it returns all elements in a shuffled order. If the array is empty, it returns an empty array.

  ```ruby
  a = ["apple", "banana", "cherry", "date", "elderberry"]
  p a.sample(3) # => ["date", "apple", "banana"] (example output)
  p a.sample(10) # => ["banana", "elderberry", "apple", "date", "cherry"] (all elements, shuffled)


  empty_array = []
  p empty_array.sample(3) # => []
  ```

- You can provide a specific `Random` instance using the `random:` keyword argument for both forms of `sample`.

  ```ruby
  rng = Random.new(789)
  a = [10, 20, 30, 40, 50]
  p a.sample(random: rng)    # => 30 (example output, consistent with seed 789)

  rng_b = Random.new(789) # Re-initialize with the same seed for predictable multi-sampling
  p a.sample(2, random: rng_b) # => [30, 50] (example output)
  ```

## Algorithm

The `mruby-random` mrbgem uses the **PCG-XSH-RR** (Permuted Congruential Generator - XorShift High, Random Rotate) algorithm for pseudo-random number generation.

### Key Features

- **Compact State**: Uses only 64 bits of state (compared to 128 bits in the previous xoshiro128++ implementation), reducing memory footprint by 50%
- **Excellent Statistical Quality**: Passes rigorous statistical test suites (TestU01, PractRand)
- **Platform-Optimized**: Automatically adapts to platform characteristics for optimal performance
  - On **32-bit platforms** (`MRB_32BIT`): Uses an optimized 32-bit multiplier that requires only 2 multiply operations instead of 3
  - On **64-bit platforms**: Uses the standard 64-bit multiplier for maximum statistical quality
- **Fast Performance**: Competitive speed with modern PRNG algorithms while maintaining smaller memory footprint

The PCG family of algorithms was developed by Melissa O'Neill and is widely used in production systems. For more details, see <https://www.pcg-random.org/>.

## License

The `mruby-random` mrbgem is released under the MIT License. See the LICENSE file for details.
