math/random.js

'use strict';

/**
 * @author schukai GmbH
 */


import {Monster, getGlobal} from '../types/global.js';


/**
 * this function uses crypt and returns a random number.
 *
 * you can call the method via the monster namespace `Monster.Math.random()`.
 *
 * ```
 * <script type="module">
 * import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.6.0/dist/modules/math/random.js';
 * console.log(Monster.Math.random(1,10)) // ↦ 5
 * </script>
 * ```
 *
 * Alternatively, you can also integrate this function individually.
 *
 * ```
 * <script type="module">
 * import {random} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.6.0/dist/modules/math/random.js';
 * console.log(random(1,10)) // ↦ 5
 * </script>
 * ```
 *
 * @param {number} min starting value of the definition set (default is 0)
 * @param {number} max end value of the definition set (default is 1000000000)
 * @returns {number}
 * @memberOf Monster/Math

 * @since 1.0.0
 * @copyright schukai GmbH
 */
function random(min, max) {

    if (min === undefined) {
        min = 0;
    }
    if (max === undefined) {
        max = MAX;
    }

    if (max < min) {
        throw new Error("max must be greater than min");
    }

    return Math.round(create(min, max));

}

/**
 * @private
 * @type {number}
 */
var MAX = 1000000000;


Math.log2 = Math.log2 || function (n) {
    return Math.log(n) / Math.log(2);
};

/**
 *
 * @param min
 * @param max
 * @returns {*}
 * @private
 */
function create(min, max) {
    let crypt;
    let globalReference = getGlobal();

    crypt = globalReference?.['crypto'] || globalReference?.['msCrypto'] || globalReference?.['crypto'] || undefined;

    if (typeof crypt === "undefined") {
        throw new Error("missing crypt")
    }

    let rval = 0;
    const range = max - min;
    if (range < 2) {
        return min;
    }

    const bitsNeeded = Math.ceil(Math.log2(range));
    if (bitsNeeded > 53) {
        throw  new Error("we cannot generate numbers larger than 53 bits.");
    }
    const bytesNeeded = Math.ceil(bitsNeeded / 8);
    const mask = Math.pow(2, bitsNeeded) - 1;

    const byteArray = new Uint8Array(bytesNeeded);
    crypt.getRandomValues(byteArray);

    let p = (bytesNeeded - 1) * 8;
    for (var i = 0; i < bytesNeeded; i++) {
        rval += byteArray[i] * Math.pow(2, p);
        p -= 8;
    }

    rval = rval & mask;

    if (rval >= range) {
        return create(min, max);
    }

    return min + rval;

}

Monster.assignToNamespace('Monster.Math', random);
export {Monster, random}