Случайные числа в java
Содержание:
1 Псевдослучайные числа
Иногда программист сталкивается с простыми, казалось бы, задачами: «отобрать случайный фильм для вечернего просмотра из определенного списка», «выбрать победителя лотереи», «перемешать список песен при тряске смартфона», «выбрать случайное число для шифрования сообщения», и каждый раз у него возникает очень закономерный вопрос: а как получить это самое случайное число?
Вообще-то, если вам нужно получить «настоящее случайное число», сделать это довольно-таки трудно. Вплоть до того, что в компьютер встраивают специальные математические сопроцессоры, которые умеют генерировать такие числа, с выполнением всех требований к «истинной случайности».
Поэтому программисты придумали свое решение — псевдослучайные числа. Псевдослучайные числа — это некая последовательность, числа в которой на первый взгляд кажутся случайными, но специалист при детальном анализе сможет найти в них определенные закономерности. Для шифрования секретных документов такие числа не подойдут, а для имитации бросания кубика в игре — вполне.
Есть много алгоритмов генерации последовательности псевдослучайных чисел и почти все из них генерируют следующее случайное число на основе предыдущего и еще каких-то вспомогательных чисел.
Например, данная программа выведет на экран неповторяющихся чисел:
Кстати, мы говорим не о псевдослучайных числах, а именно о последовательности таких чисел. Т.к. глядя на одно число невозможно понять, случайное оно или нет.
Случайное число ведь можно получить разными способами:
Why is this needed?
Despite being capable of producing numbers within [0, 1), there are a few downsides to doing so:
- It is inconsistent between engines as to how many bits of randomness:
- Internet Explorer: 53 bits
- Mozilla Firefox: 53 bits
- Google Chrome/node.js: 32 bits
- Apple Safari: 32 bits
- It is non-deterministic, which means you can’t replay results consistently
- In older browsers, there can be manipulation through cross-frame random polling. This is mostly fixed in newer browsers and is required to be fixed in ECMAScript 6.
Also, and most crucially, most developers tend to use improper and biased logic as to generating integers within a uniform distribution.
Alternate API
There is an alternate API which may be easier to use, but may be less performant. In scenarios where performance is paramount, it is recommended to use the aforementioned API.
constrandom=newRandom(MersenneTwister19937.seedWithArray(0x12345678,0x90abcdef));constvalue=r.integer(,99);constotherRandom=newRandom();
This abstracts the concepts of engines and distributions.
- : Produce an integer within the inclusive range . can be at its minimum -9007199254740992 (2 ** 53). can be at its maximum 9007199254740992 (2 ** 53). The special number is never returned.
- : Produce a floating point number within the range . Uses 53 bits of randomness.
- : Produce a boolean with a 50% chance of it being .
- : Produce a boolean with the specified chance causing it to be .
- : Produce a boolean with / chance of it being true.
- : Return a random value within the provided within the sliced bounds of and .
- : Shuffle the provided (in-place). Similar to .
- : From the array, produce an array with elements that are randomly chosen without repeats.
- : Same as
- : Produce an array of length with as many rolls.
- : Produce a random string using numbers, uppercase and lowercase letters, , and of length .
- : Produce a random string using the provided string as the possible characters to choose from of length .
- or : Produce a random string comprised of numbers or the characters of length .
- : Produce a random string comprised of numbers or the characters of length .
- : Produce a random within the inclusive range of . and must both be s.
Способы записи числа
Представьте, что нам надо записать число 1 миллиард. Самый очевидный путь:
Но в реальной жизни мы обычно опускаем запись множества нулей, так как можно легко ошибиться. Укороченная запись может выглядеть как или для 7 миллиардов 300 миллионов. Такой принцип работает для всех больших чисел.
В JavaScript можно использовать букву , чтобы укоротить запись числа. Она добавляется к числу и заменяет указанное количество нулей:
Другими словами, производит операцию умножения числа на 1 с указанным количеством нулей.
Сейчас давайте запишем что-нибудь очень маленькое. К примеру, 1 микросекунду (одна миллионная секунды):
Записать микросекунду в укороченном виде нам поможет .
Если мы подсчитаем количество нулей , их будет 6. Естественно, верная запись .
Другими словами, отрицательное число после подразумевает деление на 1 с указанным количеством нулей:
Шестнадцатеричные числа широко используются в JavaScript для представления цветов, кодировки символов и многого другого. Естественно, есть короткий стиль записи: , после которого указывается число.
Например:
Не так часто используются двоичные и восьмеричные числа, но они также поддерживаются для двоичных и для восьмеричных:
Есть только 3 системы счисления с такой поддержкой. Для других систем счисления мы рекомендуем использовать функцию (рассмотрим позже в этой главе).
Java Integer Math
Математические операции, выполняемые с целочисленными типами Java (byte, short, int и long), ведут себя немного иначе, чем обычные математические операции. Поскольку целочисленные типы не могут содержать дроби, в каждом вычислении с одним или несколькими целочисленными типами все дроби в результате обрезаются. Посмотрите на это математическое выражение:
int result = 100 / 8;
Результат этого деления будет 12,5, но так как два числа являются целыми числами, фракция .5 обрезается. Результат, следовательно, всего 12.
Округление также происходит в подрезультатах больших вычислений.
С плавающей точкой Math
Java содержит два типа данных с плавающей точкой: float и double. Они могут содержать дроби в числах. Если нужны дробные выражения в математических выражениях, вы должны использовать один из этих типов данных. Вот пример математического выражения с плавающей точкой:
double result = 100 / 8;
Несмотря на то, что переменная результата теперь имеет тип с плавающей запятой (double), конечный результат по-прежнему равен 12 вместо 12,5. Причина в том, что оба значения в математическом выражении (100 и 8) оба являются целыми числами. Таким образом, результат деления одного на другое сначала преобразуется в целое число (12), а затем присваивается переменной результата.
Чтобы избежать округления вычислений, необходимо убедиться, что все типы данных, включенные в математическое выражение, являются типами с плавающей запятой. Например, вы могли бы сначала присвоить значения переменным с плавающей запятой следующим образом:
double no1 = 100; double no2 = 8; double result = no1 / no2;
Теперь переменная результата будет иметь значение 12,5.
В Java есть способ заставить все числа в расчете быть переменными с плавающей точкой. Вы ставите числа с большой буквы F или D. Вот пример:
double result = 100D / 8D;
Обратите внимание на прописные буквы D после каждого числа. Этот верхний регистр D говорит Java, что эти числа должны интерпретироваться как числа с плавающей запятой, и, таким образом, деление должно быть делением с плавающей запятой, которое сохраняет дроби вместо их обрезания
На самом деле вы также можете сделать число длинным, добавив суффикс числа к верхнему регистру L, но long по-прежнему является целочисленным типом, поэтому он не будет сохранять дробные части в вычислениях.
Точность с плавающей точкой
Типы данных с плавающей точкой не являются точными на 100%. Вы можете столкнуться с ситуациями, когда числа со многими дробями не складываются с ожидаемым числом. Если вычисление с плавающей запятой приводит к числу с большим количеством дробей, чем может обработать число с плавающей запятой или двойное число, дроби могут быть обрезаны. Конечно, заданная точность может быть более чем достаточной для многих типов вычислений, но имейте в виду, что дроби могут фактически быть отсечены.
Посмотрите:
double resultDbl3 = 0D; System.out.println("resultDbl3 = " + resultDbl3); for(int i=0; i<100; i++){ resultDbl3 += 0.01D; } System.out.println("resultDbl3 = " + resultDbl3);
Вывод выводится при выполнении этого кода с Java 8:
resultDbl3 = 0.0 resultDbl3 = 1.0000000000000007
Первый оператор System.out.println() правильно печатает значение 0.0, которое является начальным значением переменной resultDbl3.
Однако второй оператор System.out.println() выводит несколько странный результат. Добавление значения 0,01 к 0 всего 100 раз должно привести к значению 1,0, верно? Но каким-то образом окончательный результат 1.0000000000000007. Как видите, что-то не так во фракциях.
Обычно неточность с плавающей запятой незначительна, но все же важно знать об этом
Usage
In your project, run the following command:
npm install random-js
or
yarn add random-js
In your code:
import{Random}from"random-js";constrandom=newRandom();constvalue=random.integer(1,100);
const{Random}=require("random-js");constrandom=newRandom();constvalue=random.integer(1,100);
Or to have more control:
constRandom=require("random-js").Random;constrandom=newRandom(MersenneTwister19937.autoSeed());constvalue=random.integer(1,100);
It is recommended to create one shared engine and/or instance per-process rather than one per file.
Download and place it in your project, then use one of the following patterns:
define(function(require){var Random =require("random");returnnewRandom.Random(Random.MersenneTwister19937.autoSeed());});define(function(require){var Random =require("random");returnnewRandom.Random();});define("random",function(Random){returnnewRandom.Random(Random.MersenneTwister19937.autoSeed());});
Download and place it in your project, then add it as a tag as such:
<scriptsrc="lib/random-js.min.js"><script><script>var random =newRandom.Random();alert("Random value from 1 to 100: "+random.integer(1,100));</script>
Класс Random
В качестве генератора псевдослучайных чисел можно также использовать класс java.util.Random, имеющий два
конструктора :
public Random(); public Random(long);
Поскольку Random создаёт псевдослучайное число, то определив начальное число, устанавливается начальная точка
случайной последовательности, способствующая получению одинаковых случайных последовательностей. Чтобы избежать такого
совпадения, обычно применяют второй конструктор с использованием в качестве инициирующего значения текущего времени.
В таблице представлены наиболее часто используемые методы генератора Random :
Метод | Описание |
---|---|
boolean nextBoolean() | получение следующего случайного значения типа boolean |
double nextDouble() | получение следующего случайного значения типа double |
float nextFloat() | получение следующего случайного значения типа float |
int nextInt() | получение следующего случайного значения типа int |
int nextInt(int n) | получение следующего случайного значения типа int в диапазоне от 0 до n |
long nextLong() | получение следующего случайного значения типа long |
void nextBytes(byte[] buf) | формирование массива из случайно генерируемых значений |
Пример получения псевдослучайного целочисленного значения с использованием класса Random :
Random random = new Random(); int i = random.nextInt();
С классом Random алгоритм получения псевдослучайного числа такой же, как и у метода random класса
Math. Допустим, что нам необходимо получить случайное число в диапазоне , 100 включительно. В этом случае
код может выглядеть следующим образом :
int min = 5; int max = 100; int diff = max - min; Random random = new Random(); int i = random.nextInt(diff + 1) + min;
Класс SecureRandom
В следующем примере формируется массив псевдослучайных значений типа byte :
SecureRandom random = new SecureRandom(); byte bytes[] = new byte; random.nextBytes(bytes);
Этот же массив можно сформировать методом generateSeed :
byte seed[] = random.generateSeed(8);
Пример использования SecureRandom представлен на странице
Симметричного шифрования.
Класс ThreadLocalRandom
В JDK 7 включен класс ThreadLocalRandom из многопоточного пакета
java.util.concurrent, который следует использовать для получения псевдослучайных
значений в многопоточных приложениях. Для получения экземпляра ThreadLocalRandom следует использовать
статический метод current() данного класса. Пример :
ThreadLocalRandom random = ThreadLocalRandom.current(); System.out.println("Random values : "); System.out.println("boolean : " + random.nextBoolean()); System.out.println("int : " + random.nextInt ()); System.out.println("float : " + random.nextFloat ()); System.out.println("long : " + random.nextLong ()); System.out.println("int from 0 to 5 : " + random.nextInt(5)); System.out.println("long from 5 to 15 : " + random.nextLong(5, 15));
Why is this needed?
Despite being capable of producing numbers within [0, 1), there are a few downsides to doing so:
- It is inconsistent between engines as to how many bits of randomness:
- Internet Explorer: 53 bits
- Mozilla Firefox: 53 bits
- Google Chrome/node.js: 32 bits
- Apple Safari: 32 bits
- It is non-deterministic, which means you can’t replay results consistently
- In older browsers, there can be manipulation through cross-frame random polling. This is mostly fixed in newer browsers and is required to be fixed in ECMAScript 6.
Also, and most crucially, most developers tend to use improper and biased logic as to generating integers within a uniform distribution.
Неточные вычисления
Внутри JavaScript число представлено в виде 64-битного формата IEEE-754. Для хранения числа используется 64 бита: 52 из них используется для хранения цифр, 11 из них для хранения положения десятичной точки (если число целое, то хранится 0), и один бит отведён на хранение знака.
Если число слишком большое, оно переполнит 64-битное хранилище, JavaScript вернёт бесконечность:
Наиболее часто встречающаяся ошибка при работе с числами в JavaScript – это потеря точности.
Посмотрите на это (неверное!) сравнение:
Да-да, сумма и не равна .
Странно! Что тогда, если не ?
Но почему это происходит?
Число хранится в памяти в бинарной форме, как последовательность бит – единиц и нулей. Но дроби, такие как , , которые выглядят довольно просто в десятичной системе счисления, на самом деле являются бесконечной дробью в двоичной форме.
Другими словами, что такое ? Это единица делённая на десять — , одна десятая. В десятичной системе счисления такие числа легко представимы, по сравнению с одной третьей: , которая становится бесконечной дробью .
Деление на гарантированно хорошо работает в десятичной системе, но деление на – нет. По той же причине и в двоичной системе счисления, деление на обязательно сработает, а становится бесконечной дробью.
В JavaScript нет возможности для хранения точных значений 0.1 или 0.2, используя двоичную систему, точно также, как нет возможности хранить одну третью в десятичной системе счисления.
Числовой формат IEEE-754 решает эту проблему путём округления до ближайшего возможного числа. Правила округления обычно не позволяют нам увидеть эту «крошечную потерю точности», но она существует.
Пример:
И когда мы суммируем 2 числа, их «неточности» тоже суммируются.
Вот почему – это не совсем .
Не только в JavaScript
Справедливости ради заметим, что ошибка в точности вычислений для чисел с плавающей точкой сохраняется в любом другом языке, где используется формат IEEE 754, включая PHP, Java, C, Perl, Ruby.
Можно ли обойти проблему? Конечно, наиболее надёжный способ — это округлить результат используя метод toFixed(n):
Также можно временно умножить число на 100 (или на большее), чтобы привести его к целому, выполнить математические действия, а после разделить обратно. Суммируя целые числа, мы уменьшаем погрешность, но она все равно появляется при финальном делении:
Таким образом, метод умножения/деления уменьшает погрешность, но полностью её не решает.
Забавный пример
Попробуйте выполнить его:
Причина та же – потеря точности. Из 64 бит, отведённых на число, сами цифры числа занимают до 52 бит, остальные 11 бит хранят позицию десятичной точки и один бит – знак. Так что если 52 бит не хватает на цифры, то при записи пропадут младшие разряды.
Интерпретатор не выдаст ошибку, но в результате получится «не совсем то число», что мы и видим в примере выше. Как говорится: «как смог, так записал».
Два нуля
Другим забавным следствием внутреннего представления чисел является наличие двух нулей: и .
Все потому, что знак представлен отдельным битом, так что, любое число может быть положительным и отрицательным, включая нуль.
В большинстве случаев это поведение незаметно, так как операторы в JavaScript воспринимают их одинаковыми.
Random Numbers Using the Math Class
Java provides the Math class in the java.util package to generate random numbers.
The Math class contains the static Math.random() method to generate random numbers of the double type.
The random() method returns a double value with a positive sign, greater than or equal to 0.0 and less than 1.0. When you call Math.random(), under the hood, a java.util.Random pseudorandom-number generator object is created and used.
You can use the Math.random() method with or without passing parameters. If you provide parameters, the method produces random numbers within the given parameters.
The code to use the Math.random() method:
The getRandomNumber() method uses the Math.random() method to return a positive double value that is greater than or equal to 0.0 and less than 1.0.
The output of running the code is:
Random Numbers Within a Given Range
For generating random numbers between a given a range, you need to specify the range. A standard expression for accomplishing this is:
Let us break this expression into steps:
- First, multiply the magnitude of the range of values you want to cover by the result that Math.random() produces.returns a value in the range ,max–min where max is excluded. For example, if you want 5,10], you need to cover 5 integer values so you can use Math.random()*5. This would return a value in the range ,5, where 5 is not included.
- Next, shift this range up to the range that you are targeting. You do this by adding the min value.
But this still does not include the maximum value.
To get the max value included, you need to add 1 to your range parameter (max — min). This will return a random double within the specified range.
There are different ways of implementing the above expression. Let us look at a couple of them.
Random Double Within a Given Range
By default, the Math.random() method returns a random number of the type double whenever it is called. The code to generate a random double value between a specified range is:
You can call the preceding method from the main method by passing the arguments like this.
The output is this.
Random Integer Within a Given Range
The code to generate a random integer value between a specified range is this.
The preceding getRandomIntegerBetweenRange() method produces a random integer between the given range. As Math.random() method generates random numbers of double type, you need to truncate the decimal part and cast it to int in order to get the integer random number. You can call this method from the main method by passing the arguments as follows:
The output is this.
Note: You can pass a range of negative values to generate a random negative number within the range.
Core team and contributors
Awesome contributors
- Adriano Machado
- Alberto Lagna
- Andrew Neal
- Arne Zelasko
- dadiyang
- Dovid Kopel
- Eric Taix
- euZebe
- Fred Eckertson
- huningd
- Johan Kindgren
- Joren Inghelbrecht
- Jose Manuel Prieto
- kermit-the-frog
- Lucas Andersson
- Michael Düsterhus
- Nikola Milivojevic
- nrenzoni
- Oleksandr Shcherbyna
- Petromir Dzhunev
- Rebecca McQuary
- Rodrigue Alcazar
- Ryan Dunckel
- Sam Van Overmeire
- Valters Vingolds
- Vincent Potucek
- Weronika Redlarska
- Konstantin Lutovich
- Steven_Van_Ophem
- Jean-Michel Leclercq
- Marian Jureczko
- Unconditional One
- JJ1216
- Sergey Chernov
Thank you all for your contributions!
Usage
node.js
In your project, run the following command:
npm install random-js
or
yarn add random-js
In your code:
// ES6 Modules import { Random } from "random-js"; const random = new Random(); // uses the nativeMath engine const value = random.integer(1, 100);
// CommonJS Modules const { Random } = require("random-js"); const random = new Random(); // uses the nativeMath engine const value = random.integer(1, 100);
Or to have more control:
const Random = require("random-js").Random; const random = new Random(MersenneTwister19937.autoSeed()); const value = random.integer(1, 100);
It is recommended to create one shared engine and/or instance per-process rather than one per file.
Browser using AMD or RequireJS
Download and place it in your project, then use one of the following patterns:
define(function(require) { var Random = require("random"); return new Random.Random(Random.MersenneTwister19937.autoSeed()); }); define(function(require) { var Random = require("random"); return new Random.Random(); }); define("random", function(Random) { return new Random.Random(Random.MersenneTwister19937.autoSeed()); });
Browser using tag
Download and place it in your project, then add it as a tag as such:
<script src="lib/random-js.min.js"></script> <script> // Random is now available as a global (on the window object) var random = new Random.Random(); alert("Random value from 1 to 100: " + random.integer(1, 100)); </script>
Using Java API
The Java API provides us with several ways to achieve our purpose. Let’s see some of them.
2.1. java.lang.Math
The random method of the Math class will return a double value in a range from 0.0 (inclusive) to 1.0 (exclusive). Let’s see how we’d use it to get a random number in a given range defined by min and max:
2.2. java.util.Random
Before Java 1.7, the most popular way of generating random numbers was using nextInt. There were two ways of using this method, with and without parameters. The no-parameter invocation returns any of the int values with approximately equal probability. So, it’s very likely that we’ll get negative numbers:
If we use the netxInt invocation with the bound parameter, we’ll get numbers within a range:
This will give us a number between 0 (inclusive) and parameter (exclusive). So, the bound parameter must be greater than 0. Otherwise, we’ll get a java.lang.IllegalArgumentException.
Java 8 introduced the new ints methods that return a java.util.stream.IntStream. Let’s see how to use them.
The ints method without parameters returns an unlimited stream of int values:
We can also pass in a single parameter to limit the stream size:
And, of course, we can set the maximum and minimum for the generated range:
2.3. java.util.concurrent.ThreadLocalRandom
Java 1.7 release brought us a new and more efficient way of generating random numbers via the ThreadLocalRandom class. This one has three important differences from the Random class:
- We don’t need to explicitly initiate a new instance of ThreadLocalRandom. This helps us to avoid mistakes of creating lots of useless instances and wasting garbage collector time
- We can’t set the seed for ThreadLocalRandom, which can lead to a real problem. If we need to set the seed, then we should avoid this way of generating random numbers
- Random class doesn’t perform well in multi-threaded environments
Now, let’s see how it works:
With Java 8 or above, we have new possibilities. Firstly, we have two variations for the nextInt method:
Secondly, and more importantly, we can use the ints method:
2.4. java.util.SplittableRandom
Java 8 has also brought us a really fast generator — the SplittableRandom class.
As we can see in the JavaDoc, this is a generator for use in parallel computations. It’s important to know that the instances are not thread-safe. So, we have to take care when using this class.
We have available the nextInt and ints methods. With nextInt we can set directly the top and bottom range using the two parameters invocation:
This way of using checks that the max parameter is bigger than min. Otherwise, we’ll get an IllegalArgumentException. However, it doesn’t check if we work with positive or negative numbers. So, any of the parameters can be negative. Also, we have available one- and zero-parameter invocations. Those work in the same way as we have described before.
We have available the ints methods, too. This means that we can easily get a stream of int values. To clarify, we can choose to have a limited or unlimited stream. For a limited stream, we can set the top and bottom for the number generation range:
2.5. java.security.SecureRandom
If we have security-sensitive applications, we should consider using SecureRandom. This is a cryptographically strong generator. Default-constructed instances don’t use cryptographically random seeds. So, we should either:
- Set the seed — consequently, the seed will be unpredictable
- Set the java.util.secureRandomSeed system property to true
This class inherits from java.util.Random. So, we have available all the methods we saw above. For example, if we need to get any of the int values, then we’ll call nextInt without parameters:
On the other hand, if we need to set the range, we can call it with the bound parameter:
We must remember that this way of using it throws IllegalArgumentException if the parameter is not bigger than zero.
Класс Java Math
Класс Java Math предоставляет более сложные математические вычисления, чем те, которые предоставляют базовые математические операторы Java. Класс Math содержит методы для:
- нахождения максимального или минимального значений;
- значений округления;
- логарифмических функций;
- квадратного корня;
- тригонометрических функций (sin, cos, tan и т. д.).
Math находится в пакете java.lang, а не в пакете java.math. Таким образом, полное имя класса Math — это java.lang.Math.
Поскольку многие его функции независимы друг от друга, каждый метод будет объяснен в своем собственном разделе ниже.