简体   繁体   中英

Generating unbiased random float numbers [0, 1) in JavaScript

I found that the distribution of mantissa bits of random floats numbers generated by javascript Math.random() is biased. The least significant bit is always 0, and the right bits are more even than odd.

Also, many other representable floats in that interval [0, 1) are not possible selections. For example, 0.05954861408025609 isn't an integer multiple of 2**-52.

Is there a better way to generate floats without bias?

 // Histogram for mantissa bit distribution (52 bits) function histogram(func) { var counters = []; counters.length = 52; counters.fill(0); var u = new DataView(new ArrayBuffer(8)); for (var i = 0; i < 100000; i++) { var flt = func(); u.setFloat64(0, flt, false); var bits = u.getBigUint64(0); for (var j = 0; j < 52; j++) { counters[51 - j] += Number((bits >> BigInt(j)) & 1n); } } return counters; } var hist1 = histogram(Math.random); Highcharts.chart('container', { chart: { type: 'column' }, title: { text: 'Mantissa Bit Distribution' }, xAxis: { categories: [ '51', '50', '49', '48', '47', '46', '45', '44', '43', '42', '41', '40', '39', '38', '37', '36', '35', '34', '33', '32', '31', '30', '29', '28', '27', '26', '25', '24', '23', '22', '21', '20', '19', '18', '17', '16', '15', '14', '13', '12', '11', '10', '9', '8', '7', '6', '5', '4', '3', '2', '1', '0' ], crosshair: true }, yAxis: { min: 0, title: { text: 'Frequency' } }, series: [{name: 'Bit', data: hist1}] });
 <script src="https://code.highcharts.com/highcharts.js"></script> <script src="https://code.highcharts.com/modules/exporting.js"></script> <script src="https://code.highcharts.com/modules/export-data.js"></script> <script src="https://code.highcharts.com/modules/accessibility.js"></script> <figure class="highcharts-figure"> <div id="container"></div> </figure>

Ok, I have finally managed to implemented an unbiased function based on various codes found in the following links:

The function produce unbiased uniformly distributed float numbers in the range [0, 1). All representable numbers of the IEEE-754 floating-point format, in that interval, are possible selections ( total of 1022 * 2^52 normalized floating-point numbers).

Math.random() can only generate about 0.1% of the 52-bit mantissas that are possible in a double-precision floating point value. In other words, the generated numbers should be "more random" than those of Math.random.

The function output has passed the following statistical tests:

 // ##################################################################### // Generate uniformly distributed floating point numbers in [0, 1). // All representable numbers of IEEE-754 format are possible selections. function uniform01() { // Generate a random 32-bit unsigned integer within [0, 0xffffffff] const uniform32 = function() { return (Math.random() * 2 ** 32) >>> 0; }; // sample p from geometric(2) by finding the first 1-bit // in a stream of bits generated uniformly at random and // interpret it as a power of two, 00101000000000000000. let power2 = 2, x; while ((x = uniform32()) == 0) { power2 *= 2 ** 32; } // x & -x creates a bitmask for the rightmost set bit in x. // power2 is 2 with probability 1/2, 4 with probability 1/4, // 8 with probability 1/8, and so on. power2 *= (x & -x); // scale 2^52 numbers from [1, 2) into a smaller binade, // this constructs fp number in [0, 1) as dyadic rational. return (1.0 + Math.random()) / power2; } // ##################################################################### // Histogram for mantissa bit distribution (52 bits) function histogram(func) { var counters = Array(52).fill(0); var u = new DataView(new ArrayBuffer(8)); for (var i = 0; i < 100000; i++) { var flt = func(); u.setFloat64(0, flt); var bits = u.getBigUint64(0); for (var j = 0; j < 52; j++) { counters[51 - j] += Number((bits >> BigInt(j)) & 1n); } } return counters; } //var hist1 = histogram(Math.random); var hist1 = histogram(uniform01); Highcharts.chart('container', { chart: { type: 'column' }, title: { text: 'Mantissa Bit Distribution' }, xAxis: { categories: [ '51', '50', '49', '48', '47', '46', '45', '44', '43', '42', '41', '40', '39', '38', '37', '36', '35', '34', '33', '32', '31', '30', '29', '28', '27', '26', '25', '24', '23', '22', '21', '20', '19', '18', '17', '16', '15', '14', '13', '12', '11', '10', '9', '8', '7', '6', '5', '4', '3', '2', '1', '0' ], crosshair: true }, yAxis: { min: 0, title: { text: 'Frequency' } }, series: [{name: 'Bit', data: hist1}] });
 <script src="https://code.highcharts.com/highcharts.js"></script> <script src="https://code.highcharts.com/modules/exporting.js"></script> <script src="https://code.highcharts.com/modules/export-data.js"></script> <script src="https://code.highcharts.com/modules/accessibility.js"></script> <figure class="highcharts-figure"> <div id="container"></div> </figure>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM