Fuzion Logo
flang.dev — The Fuzion Language Portal
JavaScript seems to be disabled. Functionality is limited.

random.fz


# This file is part of the Fuzion language implementation.
#
# The Fuzion language implementation is free software: you can redistribute it
# and/or modify it under the terms of the GNU General Public License as published
# by the Free Software Foundation, version 3 of the License.
#
# The Fuzion language implementation is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
# License for more details.
#
# You should have received a copy of the GNU General Public License along with The
# Fuzion language implementation.  If not, see <https://www.gnu.org/licenses/>.


# -----------------------------------------------------------------------
#
#  Tokiwa Software GmbH, Germany
#
#  Source code of Fuzion standard library feature random
#
#  Author: Fridtjof Siebert (siebert@tokiwa.software)
#
# -----------------------------------------------------------------------

# random -- effect that provides random numbers
#
random (

  # the provider this effect uses to create random numbers
  p randomProvider,

  redef r effectMode.val
  ) : effect r
is


  # for a random instance installed in the current environment, update the
  # environment to the next random value.  The the contents of the current
  # instance are left unchanged, 'random.env' will provide the new instance.
  #
  next => random p.next mode


  # return next random number of type i32 in the range 0..max-1
  #
  next_i32(max i32)
  pre max >= 0
   => (next.get max.as_u64).as_i32


  # return next random number of type i64 in the range 0..max-1
  #
  next_i64(max i64)
  pre max >= 0
   => (next.get max.as_u64).as_i64


  # return next random number of type u32 in the range 0..max-1
  #
  next_u32(max u32) => (next.get max.as_u64).as_u32


  # return next random number of type u64 in the range 0..max-1
  #
  next_u64(max u64) =>  next.get max


  # return next random number of type f32 in the range [0..1),
  # result will never be equal to f32 1
  #
  next_f32 =>
    max := u64 1 << f32s.significandBits.as_u64
    (next_u64 max).as_f64.as_f32 / max.as_f64.as_f32


  # return next random number of type f64 in the range [0..1),
  # result will never be equal to f64 1
  #
  next_f64 =>
    max := u64 1 << f64s.significandBits.as_u64
    (next_u64 max).as_f64 / max.as_f64


  # return next random number as a bool
  #
  next_bool =>
    (next.get 2) %% 2


  # get the current random number in the range 0..max-1
  #
  get (max u64)
   =>
     p.get % max  # NYI: This implementation is bad if max is big


# random with no argument returns random.env, i.e., the currently installed
# source of randomness.
#
random =>
  randoms.installDefault
  random.env

simpleRandom(seed u64, rr ()->unit) =>
  _ := random (simpleRandomProvider seed seed) (effectMode.inst rr)

timeSeededRandom(rr ()->unit) =>
  _ := random simpleRandomProviders.timeSeeded (effectMode.inst rr)


# randomProvider -- abstract source of random numbers
#
# This provides the random number input to be used by the 'random' effect.
# Implementation may be dumb sequences of pseudo-random numbers or high-qualiity
# cryptographic random number generators
#
# A randomProvider contains an immutable state, repeated calls to 'get' result
# in the same value.  To produce a sequence of different random numbers, 'next'
# must be used to create a new instance of 'randomProvider' before 'get' can be
# used to obtain the new random number.
#
randomProvider ref is

  # create a new instance of randomProvider containing a new state. Depending
  # on the qualifity of this random number generator, this might be a simple
  # function of the original randomProvider or it might use an external source
  # of entropy to create a new instance.
  #
  next randomProvider is abstract

  # Return the random number stored in this instance of randomProvider, in the
  # range 0..u64.max
  #
  # NOTE: this feature is pure, i.e., repeated calls on the same target result
  # in equal results.  Use 'next' to get different results.
  #
  get u64 is abstract


# type related to random declaring features not requiring an instance of random
#
randoms is

  installDefault is
    if !effects.exists random
      _ := random simpleRandomProviders.default effectMode.default



# simple random number provider for pseudo random numbers that are not safe
# for security and that do not meet typical requirements for a good
# random number generator.
#
simpleRandomProvider(seed1 u64, seed2 u64) : randomProvider is

  # get next instance by shuffling bits in seeds around
  #
  next randomProvider is
    # NYI: This does not meet any requirements on a decent random number generator
    s1 := (seed1 *° 1717171717 +° 13113131313) ^ seed2
    s2 := (seed2 *° 9191919191 +° 37637373737) ^ seed1
    rot1 := s2 & 0x3f
    rot2 := s1 & 0x3f
    (simpleRandomProvider ((s2 << rot2) | (s2 >> (u64 64 - rot2)))
                          ((s1 << rot1) | (s1 >> (u64 64 - rot1))))

  # get current random number
  #
  get u64 is seed1


# type related to simpleRandomProvider declaring features not requiring an instance
# of simpleRandomProvider
#
simpleRandomProviders is

  # name of env var to provide default random seed.
  #
  # If set, this u64-value will be used too seed the default random number generator.
  # If not set, the default random number generator will be time seeded.
  #
  # If set to unparsable number, panic on installation of the default random
  # number generator.
  #
  random_seed_env_var => "FUZION_RANDOM_SEED"

  # create the default random provider depending on the environment settings
  #
  default =>
    match envir.vars.get random_seed_env_var
     s string =>
       match s.parse_u64
         seed u64 => simpleRandomProvider seed 98765432109876543
         e error  => panic "Failed to parse value in env var '$random_seed_env_var': $e"
     nil      => timeSeeded


  # create a time-seeded simple provider for pseudo random numbers that are not safe
  # for security and that do not meet typical requirements for a good
  # random number generator.
  #
  timeSeeded randomProvider is
    # initialize with large numbers XORed with nano time
    #
    simpleRandomProvider (u64 77777777777777 ^ time.nano.read) 98765432109876543

  # create a time-seed sprovider for pseudo random numbers that are not safe
  # for security and that do not meet typical requirements for a good
  # random number generator.
  #
  timeSeeded(rr ()->unit) =>
    _ := random timeSeeded (effectMode.inst rr)