Есть `Math.random()`, а затем есть `Math.random()`
Math.random()
возвращает значениеNumber
с положительным знаком, большее или равное0
, но меньшее1
, выбираемое случайным или псевдослучайным образом с приблизительно равномерным распределением в этом диапазоне, используя алгоритм или стратегию, зависящие от реализации. Эта функция не принимает аргументов.
Math.random()
— это самый известный и часто используемый источник случайности в JavaScript. В V8 и большинстве других движков JavaScript он реализован с использованием генератора псевдослучайных чисел (PRNG). Как и все PRNG, случайное число выводится из внутреннего состояния, которое изменяется фиксированным алгоритмом для каждого нового случайного числа. Таким образом, для заданного начального состояния последовательность случайных чисел является детерминированной. Поскольку размер битов n внутреннего состояния ограничен, числа, генерируемые PRNG, в конечном итоге будут повторяться. Верхняя граница длины периода этого цикла перестановки составляет 2n.
Существует много разных алгоритмов PRNG; среди самых известных — Mersenne-Twister и LCG. У каждого из них есть свои особенности, преимущества и недостатки. В идеале он должен использовать как можно меньше памяти для начального состояния, быть быстрым в выполнении, иметь большую длину периода и предлагать высококачественное случайное распределение. Хотя использование памяти, производительность и длину периода легко измерить или рассчитать, качество определить сложнее. За статистическими тестами для проверки качества случайных чисел стоит много математики. Фактический стандарт набора тестов PRNG, TestU01, реализует многие из этих тестов.
До конца 2015 года (до версии 4.9.40) выбор PRNG для V8 был MWC1616 (умножение с переносом, соединяя две части по 16 бит). Он использует 64 бита внутреннего состояния и выглядит примерно так:
uint32_t state0 = 1;
uint32_t state1 = 2;
uint32_t mwc1616() {
state0 = 18030 * (state0 & 0xFFFF) + (state0 >> 16);
state1 = 30903 * (state1 & 0xFFFF) + (state1 >> 16);
return state0 << 16 + (state1 & 0xFFFF);
}
Значение 32-бита затем преобразуется в число с плавающей точкой между 0 и 1 в соответствии с спецификацией.
MWC1616 использует мало памяти и довольно быстро вычисляется, но, к сожалению, предлагает низкое качество:
- Количество случайных значений, которые он может генерировать, ограничено 232, в отличие от 252 чисел между 0 и 1, которые может представлять двойная точность с плавающей точкой.
- Более значительная верхняя часть результата почти полностью зависит от значения state0. Длина периода составляла бы максимум 232, но вместо нескольких больших циклов перестановки существует множество коротких. При плохо выбранном начальном состоянии длина цикла могла бы составить менее 40 миллионов.
- Он не проходит многие статистические тесты в наборе TestU01.
Нам сообщили об этом проблеме, и, разобравшись с проблемой и проведя небольшое исследование, мы решили переосмыслить реализацию Math.random
, основываясь на алгоритме xorshift128+. Он использует 128 бит внутреннего состояния, имеет длину периода 2128 - 1 и проходит все тесты из набора TestU01.
Реализация была внедрена в V8 v4.9.41.0 всего через несколько дней после того, как мы узнали о проблеме. Она стала доступна с Chrome 49. Как Firefox, так и Safari также перешли на xorshift128+.
В V8 v7.1 реализация была снова скорректирована CL, опираясь только на state0. Дополнительные детали реализации можно найти в исходном коде.
Однако не заблуждайтесь: хотя xorshift128+ является огромным улучшением по сравнению с MWC1616, он все же не является криптографически безопасным. Для таких случаев использования, как хеширование, генерация подписей и шифрование/дешифрование, обычные генераторы псевдослучайных чисел не подходят. API веб-криптографии вводит метод window.crypto.getRandomValues
, который возвращает криптографически безопасные случайные значения, но за счет снижения производительности.
Пожалуйста, имейте в виду: если вы увидите области, которые можно улучшить в V8 и Chrome, даже те, которые — как эта — не напрямую влияют на соблюдение спецификаций, стабильность или безопасность, пожалуйста, сообщите нам об этой проблеме в нашем трекере ошибок.