Skip to content

Typescript conversion and tweaks #9

@Rycochet

Description

@Rycochet

Had a use for this, but wanted to update it a little for better practices etc, plus for my own use-case... I don't want to fork or release separately, and as this is under MIT I'm pasting this chunk of code for anyone (including @coverslide if he wishes) to make use of under the MIT too etc.

Note the use of arrow functions for lower overhead, and that you can pass one Alea object as an argument to make cloning easier, the import also allows chaining so you can do things with less overhead (although arguably the Object.defineProperties are a little slower, but they're safer so I use them).

I've also added a couple of utility functions in there that I found I was using quite often - documentation within the code is definitely lacking (but useful for autocomplete in VSCode etc)

The actual working code has only been moved around, so there's no functional differences to it - so it's a nice drop-in replacement for anyone using a more modern build infrastructure 🙂

export const ALEA_VERSION = "Alea 0.10";

/**
 * Mash any stringable value into a single 32-bit number.
 */
function Mash() {
    let n = 0xefc8249d;

    return (value: { toString: () => string }) => {
        const data = value.toString();

        for (let h, i = 0; i < data.length; i++) {
            n += data.charCodeAt(i);
            h = 0.02519603282416938 * n;
            n = h >>> 0;
            h -= n;
            h *= n;
            n = h >>> 0;
            h -= n;
            n += h * 0x100000000; // 2^32
        }

        return (n >>> 0) * 2.3283064365386963e-10; // 2^-32
    };
}

/**
 * A sequence of 4 numbers, and a version string so it can be imported and
 * exported safely.
 */
export type AleaState = [number, number, number, number] & { version: string };

/**
 * The return value from the Alea() constuctor.
 */
export interface Alea {
    /**
     * Used to track the version, should match `ALEA_VERSION`.
     */
    version: string;

    /**
     * Call as a function to get the next number.
     *
     * @returns -1 < n < 1
     */
    (): number;

    /**
     * Alias for calling directly.
     *
     * @returns -1 < n < 1
     */
    next: () => number;

    /**
     * Get a signed number (non-integer).
     *
     * @param min 0
     * @param max 1
     *
     * @returns min < n < max
     */
    abs: (max?: number, min?: number) => number;

    /**
     * Get a signed number (non-integer).
     *
     * @param max 0x100000000 (2^32)
     *
     * @returns -max < n < max
     */
    max: (max: number) => number;

    /**
     * Get a signed integer.
     *
     * @param max 0x100000000 (2^32)
     *
     * @returns -max < n < max
     */
    int: (max?: number) => number;

    /**
     * Get an unsigned integer.
     *
     * @param min 0
     * @param max 0x100000000 (2^32)
     *
     * @returns min < n < max
     */
    uint: (max?: number, min?: number) => number;

    /**
     * Get a valid fraction.
     */
    frac53: () => number;

    /**
     * Export the current Alea state so the pattern can be duplicated.
     */
    exportState: () => AleaState;

    /**
     * Import an Alea state.
     */
    importState: (state: AleaState) => Alea;
}

/**
 * Random number generator which can take a list of seed values and produce the
 * same consistent pseudo-random pattern.
 *
 * @param args One or more values to be used as seed, or an Alea instance, or
 *             an exported Alea state.
 */
export const Alea = (...args: any[]): Alea => {
    let s0 = 0;
    let s1 = 0;
    let s2 = 0;
    let c = 1;

    const rng = (() => {
        const t = 2091639 * s0 + c * 2.3283064365386963e-10;

        return s0 = s1, s1 = s2, s2 = t - (c = t | 0);
    }) as any as Alea;

    Object.defineProperties(rng, {
        version: { value: ALEA_VERSION },
        next: { value: rng },
        abs: { value: (max = 1, min = 0) => Math.abs(rng() * (max - min)) + min },
        max: { value: (max = 0x100000000) => rng() * max },
        int: { value: (max = 0x100000000) => Math.round(rng() * max) },
        uint: { value: (max = 0x100000000, min = 0) => Math.round(Math.abs(rng() * (max - min)) + min) },
        fract53: { value: () => rng() + (rng() * 0x200000 | 0) * 1.1102230246251565e-16 }, // 2^-53
        exportState: { value: () => Object.defineProperty([s0, s1, s2, c] as AleaState, "version", { value: ALEA_VERSION }) },
        importState: {
            value: (state: AleaState) => {
                if (state.version === ALEA_VERSION) {
                    s0 = +state[0] || 0;
                    s1 = +state[1] || 0;
                    s2 = +state[2] || 0;
                    c = +state[3] || 0;
                }

                return rng;
            },
        },
    });

    if (args.length === 1 && args[0].version === ALEA_VERSION) {
        if (args[0].length === 4) {
            rng.importState(args[0]);
        } else {
            rng.importState(args[0].exportState());
        }
    } else {
        const mash = Mash();

        s0 = mash(" ");
        s1 = mash(" ");
        s2 = mash(" ");

        if (!args.length) {
            args = [+new Date];
        }

        for (const arg of args) {
            s0 -= mash(arg);
            s1 -= mash(arg);
            s2 -= mash(arg);

            if (s0 < 0) {
                s0 += 1;
            }
            if (s1 < 0) {
                s1 += 1;
            }
            if (s2 < 0) {
                s2 += 1;
            }
        }
    }

    return rng;
}

export default Alea;

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions