Storing or Using Numeric Output From a Function

Hi all,

I'm trying to implement a linear congruential pseudorandom number generator (<http://en.wikipedia.org/wiki/Linear\_congruential_generator&gt;\), since $RANDOM and /dev/random aren't standardized. I'm referring to the Shell & Utilities volume of POSIX.1-2008, but I'm running into some odd issues. This works fine:

#! /bin/sh

rand_x=1

rand()
{
    rand_x=$(((($rand_x * 1103515245) + 12345) % 4294967296))
    echo $rand_x
}

while true; do
    rand
    sleep 2
done

But I want to use the generated integer in an arithmetic expansion, for modular arithmetic (I know it's a sloppy way of limiting the range of values, but it's good enough for my purpose).

I've tried the following, but it simply repeats "1103527590 0" over and over:

#! /bin/sh

rand_x=1

rand()
{
    rand_x=$(((($rand_x * 1103515245) + 12345) % 4294967296))
    echo $rand_x
}

while true; do
    r=`rand`
    printf '%i %i\n' $r $(($r % 3))
    sleep 2
done

Similarly, this repeats "1103527590":

#! /bin/sh

rand_x=1

rand()
{
    rand_x=$(((($rand_x * 1103515245) + 12345) % 4294967296))
    echo $rand_x
}

while true; do
    printf '%i\n' `rand`
    sleep 2
done

It looks like, if I try to store or use the output of rand(), either $rand_x is being reset to 1 each time or the arithmetic substitution in rand() isn't being used.

But if I access $rand_x directly, as in the following script, I get the correct sequence:

#! /bin/sh

rand_x=1

rand()
{
    rand_x=$(((($rand_x * 1103515245) + 12345) % 4294967296))
}

while true; do
    rand
    printf '%i %i\n' $rand_x $(($rand_x % 3))
    sleep 2
done

However I'd like to avoid exposing $rand_x like that (rand() will eventually become a library function). So does anyone have any idea why using echo and capturing/using the output of rand() isn't working?

I'm testing this in GNU Bash and dash on GNU/Linux. When I get this working, I'll test it in more shells.

---------- Post updated at 23:58 ---------- Previous update was at 23:26 ----------

The former seems to be the case. If I echo $rand_x from rand() and just store the output of rand(), $rand_x is reset to 1 for some reason.

The following script illustrates this:

#! /bin/sh

rand_x=1

rand()
{
    rand_x=$(((($rand_x * 1103515245) + 12345) % 4294967296))
    echo $rand_x >&2
    echo $rand_x
}

while true; do
    r=`rand`
    printf '%i %i\n' $rand_x $(($rand_x % 3))
    sleep 2
done

Note that in rand(), $rand_x is now also printed to standard error (file descriptor 2) so it appears on the terminal even though standard output is stored in $r.

Output (file descriptors 1 and 2):

Anyone have any idea why this is happening?

try with below

rand_x=1
rand()
{
    rand_x=$(((($rand_x * 1103515245) + 12345) % 4294967296))
    echo $rand_x >&2
    return $rand_x
}
while true; do
    rand
    printf '%i %i\n' $rand_x $(($rand_x % 3))
    sleep 2
done

output

1103527590
1103527590 0
2524885223
printf: 2524885223: Result too large
2147483647 2
662824084
662824084 1
3295386429
printf: 3295386429: Result too large
2147483647 0

Since it is an infinite loop the output will keep on printing on the prompt

Cheers
Harish

It's because you are running the function in a sub-shell. The value of the $rand_x in your current shell is not altered.

Without sub-shell it works fine...

$ function rand() { rand_x=$(((($rand_x * 1103515245) + 12345) % 4294967296)); echo $rand_x; }

$ for i in 1 2 3; do rand; done
1448521415
3776134388
3397090333

...but using a sub-shell...

$ for i in 1 2 3; do (rand); done
835903122
835903122
835903122

This is in response to a private message I received (since I can't send PMs until I have 10 posts...).

I've implemented this linear congruential generator in C to generate the correct sequence of integers (that is, what I'm expecting from this shell function):

#include <stdio.h>
#include <unistd.h>
#include <stdint.h>

uint32_t
myrand()
{
    static uint32_t rand_x = 1;

    rand_x = (rand_x * 1103515245 + 12345) % 4294967296;

    return rand_x;
}

int
main(int argc, char **argv)
{
    for (;;) {
        printf("%u\n", myrand());
        sleep(2);
    }   

    return 0;
}

And here is the printed sequence:

If I use "return" instead of "echo" and get the value in $? after calling rand(), my shell terminates with the following:

So it's generating the correct number. I guess the problem is that this number is 32 bits long and would exceed a signed long integer on my i686 system. Oops. :wall:

I changed my modulus, multiplier, and increment so the returned value doesn't exceed 2^31. So now this works:

#! /bin/sh

rand_x=1

rand()
{
    rand_x=$(((($rand_x * 65537) + 12345) % 2147483648))
    return $rand_x
}

while true; do
    rand
    printf '%i %i\n' $? $(($? % 3))
    sleep 2
done

For some reason "echo" still just resets $rand_x to its original value... I wish I knew why it did that. But I'll just use "return".

I'll test this script in a few different shells now to see if the signed long issue affects numbers before they're wrapped with modulo. If not, I think this problem is solved. :slight_smile:

EDIT:
Harish: I know it's an infinite loop. I just interrupt it after a few iterations.
Ygor: Oh, I didn't realize command substitution used a subshell (I see it now in POSIX.1-2008) or that variables are set differently in a subshell. Yeah, that explains the weird behavior with "echo" then. Thanks!

EDIT 2:
I used these new values in three different shells so far (GNU Bash, dash, and BusyBox ash) and in C (modifying the above code). I generated 1000 pseudorandom numbers using all four test cases, and all four results were equal. I think the results are undefined if there is overflow in the intermediate arithmetic though. Oh well; I'll test more shells to be sure.

EDIT 3:
Well I've tested 8 shells (GNU Bash, dash, BusyBox ash, zsh, csh, tcsh, ksh, and mksh) across two PC architectures, haha. All give the same 1000 numbers (seeded at 1), so the arithmetic seems portable. Anyway, thanks again Ygor and Harish!