Generate a random number in a fully POSIX compliant shell, 'dash'...


 
Thread Tools Search this Thread
Operating Systems OS X (Apple) Generate a random number in a fully POSIX compliant shell, 'dash'...
# 1  
Old 01-18-2020
Generate a random number in a fully POSIX compliant shell, 'dash'...

Hi all...
Apologies for any typos, etc...
This took a while but it didn't beat me...

Although there are many methods of generating random numbers in a POSIX shell this uses integer maths and a simple C source to create an executable to get epoch to microseconds accuracy if it is needed. I take no credit for the C source, the credit is inside the script.
It is an exercise in futility but I love finding the limits of a language.
This is only tested in OSX 10.14.6 and not Linux at the moment for the C source only as I have no idea if gcc will compile.
Code:
#!/usr/local/bin/dash
# #!/bin/sh
#
# Fully POSIX random number generator.
# Wichmann-Hill method.
# https://en.wikipedia.org/wiki/Wichmann%E2%80%93Hill

# Initialise variables as global.
EPOCH=1234567890.987654
SEED1=1
SEED2=1
SEED3=1
VALUE=0
TEST_VAL=1
TEST_NUM=256

# Get epoch value to microseonds.
epoch()
{
cat << 'EPOCH_MICROSECS' > /tmp/epoch_microsecs.c
/* 'epoch_microsecs.c' */
/* Thanks to Perderabo for this little snippet modified to suit my needs. */
/* It saved me the bother of working it out by myself. */
/* https://www.unix.com/programming/1991-time-microseconds-2.html */

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>

int main(void)
{
    struct timeval tv;
    struct timezone tz;
    struct tm *tm;
    gettimeofday(&tv, &tz);
    tm=localtime(&tv.tv_sec);
    printf("%ld.%06d", tv.tv_sec, tv.tv_usec);
    exit(0);
}
EPOCH_MICROSECS
# Compile the above using gcc.
gcc -Wall -pedantic -ansi -o /tmp/epoch_microsecs /tmp/epoch_microsecs.c -lm
}

