Beefy Boxes and Bandwidth Generously Provided by pair Networks
go ahead... be a heretic
 
PerlMonks  

Old random number generator

by Pascal666 (Scribe)
on May 09, 2020 at 20:22 UTC ( #11116622=perlquestion: print w/replies, xml ) Need Help??

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

Perl v5.20 now uses its own random number generator. Is there a way to get the old behavior back?

I have a few programs that depend upon generating repeatable pseudorandom number sequences. srand has always worked well for this. Pass it the same value and get the same series of outputs from rand.

Before 5.20, srand 0;print rand in Strawberry Perl always gave me 0.00115966796875. With 5.20 and above, I instead always get 0.17082803610629. Berrybrew made finding the dividing line relatively easy. How do I get my old number stream back?

Replies are listed 'Best First'.
Re: Old random number generator
by haukex (Bishop) on May 09, 2020 at 21:27 UTC
    Is there a way to get the old behavior back?

    From what little I can tell from this, it may still be possible to compile Perl on Windows to use the old way. But that seems overly complex.

    I have a few programs that depend upon generating repeatable pseudorandom number sequences.

    The new random number generator still does this, it's even more reliable because it'll be the same no matter what platform. So I suspect that's not really your point - I suspect you've got code that is depending on a certain sequence of random numbers? Note that, in general, this isn't reliable anyway, since AFAICT, Perl before 5.20 didn't guarantee a certain algorithm to be used. What you probably really want is a PRNG with a known algorithm.

    Before 5.20, srand 0;print rand in Strawberry Perl always gave me 0.00115966796875.

    Well, the following appears to work (after installing Microsoft Visual C++ 2010 Redistributable Package (x64); I'm not a Windows expert so maybe there's a better way*) - at least for the single value you provided. But again, you're probably better off choosing a known PRNG instead, possibly from CPAN.

    use warnings; use strict; use Win32::API; my $srand = Win32::API::More->new( 'msvcr100', 'void srand(unsigned int seed)' ) or die "Error: ".Win32::FormatMessage(Win32::GetLastError()); my $rand = Win32::API::More->new( 'msvcr100', 'int rand()' ) or die "Error: ".Win32::FormatMessage(Win32::GetLastError()); $srand->Call(0); print $rand->Call()/(1<<15), "\n"; # 0.00115966796875

    * Update: I can confirm that 'msvcrt' instead of 'msvcr100' works for me as well, thanks syphilis! /Update

    And after a bit of digging, the Linear congruential generator that Windows uses can be reimplemented in Perl:

    sub winrand { use feature 'state'; state $r = 0; # seed $r = ( 214013*$r + 2531011 ) & 0xFFFFFFFF; return ($r>>16)&0x7FFF; }

    The output of this function can be used in the same way, i.e. the first call to winrand()/(1<<15) also produces 0.00115966796875, and further calls to winrand() produce the same as $rand->Call() in the above code. Note I've only tested this on a 64-bit Perl.

      I think that will fit Pascal666's requirement splendidly.

      You probably don't need to install anything to get the script to work.
      IIRC, all windows systems come with msvcrt.dll - and modifying the script to use 'msvcrt.dll' instead of 'msvcr100.dll' worked fine for me.

      Cheers,
      Rob

      Wow. Thank you. Yes, I need a PRNG that outputs the same numbers as the old one. If I can implement it in Perl so that I don't have to worry about it ever changing again and it is the same on multiple platforms, awesome!

      winrand()/(1<<15) does appear to output the correct test numbers with my test seeds. Unfortunately, I can't figure out how to use winrand(EXPR) the same way I would use rand(EXPR) ("returns a random fractional number greater than or equal to 0 and less than the value of EXPR").

      For example, srand 0;print int(rand(99)) for 0..9 must always yield 023647263525973192.

        For example, srand 0;print int(rand(99)) for 0..9 must always yield 023647263525973192

        This outputs correctly for me:
        use warnings; use strict; my_srand(0); print int(my_rand(99)) for 0..9; sub my_srand { use Win32::API; my $srand = Win32::API::More->new( 'msvcrt', 'void srand(unsigned int seed)' ) or die "Error: ".Win32::FormatMessage(Win32::GetLastError()); $srand->Call($_[0]); } sub my_rand { my $rand = Win32::API::More->new( 'msvcrt', 'int rand()' ) or die "Error: ".Win32::FormatMessage(Win32::GetLastError()); return int(($rand->Call()) * $_[0] / 32767); }
        It outputs 023647263525973192
        The only problem is that it calls my_srand and my_rand, but I gather that the requirement is that it instead call srand and rand.
        I don't know how/if the builtin srand and rand can be overridden to point to my_srand and my_rand.

        Cheers,
        Rob
        #!/usr/bin/perl use warnings; use strict; { my $seed = 0; sub win_srand { $seed = shift } sub win_rand { my ($max) = @_; $seed = ( 214013 * $seed + 2531011 ) & 0xFFFFFFFF; return $max * (($seed >> 16) & 0x7FFF) / (1 << 15) } } win_srand(0); print int(win_rand(99)) for 0..9; # 023647263525973192.
        map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]
Re: Old random number generator
by bliako (Prior) on May 10, 2020 at 09:52 UTC

    As haukex and syphilis already suggested, a good approach is not to depend on Perl's internal RNG algorithm but use your own. Either from one of the modules from CPAN, or roll your own algorithm.

    The added benefit, apart from getting same sequences across systems and versions (perhaps with a bit of tuning, re: arithmetic operations overflows etc.) is that you will control the use of this new rand() and you will never miss the next in sequence because another module or part of your program calls rand() too! Imagine if/when that happens conditionally and different runs of your program produce different results because sometimes a rand() is consumed by another part of your program or external module and sometimes it does not.

    On this last point (i.e. rand() consumed by system build-in needs) I experimented with build-in hashtable and its random needs - I thought wrongly that it draws from the same system's rand() but this is not true: creating and using a hash in-between 2 rand() calls does not seem change the RNG sequence. I am not sure what other build-ins use RNG, regex engine perhaps?

    And now to the juice, as syphilis wonders how one can override a system build-in sub, here is how to override rand() with your own algorithm or calling external module. This will give you seamless behaviour with your existing code and no other changes need to be made to your scripts. Below code also demonstrates (simplistically) how to use different RNGs depending on who is the caller. Here's the codez based on the original solution by LanX:

    #!/usr/bin/env perl use strict; use warnings; my $total_sleep_time = 0; BEGIN { *CORE::GLOBAL::rand = sub { my $parent = (caller(1))[3] // ""; if( $parent =~ /abc/ ){ return CORE::rand() } return 123; }; } print "fake rand: ".rand()."\n"; abc(); sub abc { print "real rand(): ".rand()."\n" }

    For more information, see the answers to a question I posted here Copy a builtin sub to a different name and then override some time ago.

    And if you want the exact same sequence as this version or that platformed produced, then nothing stops you from HARDCODING a random sequence into your programs and use the method above to run through it, instead of using an algorithm. Provided that you will ever going to need just a million r random numbers. (Actually that can become a web-service!)

    bw, bliako

      syphilis wonders how one can override a system build-in sub, here is how to override rand() with your own algorithm or calling external module

      Thank you for that.
      I had come close ... but still managed to miss the correct incantation I was looking for.
      I had done:
      BEGIN { *CORE::GLOBAL::srand = \&my_srand; *CORE::GLOBAL::rand = \&my_rand; };
      but failed to declare my_rand() and my_srand() inside the BEGIN{} block.
      Not a very intelligent mistake to make ... but then, that's the nature of mistakes ;-)

      I don't see a need for named subs anyway, and I think it looks more natural to use anonymous subs as you have done.
      Here's a working version of what I was trying to come up with, by using named subs.
      use warnings; use strict; BEGIN { *CORE::GLOBAL::srand = \&my_srand; *CORE::GLOBAL::rand = \&my_rand; sub my_srand { use Win32::API; my $srand = Win32::API::More->new( 'msvcrt', 'void srand(unsigned int seed)' ) or die "Error: ".Win32::FormatMessage(Win32::GetLastError()); $srand->Call($_[0]); } sub my_rand { my $rand = Win32::API::More->new( 'msvcrt', 'int rand()' ) or die "Error: ".Win32::FormatMessage(Win32::GetLastError()); return int(($rand->Call()) * $_[0] / 32767); } } srand(0); print int(rand(99)) for 0..9; __END__ Outputs 023647263525973192
      (However, I personally prefer the use of Inline::C, rather than Win32::API.)

      Cheers,
      Rob
Re: Old random number generator
by Corion (Pope) on May 09, 2020 at 20:52 UTC

    Maybe just copy the code from the old Perl version? It's not as if old versions of Perl are unavailable.

Re: Old random number generator
by perlfan (Vicar) on May 12, 2020 at 02:24 UTC
    Just throwing this out there, Test::Random Also, OP's problem seems similar to the issue of relying on hash keys being iterated over in a deterministic order. In other words, bone headed.
A reply falls below the community's threshold of quality. You may see it by logging in.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://11116622]
Approved by haukex
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others avoiding work at the Monastery: (6)
As of 2020-10-23 03:22 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    My favourite web site is:












    Results (234 votes). Check out past polls.

    Notices?