http://www.perlmonks.org?node_id=940642

Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

i'm working on a perl script and i need a very specific random number generator.

what i have right now is multrand. it returns a random number between x/y and x*y, with the median result being x. half the time less than x, half the time, greater than x.

i want to build a sub that returns a random in the same range, but with the result more likely to be closer to x.

with a simple random, i'd just run the sub z times and return result/z. graph the results of that, and you get a bell curve.

the results/z method won't work here because i still want the results to be half the time less than x and half the time greater than x.

any ideas?

sub multrand { my ( $inmed, $inmult ) = @_; my $randreturn = 0; # $randtemp is used to determine if result will be more than, +less than, or equal to original my $randtemp = int(rand($inmed * 2)); if ( $randtemp > $inmed ) { # $randreturn can be up to $inmed * $inmult $randreturn = int(rand(($inmed*$inmult)-$inmed)) + int +($inmed); } elsif ( $randtemp < $inmed ) { # $randreturn can be as little as $inmed / $inmult $randreturn = int(rand($inmed*(1-(1/$inmult)))) + int( +$inmed*(1/$inmult)); } else { $randreturn = $inmed; } if ( $debug > 2 ) { print "$randreturn\n"; } return $randreturn; }

Replies are listed 'Best First'.
Re: custom random number generator
by salva (Canon) on Nov 29, 2011 at 16:17 UTC
    $r = $x * $y ** (1 - rand 2);

    update:

    Or if you want to reduce the variance:

    $r = $x * $y ** ((1 - rand 2) ** $n)
    where $n is an odd integer bigger than one.
Re: custom random number generator
by BrowserUk (Patriarch) on Nov 29, 2011 at 18:12 UTC

    Improved and better tested:

    #! perl -slw use strict; use Data::Dump qw[ pp ]; use Math::Random::MT qw[ rand srand ]; sub xrand { my( $x, $y ) = @_; my $lo = $x / $y; my $hi = $x * $y; if( rand() < 0.5 ) { my $root = sqrt( $hi - $x ); my $rv = rand( $root )**2 + $x; return $rv; } else { my $diff = $x - $lo; my $root = sqrt( $diff ); my $rv = ( $diff - rand( $root )**2 ) + $lo; return $rv; } } our $T //= 1000; our $X //= 10; our $Y //= 2; my $min = $X / $Y; my $max = $X * $Y; my( $loCount, $exactX, $hiCount ) = (0) x 3; my %dist; for ( 1 .. $T ) { my $rand = xrand( $X, $Y ); die 'Out of range' unless $rand >= $min and $rand <= $max; if( $rand < $X ) { ++$loCount; } elsif( $rand > $X ) { ++$hiCount; } else { ++$exactX; } ++$dist{ int( $rand *10 ) / 10 }; } pp \%dist; printf "After $T samples %f%% < %f%% < %f%%\n", $loCount * 100 / $T, $exactX *100 / $T, $hiCount * 100 / $T;

    With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
Re: custom random number generator
by JavaFan (Canon) on Nov 29, 2011 at 17:10 UTC
    Without further constraints, why not take a random number, if it's less than 0.5, return a random number between x/y and x; if the first random number exceeds 0.5, return a random number between x and x*y. Something like (untested):
    sub r2 { my ($low, $high) = @_; $low + rand($high-$low); } sub myrand { my ($x, $y) = @_; rand() < .5 ? r2($x/$y, $x) : r2($x,$x*$y); }
    (This assumes 1 <= $y < $x; adjust to taste).
Re: custom random number generator
by TJPride (Pilgrim) on Nov 29, 2011 at 20:56 UTC
    I think you'll like this. Not only does it randomize your values for you, but it gives a visual display of the result so you can see the distribution. Change the weight to see how it affects things - larger numbers mean closer to $x, smaller means further, with 1 being classic bell curve.

    use strict; use warnings; my ($x, $y, $weight, @r) = (5, 10, 2); push @r, multirand($x, $y, $weight) for 1..10000; display(\@r, 50); sub multirand { my ($x, $y, $weight) = @_; return $x * $y ** (rand() ** $weight * (rand() > 0.5 ? 1 : -1)); } sub display { my ($r1, $max) = @_; my (@r2, $skip, $i); $skip = ($#$r1 + 1) / 40; @r2 = sort { $a <=> $b } @$r1; for ($i = 0; $i <= $#r2; $i += $skip) { print 'x' x ($r2[$i] / $max * 80 + 0.5) . "\n"; } }

    Output:

    x x x xx xx xx xxx xxx xxx xxxx xxxx xxxxx xxxxx xxxxxx xxxxxx xxxxxxx xxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxxx xxxxxxxxx xxxxxxxxxx xxxxxxxxxxx xxxxxxxxxxxx xxxxxxxxxxxxx xxxxxxxxxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Re: custom random number generator
by toro (Beadle) on Nov 30, 2011 at 07:24 UTC

    Anonymous Monk, you could

    1. do your bell curve trick to produce a standard normal ∈ (−∞, +∞) centred on 0
    2. map the negative half (−∞, 0] thru exp onto 0,1 and thence thru ƒ($num) = x over {1 + y • (1 − $num)} onto [x/y, x].
    3. map the positive half (0, +∞) thru g($rand) = 1−exp(−$rand) onto (0,1) and thence thru h($num) = x • (1 + $num • y) onto (x, xy).
    . That will be centred on x, mound-shaped, and put half the probability mass on either side of x — stretched according to your whims.

Re: custom random number generator
by BrowserUk (Patriarch) on Nov 29, 2011 at 16:51 UTC

    Update: Ignore this. It works for my test values, but not for others.


    With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.