# Use clock timer for seeds.
time_seeds()
{
    # Create the three required seeds using the clock.
    # Each must be between 1 and 30000 inclusive.
    EPOCH=$( /tmp/epoch_microsecs )
    SEED1=${EPOCH#????????????}
    SEED1=$( expr ${SEED1} + 1 )
    if [ ${SEED1} -gt 30000 ]
    then
        SEED1=$(( SEED1 / 4 ))
    fi
    #
    EPOCH=$( /tmp/epoch_microsecs )
    SEED2=${EPOCH#????????????}
    SEED2=$( expr ${SEED2} + 1 )
    if [ ${SEED2} -gt 30000 ]
    then
        SEED2=$(( SEED2 / 4 ))
    fi
    #
    EPOCH=$( /tmp/epoch_microsecs )
    SEED3=${EPOCH#????????????}
    SEED3=$( expr ${SEED3} + 1 )
    if [ ${SEED3} -gt 30000 ]
    then
        SEED3=$(( SEED3 / 4 ))
    fi
}

# Use user defined values for seeds.
fixed_seeds()
{
    # All seeds must be an integer, 1 to 30000!
    # Called as 'fixed_seeds <SEED1> <SEED2> <SEED3><CR>'
    SEED1=${1}
    SEED2=${2}
    SEED3=${3}
    check_seeds
}

# Use external user defined values for seeds. 
manual_seeds()
{
    # All seeds must be an integer, 1 to 30000!
    printf "Enter first integer seed, 1 to 30000:- "
    read -r SEED1
    printf "Enter second integer seed, 1 to 30000:- "
    read -r SEED2
    printf "Enter third integer seed, 1 to 30000:- "
    read -r SEED3
    check_seeds
}

# Check user seeds for basic errors.
# Any other errors are taken care of by the shell itself.
check_seeds()
{
    if [ "${SEED1}" = "" ] || [ "${SEED2}" = "" ] || [ ${SEED3} = "" ]
    then
        echo "Usage1: fixed_seeds <SEED1> <SEED2> <SEED3>"
        echo "OR..."
        echo "Usage2: manual_seeds and then follow the on screen prompts!"
        echo "Aborting..."
        exit 1
    fi
    if [ ${SEED1} -lt 1 ] || [ ${SEED2} -lt 1 ] || [ ${SEED3} -lt 1 ]
    then
        echo "ERROR! One or more of the seeds are wrong! Aborting..."
        exit 2
    fi
    if [ ${SEED1} -gt 30000 ] || [ ${SEED2} -gt 30000 ] || [ ${SEED3} -gt 30000 ]
    then
        echo "ERROR! One or more of the seeds are wrong! Aborting..."
        exit 3
    fi
}

# This is the working part of this Wichmann-Hill random module...
whrandom()
{
    SEED1=$(( ( 171 * SEED1 ) % 30269 ))
    SEED2=$(( ( 172 * SEED2 ) % 30307 ))
    SEED3=$(( ( 170 * SEED3 ) % 30323 ))
    
    VALUE=$(( ( ( SEED1 * 1000000000 ) / 30269 ) + ( ( SEED2 * 1000000000 ) / 30307 ) + ( ( SEED3 * 1000000000 ) / 30323 ) ))
    VALUE=$(( VALUE % 1000000000 ))

    printf "%.9f" "${VALUE}e-9"
}

# Test loop only, press Ctrl-C to stop...
test()
{
    while true
    do
        # Save fixed point value to disk...
        # For some reason this does not work, 'TEST_VAL=$( whramdom )'. ;'(
        whrandom > /tmp/VAL
        # Immediately read it back.
        read -r TEST_VAL < /tmp/VAL
        # Remove any leading zeros, (0).
        TEST_VAL=$( expr ${TEST_VAL#??} + 0 )
        # Give a test number, 256 for this DEMO so as to have 0 to 255 values.
        TEST_NUM=256
        # Multiply the two variables together......
        TEST_VAL=$(( TEST_VAL * TEST_NUM ))
        # ......AND correct for the fixed point position removing anything beyond a decimal point.
        printf "%.0f\n" "${TEST_VAL}e-9"
    done
}

epoch

time_seeds

test

Results OSX 10.14.6, using dash called from default shell...
Code:
Last login: Sat Jan 18 21:21:52 on ttys000
AMIGA:amiga~> cd Desktop/Code/Shell
AMIGA:amiga~/Desktop/Code/Shell> ./WH_random_POSIX.sh
98
49
123
233
4
90
73
145
56
198
166
10
205
110
153
.
.
.
.
155
235
141
245
242
45
50
^C
AMIGA:amiga~/Desktop/Code/Shell> _

Looking forwards to input from the big guns to improve upon it...

Last edited by wisecracker; 01-20-2020 at 10:50 AM.. Reason: Copy and paste error, although it makes no difference.
These 4 Users Gave Thanks to wisecracker For This Post:
# 2  
Old 01-19-2020
Quote:
Originally Posted by wisecracker
Looking forwards to input from the big guns to improve upon it...
Thanks so much for posting these kinds of projects. I wish everyone would do this, regardless of their skill level. Sharing is the operative word these days.

On a similar note:

In 2020, I prefer we call on less experienced users to ask questions about code, versus the "old style from before 2019" of asking "experts" and "heavy hitters" to improve code.

One of my main goals at unix.com in 2020 is to encourage new users and people with less experience to feel good about asking questions and coding, even if they make a lot of mistakes, and their code is not "pretty", versus promoting "better, cleaner, shorter, standards-based code"......

If we have an environment for the "big guns" or the less than 1% or even 0.1% (or less), this will discourage the 99.9% of people (or more) who are not "big guns"...

I'm trying hard to refocus the site away from "elitism" as was the focus for the past number of years (by evolution, not be design), and toward "sharing", especially for beginners and those who may not normally feel comfortable sharing their code and projects with "experts" or "big guns". This also applies to the latest tech (not legacy tech), where everyone is learning together.

"Learn By Sharing Regardless of Your Skill Level"

Thanks so much!
These 2 Users Gave Thanks to Neo For This Post:
# 3  
Old 01-19-2020
Better replace all expr with arithmetics by the builtin=faster $(( ))
Code:
    SEED1=$(( ${SEED1} + 1 ))
TEST_VAL=$(( 0${TEST_VAL#??} ))

# 4  
Old 01-19-2020
Quote:
Originally Posted by MadeInGermany
Better replace all expr with arithmetics by the builtin=faster $(( ))
Code:
    SEED1=$(( ${SEED1} + 1 ))
TEST_VAL=$(( 0${TEST_VAL#??} ))

As the clock is very quickly cycling inside the microsecond part there is always a possibility of these values '00000' to '09999' occurring. These are obviously NOT octal but decimal with leading zeros so......
......I used 'expr' to remove leading zeros otherwise there is a major shell error, thus I can't see how your modification works.
The maths method does not remove those leading zeros...
# 5  
Old 01-19-2020
Well the C source does not compile on a current update, 19-01-2020, Linux Mint 19 using gcc 7.4.0 so here is a completely simplified and modified version for gcc 4.2.1 and 7.4.0...
The printf function will need to edited for it to work.
It is set up for Linux, (Mint 19), using gcc 7.4.0.
You can still leave to original as it is for OSX 10.14.6+ as it won't affect the performance at all, but you can edit this in instead and have both capable with the minor alteration shown...
Code:
/* 'epoch_microsecs.c' */
/* Thanks to Perderabo for this little snippet modified to suit my needs. */
/* It saved me the bother of working it out by myself. */
/* https://www.unix.com/programming/1991-time-microseconds-2.html */

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>

int main(void)
{
    struct timeval tv;
    gettimeofday(&tv, NULL);

    /* Line below for gcc version 4.2.1, OSX 10.14.6... */
    /* printf("%ld.%06d", tv.tv_sec, tv.tv_usec); */

    /* Line below for gcc version 7.4.0, Linux Mint 19... */
    printf("%ld.%06ld", tv.tv_sec, tv.tv_usec);

    exit(0);
}


Last edited by wisecracker; 01-19-2020 at 06:46 PM.. Reason: Copy and paste error...
# 6  
Old 01-20-2020
Quote:
Originally Posted by wisecracker
As the clock is very quickly cycling inside the microsecond part there is always a possibility of these values '00000' to '09999' occurring. These are obviously NOT octal but decimal with leading zeros so......
......I used 'expr' to remove leading zeros otherwise there is a major shell error, thus I can't see how your modification works.
The maths method does not remove those leading zeros...
I see. Here is how to strip leading zeros with Posix-shell-builtins:
Code:
 num=${TEST_VAL#??}
 lz=${num%%[1-9]*}
 TEST_VAL=${num#$lz}

Don't know if this is really faster than expr.
# 7  
Old 01-20-2020
Hi MadeInGermany...
Although there is a workaround this is an edge case:
Code:
#!/usr/local/bin/dash
# lead_zeros.sh

# This is a gotcha...
# There will always be 6 zeros sooner or later, 000000.
 
# Only the last 6 digits are really needed... 
TEST_VAL=0.000000
TEST_NUM=123

num=${TEST_VAL#??}
echo "${num}"
lz=${num%%[1-9]*}
echo "${lz}"
TEST_VAL=${num#$lz}

echo "Test value = ${TEST_VAL}..."

TEST_VAL=$(( TEST_VAL * TEST_NUM ))

echo "Required integer = ${TEST_VAL}..."

Results:
Code:
Last login: Mon Jan 20 21:18:42 on ttys000
AMIGA:amiga~> cd Desktop/Code/Shell
AMIGA:amiga~/Desktop/Code/Shell> chmod 755 lead_zeros.sh
AMIGA:amiga~/Desktop/Code/Shell> ./lead_zeros.sh
000000
000000
Test value = ...
./lead_zeros.sh: 19: ./lead_zeros.sh: Illegal number: 
AMIGA:amiga~/Desktop/Code/Shell> _

EDIT:
This is a workaround...
TEST_VAL=$(( (${TEST_VAL} + 0 ) * TEST_NUM ))

Last edited by wisecracker; 01-20-2020 at 06:46 PM.. Reason: See EDIT:
Login or Register to Ask a Question

Previous Thread | Next Thread

10 More Discussions You Might Find Interesting

1. Shell Programming and Scripting

Q: Is SQRT(n) possible in a POSIX compliant shell? A: Yes within limits.

Hi all... This is just a fun project to see if it is possible to get a square root of a positive integer from 1 to 9200000 to 6 decimal places on a 64 bit architecture machine. It is coded around dash and the results show the values from 0 to 10000. Complex numbers can easily be catered for by... (3 Replies)
Discussion started by: wisecracker
3 Replies

2. Shell Programming and Scripting

Generating a POSIX random number?

Hi Guys and gals... As you know I am getting to grips with POSIX and hit this stumbling block. Generating two random numbers 0 to 255 POSIXly. Speed in not important hence the 'sleep 1' command. I have done a demo that works, but it sure is ugly! Is there a better way? #!/bin/sh # Random... (12 Replies)
Discussion started by: wisecracker
12 Replies

3. Shell Programming and Scripting

Help with generate a pair of random number

Hi, Is anybody experience generate a pair of random number by using awk command? I wanna to generate a pair of random number (range from 1 to 4124) and repeats it 416 times. Desired output 2 326 123 1256 341 14 3245 645 . . . I did write the below command: awk... (5 Replies)
Discussion started by: perl_beginner
5 Replies

4. UNIX for Dummies Questions & Answers

how to generate random number as as the first column of a txt file

Dear all, I have a question. I have a txt file say 4000 rows X 1800 Column. I 'd like to creat a new column as the first column which is a column of random numbers (n=4000) thanks a lot! Lin (2 Replies)
Discussion started by: forevertl
2 Replies

5. Shell Programming and Scripting

Logical expression in POSIX compliant Korn Shell

Hi, i want to check if a variable var1 is not a or b or c pseudo code: If NOT (var1 = a or var1 = b or var1 = c) then ... fi I want to use POSIX complaint Korn shell, and for string comparison For the following code, logical.sh #!/usr/bin/ksh var="j" echo "Var : $var" if ! { || ||... (12 Replies)
Discussion started by: ysrini
12 Replies

6. Shell Programming and Scripting

Unix random number generate in given range

Hi All, I have extracted some report from database for few activities done. Now I have a requirement to add some random time(In range of 10-35) in front of each activity. Can be generated random numbers in any bash/sh shell within a given number range, let's say in between 10-30. ... (10 Replies)
Discussion started by: gr8_usk
10 Replies

7. Programming

Generate random number

I saw this formula to generate random number between two specified values in shell script.the following. $(((RANDOM%(max-min+divisibleBy))/divisibleBy*divisibleBy+min)) Give a example in book. Generate random number between 6 and 30.like this. $(((RANDOM%30/3+1)*3)) But I have a... (1 Reply)
Discussion started by: luoluo
1 Replies

8. Shell Programming and Scripting

generate random number in perl

Could any one tell how can I generate random number from (0, 100..200) in perl? Thanks! (2 Replies)
Discussion started by: zx1106
2 Replies

9. Shell Programming and Scripting

generate random number in korn shell

I want to be able to generate a random number within a korn shell script.. Preferably i would like to be able to state how many digits should be in this random number... ie 4 digits or 5 digits etc Any ideas? (2 Replies)
Discussion started by: frustrated1
2 Replies

10. Programming

How to generate a random number?

How to generate a random integer with specific range(for example, from 1 to 1000)? Also, how to convert a floating point number into a integer? (2 Replies)
Discussion started by: MacMonster
2 Replies
Login or Register to Ask a Question