В современном JavaScript существует два типа чисел:
Обычные числа в JavaScript хранятся в 64-битном формате IEEE-754, также известном как «числа с плавающей запятой двойной точности». Это числа, которые мы используем большую часть времени, и мы поговорим о них в этой главе.
Числа BigInt представляют собой целые числа произвольной длины. Иногда они необходимы, поскольку обычное целое число не может безопасно превышать (2 53 -1)
или быть меньше -(2 53 -1)
, как мы упоминали ранее в главе «Типы данных». Поскольку bigints используются в нескольких специальных областях, мы посвящаем им специальную главу BigInt.
Итак, здесь мы поговорим об обычных числах. Давайте расширим наши знания о них.
Представьте, нам нужно написать 1 миллиард. Очевидный способ:
пусть миллиард = 1000000000;
Мы также можем использовать подчеркивание _
в качестве разделителя:
пусть миллиард = 1_000_000_000;
Здесь подчеркивание _
играет роль «синтаксического сахара», оно делает число более читабельным. Движок JavaScript просто игнорирует _
между цифрами, поэтому это тот же миллиард, что и выше.
Однако в реальной жизни мы стараемся избегать написания длинных последовательностей нулей. Мы слишком ленивы для этого. Попробуем написать что-то вроде "1bn"
для миллиарда или "7.3bn"
для 7 миллиардов 300 миллионов. То же самое справедливо и для большинства больших чисел.
В JavaScript мы можем сократить число, добавив к нему букву "e"
и указав количество нулей:
пусть миллиард = 1e9; // 1 миллиард, буквально: 1 и 9 нулей предупреждение (7.3e9); // 7,3 миллиарда (то же самое, что 7300000000 или 7_300_000_000)
Другими словами, e
умножает число на 1
с заданным количеством нулей.
1e3 === 1 * 1000; // e3 означает *1000 1,23e6 === 1,23 * 1000000; // e6 означает *1000000
Теперь давайте напишем что-нибудь очень маленькое. Скажем, 1 микросекунда (одна миллионная секунды):
пусть mсs = 0,000001;
Как и раньше, может помочь использование "e"
. Если мы хотим избежать явного написания нулей, мы могли бы написать то же самое:
пусть mcs = 1e-6; // пять нулей слева от 1
Если посчитать нули в 0.000001
, их будет 6. Поэтому, естественно, это 1e-6
.
Другими словами, отрицательное число после "e"
означает деление на 1 с заданным количеством нулей:
// -3 делит на 1 с 3 нулями 1е-3 === 1/1000; // 0,001 // -6 делит на 1 с 6 нулями 1,23е-6 === 1,23/1000000; // 0.00000123 // пример с большим числом 1234е-2 === 1234/100; // 12.34, десятичная точка перемещается 2 раза
Шестнадцатеричные числа широко используются в JavaScript для представления цветов, кодирования символов и для многих других целей. Поэтому, естественно, существует более короткий способ их записи: 0x
, а затем число.
Например:
предупреждение (0xff); // 255 предупреждение (0xFF); // 255 (то же самое, регистр не имеет значения)
Двоичная и восьмеричная системы счисления используются редко, но также поддерживаются с использованием префиксов 0b
и 0o
:
пусть а = 0b11111111; // двоичная форма числа 255 пусть б = 0о377; // восьмеричная форма числа 255 Предупреждение (а == б); // правда, одно и то же число 255 с обеих сторон
Есть только 3 системы счисления с такой поддержкой. Для других систем счисления нам следует использовать функцию parseInt
(которую мы увидим позже в этой главе).
Метод num.toString(base)
возвращает строковое представление num
в системе счисления с заданной base
.
Например:
пусть число = 255; предупреждение(num.toString(16)); // фф Предупреждение(num.toString(2)); // 11111111
base
может варьироваться от 2
до 36
. По умолчанию это 10
.
Общие случаи использования для этого:
base=16 используется для шестнадцатеричных цветов, кодировок символов и т. д., цифры могут быть 0..9
или A..F
.
base=2 в основном предназначен для отладки побитовых операций, цифры могут быть 0
или 1
.
base=36 — максимум, цифры могут быть 0..9
или A..Z
. Для обозначения числа используется весь латинский алфавит. Забавный, но полезный случай для 36
— это когда нам нужно превратить длинный числовой идентификатор во что-то более короткое, например, чтобы сделать короткий URL-адрес. Можно просто представить это в системе счисления с основанием 36
:
Предупреждение(123456..toString(36)); // 2n9c
Две точки для вызова метода
Обратите внимание, что две точки в 123456..toString(36)
не являются опечаткой. Если мы хотим вызвать метод непосредственно для числа, как toString
в примере выше, то нам нужно поставить две точки ..
после него.
Если бы мы поместили одну точку: 123456.toString(36)
, возникла бы ошибка, поскольку синтаксис JavaScript подразумевает десятичную часть после первой точки. А если мы поставим еще одну точку, то JavaScript узнает, что десятичная часть пуста, и теперь выполнит метод.
Также можно написать (123456).toString(36)
.
Одной из наиболее часто используемых операций при работе с числами является округление.
Для округления имеется несколько встроенных функций:
Math.floor
Округляет в меньшую сторону: 3.1
становится 3
, а -1.1
становится -2
.
Math.ceil
Округляем вверх: 3.1
становится 4
, а -1.1
становится -1
.
Math.round
Округляет до ближайшего целого числа: 3.1
становится 3
, 3.6
становится 4
. В средних случаях 3.5
округляет до 4
и -3.5
округляет до -3
.
Math.trunc
(не поддерживается Internet Explorer)
Удаляет все после десятичной точки без округления: 3.1
становится 3
, -1.1
становится -1
.
Вот таблица, в которой суммированы различия между ними:
Math.floor | Math.ceil | Math.round | Math.trunc | |
---|---|---|---|---|
3.1 | 3 | 4 | 3 | 3 |
3.5 | 3 | 4 | 4 | 3 |
3.6 | 3 | 4 | 4 | 3 |
-1.1 | -2 | -1 | -1 | -1 |
-1.5 | -2 | -1 | -1 | -1 |
-1.6 | -2 | -1 | -2 | -1 |
Эти функции охватывают все возможные способы работы с десятичной частью числа. Но что, если мы хотим округлить число до n-th
знака после запятой?
Например, у нас есть 1.2345
, и мы хотим округлить его до двух цифр, получив только 1.23
.
Есть два способа сделать это:
Умножить и разделить.
Например, чтобы округлить число до второй цифры после запятой, мы можем умножить число на 100
, вызвать функцию округления, а затем разделить его обратно.
пусть число = 1,23456; alert( Math.round(num * 100) / 100 ); // 1,23456 -> 123,456 -> 123 -> 1,23
Метод toFixed(n) округляет число до n
цифр после точки и возвращает строковое представление результата.
пусть число = 12,34; предупреждение(num.toFixed(1)); // "12,3"
Это округляет вверх или вниз до ближайшего значения, аналогично Math.round
:
пусть число = 12,36; предупреждение(num.toFixed(1)); // "12,4"
Обратите внимание, что результатом toFixed
является строка. Если десятичная часть короче требуемого, в конец добавляются нули:
пусть число = 12,34; предупреждение(num.toFixed(5)); // "12.34000", добавлены нули, чтобы получилось ровно 5 цифр
Мы можем преобразовать его в число, используя унарный плюс или вызов Number()
, например, написав +num.toFixed(5)
.
Внутренне число представлено в 64-битном формате IEEE-754, поэтому для хранения числа имеется ровно 64 бита: 52 из них используются для хранения цифр, 11 из них хранят положение десятичной точки, а 1 бит используется для хранения цифр. это для знака.
Если число действительно велико, оно может переполнить 64-битное хранилище и стать специальным числовым значением Infinity
:
предупреждение(1e500); // Бесконечность
Что может быть немного менее очевидным, но случается довольно часто, так это потеря точности.
Рассмотрим этот (ложный!) тест на равенство:
предупреждение (0,1 + 0,2 == 0,3); // ЛОЖЬ
Правильно, если мы проверим, равна ли сумма 0.1
и 0.2
0.3
, мы получим false
.
Странный! Что это, если не 0.3
?
предупреждение(0,1 + 0,2); // 0.300000000000000004
Ой! Представьте, что вы создаете сайт электронной торговли, и посетитель кладет в корзину товары $0.10
и $0.20
. Общая сумма заказа составит $0.30000000000000004
. Это удивило бы любого.
Но почему это происходит?
Число хранится в памяти в двоичной форме, последовательности битов – единиц и нулей. Но такие дроби, как 0.1
, 0.2
, которые выглядят простыми в десятичной системе счисления, на самом деле являются бесконечными дробями в своей двоичной форме.
оповещение (0.1.toString (2)); // 0.0001100110011001100110011001100110011001100110011001101 оповещение (0.2.toString (2)); // 0.001100110011001100110011001100110011001100110011001101 оповещение((0,1 + 0,2).toString(2)); // 0.0100110011001100110011001100110011001100110011001101
Что такое 0.1
? Это единица, разделенная на десять 1/10
, одна десятая. В десятичной системе счисления такие числа легко представимы. Сравните это с одной третью: 1/3
. Получается бесконечная дробь 0.33333(3)
.
Таким образом, деление на степени 10
гарантированно хорошо работает в десятичной системе, а деление на 3
— нет. По этой же причине в двоичной системе счисления гарантированно работает деление на степени 2
, но 1/10
становится бесконечной двоичной дробью.
Просто невозможно сохранить ровно 0,1 или ровно 0,2 в двоичной системе, так же как невозможно сохранить одну треть в виде десятичной дроби.
Числовой формат IEEE-754 решает эту проблему путем округления до ближайшего возможного числа. Эти правила округления обычно не позволяют нам увидеть эту «незначительную потерю точности», но она существует.
Мы можем увидеть это в действии:
предупреждение(0.1.toFixed(20)); // 0.100000000000000000555
А когда мы суммируем два числа, их «потери точности» складываются.
Вот почему 0.1 + 0.2
— это не совсем 0.3
.
Не только JavaScript
Та же проблема существует во многих других языках программирования.
PHP, Java, C, Perl и Ruby дают одинаковый результат, поскольку они основаны на одном и том же числовом формате.
Можем ли мы обойти проблему? Конечно, самый надежный способ — округлить результат с помощью метода toFixed(n):
пусть сумма = 0,1 + 0,2; предупреждение( sum.toFixed(2) ); // "0,30"
Обратите внимание, что toFixed
всегда возвращает строку. Это гарантирует, что после десятичной точки есть 2 цифры. Это действительно удобно, если у нас есть интернет-магазин и нам нужно показать $0.30
. В других случаях мы можем использовать унарный плюс, чтобы преобразовать его в число:
пусть сумма = 0,1 + 0,2; предупреждение(+sum.toFixed(2)); // 0,3
Мы также можем временно умножить числа на 100 (или на большее число), чтобы превратить их в целые числа, выполнить математические вычисления, а затем разделить обратно. Затем, когда мы занимаемся математическими вычислениями с целыми числами, ошибка несколько уменьшается, но мы все равно получаем ее при делении:
предупреждение((0,1*10 + 0,2*10)/10); // 0,3 предупреждение((0,28*100 + 0,14*100)/100); // 0.4200000000000001
Таким образом, подход «умножение/деление» уменьшает ошибку, но не устраняет ее полностью.
Иногда мы могли вообще попытаться избежать дробей. Например, если мы имеем дело с магазином, то мы можем хранить цены в центах, а не в долларах. А что, если мы применим скидку 30%? На практике полное уклонение от дробей редко возможно. Просто закруглите их, чтобы при необходимости обрезать «хвосты».
Самое смешное
Попробуйте запустить это:
// Привет! Я самовозрастающее число! оповещение (9999999999999999 ); // показывает 100000000000000000
Это страдает от той же проблемы: потеря точности. Для числа предусмотрено 64 бита, из них 52 можно использовать для хранения цифр, но этого недостаточно. Таким образом, наименее значащие цифры исчезают.
JavaScript не вызывает ошибки в таких событиях. Он делает все возможное, чтобы вписать число в нужный формат, но, к сожалению, этот формат недостаточно велик.
Два нуля
Еще одним забавным следствием внутреннего представления чисел является наличие двух нулей: 0
и -0
.
Это связано с тем, что знак представлен одним битом, поэтому его можно устанавливать или не устанавливать для любого числа, включая ноль.
В большинстве случаев различие незаметно, поскольку операторы склонны относиться к ним как к одному и тому же.
Помните эти два специальных числовых значения?
Infinity
(и -Infinity
) — это особое числовое значение, которое больше (меньше) чего-либо.
NaN
представляет собой ошибку.
Они относятся к типу number
, но не являются «обычными» числами, поэтому для их проверки существуют специальные функции:
isNaN(value)
преобразует свой аргумент в число, а затем проверяет его на NaN
:
Предупреждение(isNaN(NaN)); // истинный Предупреждение(isNaN("str")); // истинный
Но нужна ли нам эта функция? Разве мы не можем просто использовать сравнение === NaN
? К сожалению, нет. Значение NaN
уникально тем, что оно не равно ничему, включая само себя:
Предупреждение( НЭН === НЭН ); // ЛОЖЬ
isFinite(value)
преобразует свой аргумент в число и возвращает true
если это обычное число, а не NaN/Infinity/-Infinity
:
Предупреждение(isFinite("15")); // истинный Предупреждение(isFinite("str")); // ложь, потому что специальное значение: NaN Предупреждение(isFinite(Бесконечность)); // false, потому что специальное значение: бесконечность
Иногда isFinite
используется для проверки того, является ли строковое значение обычным числом:
let num = +prompt("Введите число", ''); // будет истинно, если вы не введете Infinity, -Infinity или не число Предупреждение (isFinite (число));
Обратите внимание, что пустая строка или строка, содержащая только пробелы, рассматривается как 0
во всех числовых функциях, включая isFinite
.
Number.isNaN
и Number.isFinite
Методы Number.isNaN и Number.isFinite являются более «строгими» версиями функций isNaN
и isFinite
. Они не преобразуют свой аргумент в число автоматически, а вместо этого проверяют, принадлежит ли он number
типу.
Number.isNaN(value)
возвращает true
если аргумент принадлежит number
типу и имеет NaN
. В любом другом случае он возвращает false
.
предупреждение( Number.isNaN(NaN) ); // истинный alert( Number.isNaN("str" / 2)); // истинный // Обратите внимание на разницу: Предупреждение( Number.isNaN("str")); // false, потому что "str" относится к строковому типу, а не к числовому типу Предупреждение(isNaN("str")); // правда, потому что isNaN преобразует строку «str» в число и в результате этого преобразования получает NaN
Number.isFinite(value)
возвращает true
, если аргумент принадлежит number
типу и не является NaN/Infinity/-Infinity
. В любом другом случае он возвращает false
.
предупреждение( Number.isFinite(123) ); // истинный предупреждение( Number.isFinite(Бесконечность)); // ЛОЖЬ оповещение( Number.isFinite(2 / 0) ); // ЛОЖЬ // Обратите внимание на разницу: alert( Number.isFinite("123")); // false, поскольку "123" принадлежит строковому типу, а не числовому типу Предупреждение(isFinite("123")); // правда, потому что isFinite преобразует строку «123» в число 123
В некотором смысле, Number.isNaN
и Number.isFinite
проще и понятнее, чем функции isNaN
и isFinite
. Однако на практике чаще всего используются isNaN
и isFinite
, поскольку их короче писать.
Сравнение с Object.is
Существует специальный встроенный метод Object.is
, который сравнивает значения типа ===
, но он более надежен для двух крайних случаев:
Он работает с NaN
: Object.is(NaN, NaN) === true
, это хорошо.
Значения 0
и -0
различны: Object.is(0, -0) === false
, технически это правильно, потому что внутри числа есть знаковый бит, который может отличаться, даже если все остальные биты равны нулям.
Во всех остальных случаях Object.is(a, b)
аналогичен a === b
.
Мы упоминаем здесь Object.is
, потому что он часто используется в спецификации JavaScript. Когда внутреннему алгоритму необходимо сравнить два значения на предмет их идентичности, он использует Object.is
(внутренне называемый SameValue).
Числовое преобразование с использованием плюса +
или Number()
является строгим. Если значение не является точно числом, оно терпит неудачу:
Оповещение( +"100px"); // НЭН
Единственным исключением являются пробелы в начале или конце строки, поскольку они игнорируются.
Но в реальной жизни мы часто указываем значения в единицах измерения, например "100px"
или "12pt"
в CSS. Кроме того, во многих странах символ валюты идет после суммы, поэтому у нас есть "19€"
, и мы хотели бы извлечь из этого числовое значение.
Для этого и нужны parseInt
и parseFloat
.
Они «читают» число из строки до тех пор, пока не могут. В случае ошибки возвращается собранное число. Функция parseInt
возвращает целое число, а parseFloat
возвращает число с плавающей запятой:
Предупреждение(parseInt('100px')); // 100 Предупреждение(parseFloat('12.5em')); // 12,5 Предупреждение(parseInt('12.3')); // 12, возвращается только целая часть Предупреждение(parseFloat('12.3.4')); // 12.3, вторая точка останавливает чтение
Бывают ситуации, когда parseInt/parseFloat
вернет NaN
. Это происходит, когда цифры не читаются:
Предупреждение(parseInt('a123')); // NaN, первый символ останавливает процесс
Второй аргумент parseInt(str, radix)
Функция parseInt()
имеет необязательный второй параметр. Он определяет основу системы счисления, поэтому parseInt
также может анализировать строки шестнадцатеричных чисел, двоичных чисел и т. д.:
Предупреждение(parseInt('0xff', 16)); // 255 Предупреждение(parseInt('ff', 16)); // 255, без 0x тоже работает Предупреждение(parseInt('2n9c', 36)); // 123456
В JavaScript есть встроенный объект Math, который содержит небольшую библиотеку математических функций и констант.
Несколько примеров:
Math.random()
Возвращает случайное число от 0 до 1 (не включая 1).
предупреждение( Math.random()); // 0.1234567894322 предупреждение( Math.random()); // 0.5435252343232 предупреждение( Math.random()); // ... (любые случайные числа)
Math.max(a, b, c...)
и Math.min(a, b, c...)
Возвращает наибольшее и наименьшее из произвольного числа аргументов.
alert( Math.max(3, 5, -10, 0, 1) ); // 5 предупреждение(Math.min(1, 2)); // 1
Math.pow(n, power)
Возвращает n
возведенное в заданную степень.
предупреждение( Math.pow(2, 10)); // 2 в степени 10 = 1024
В объекте Math
имеется больше функций и констант, включая тригонометрию, которую вы можете найти в документации по объекту Math.
Чтобы записать числа со многими нулями:
Добавьте "e"
к числу нулей. Например: 123e6
— это то же самое, что 123
с 6 нулями 123000000
.
Отрицательное число после "e"
приводит к делению числа на 1 с заданными нулями. Например, 123e-6
означает 0.000123
( 123
миллионные).
Для разных систем счисления:
Может записывать числа непосредственно в шестнадцатеричной ( 0x
), восьмеричной ( 0o
) и двоичной ( 0b
) системах.
parseInt(str, base)
преобразует строку str
в целое число в системе счисления с заданным base
, 2 ≤ base ≤ 36
.
num.toString(base)
преобразует число в строку в системе счисления с заданной base
.
Для обычных числовых тестов:
isNaN(value)
преобразует свой аргумент в число, а затем проверяет его на NaN
Number.isNaN(value)
проверяет, принадлежит ли его аргумент number
типу, и если да, проверяет его на NaN
isFinite(value)
преобразует свой аргумент в число, а затем проверяет, не является ли он NaN/Infinity/-Infinity
Number.isFinite(value)
проверяет, принадлежит ли его аргумент number
типу, и если да, проверяет его на отсутствие NaN/Infinity/-Infinity
Для преобразования таких значений, как 12pt
и 100px
в число:
Используйте parseInt/parseFloat
для «мягкого» преобразования, которое считывает число из строки, а затем возвращает значение, которое оно могло прочитать до ошибки.
Для дробей:
Округлите с помощью Math.floor
, Math.ceil
, Math.trunc
, Math.round
или num.toFixed(precision)
.
Обязательно помните, что при работе с дробями происходит потеря точности.
Еще математические функции:
Просматривайте объект Math, когда он вам понадобится. Библиотека очень маленькая, но может удовлетворить основные потребности.
важность: 5
Создайте скрипт, который предлагает посетителю ввести два числа, а затем показывает их сумму.
Запустить демо-версию
PS Есть подвох с типами.
let a = +prompt("Первое число?", ""); let b = +prompt("Второе число?", ""); предупреждение (а + б);
Обратите внимание на унарный плюс +
перед prompt
. Он немедленно преобразует значение в число.
В противном случае a
и b
были бы строкой, а их сумма была бы их конкатенацией, то есть: "1" + "2" = "12"
.
важность: 4
Согласно документации Math.round
и toFixed
оба округляются до ближайшего числа: 0..4
впереди, а 5..9
вверх.
Например:
предупреждение(1.35.toFixed(1)); // 1.4
Почему в аналогичном примере ниже 6.35
округляется до 6.3
, а не до 6.4
?
предупреждение(6.35.toFixed(1)); // 6.3
Как правильно округлить 6.35
?
Внутренне десятичная дробь 6.35
представляет собой бесконечную двоичную дробь. Как всегда в таких случаях, оно сохраняется с потерей точности.
Давайте посмотрим:
предупреждение(6.35.toFixed(20)); // 6.34999999999999964473
Потеря точности может привести как к увеличению, так и к уменьшению числа. В данном конкретном случае число становится чуть-чуть меньше, поэтому округляется в меньшую сторону.
А что за 1.35
?
предупреждение(1.35.toFixed(20)); // 1.350000000000000008882
Здесь из-за потери точности число стало немного больше, поэтому оно округлилось в большую сторону.
Как мы можем решить проблему с 6.35
если хотим, чтобы оно было правильно округлено?
Нам следует приблизить его к целому числу перед округлением:
alert( (6.35 * 10).toFixed(20) ); // 63.50000000000000000000
Обратите внимание, что 63.5
вообще не имеет потери точности. Это потому, что десятичная часть 0.5
на самом деле равна 1/2
. Дроби, разделенные степенями 2
, точно представлены в двоичной системе, теперь мы можем ее округлить:
alert( Math.round(6.35 * 10) / 10 ); // 6,35 -> 63,5 -> 64(округлено) -> 6,4
важность: 5
Создайте функцию readNumber
, которая запрашивает число до тех пор, пока посетитель не введет допустимое числовое значение.
Полученное значение должно быть возвращено в виде числа.
Посетитель также может остановить процесс, введя пустую строку или нажав «ОТМЕНА». В этом случае функция должна возвращать null
.
Запустить демо-версию
Откройте песочницу с тестами.
функция readNumber() { пусть число; делать { num = Prompt("Введите число, пожалуйста?", 0); } while (!isFinite(num)); if (num === null || num === '') возвращает ноль; вернуть +номер; } alert(`Читать: ${readNumber()}`);
Решение немного сложнее, чем могло бы быть, потому что нам нужно обрабатывать null
/пустые строки.
Таким образом, мы фактически принимаем ввод до тех пор, пока он не станет «обычным числом». И null
(отмена), и пустая строка также соответствуют этому условию, поскольку в числовой форме они равны 0
.
После того, как мы остановились, нам нужно особым образом обработать null
и пустую строку (вернуть null
), поскольку преобразование их в число вернет 0
.
Откройте решение с тестами в песочнице.
важность: 4
Этот цикл бесконечен. Это никогда не заканчивается. Почему?
пусть я = 0; в то время как (я != 10) { я += 0,2; }
Это потому, что i
никогда не стал бы равен 10
.
Запустите его, чтобы увидеть реальные значения i
:
пусть я = 0; в то время как (я < 11) { я += 0,2; if (i > 9,8 && i < 10,2) alert( i ); }
Ни один из них не равен ровно 10
.
Такие вещи происходят из-за потери точности при добавлении дробей, таких как 0.2
.
Вывод: уклоняйтесь от проверок на равенство при работе с десятичными дробями.
важность: 2
Встроенная функция Math.random()
создает случайное значение от 0
до 1
(не включая 1
).
Напишите функцию random(min, max)
для генерации случайного числа с плавающей запятой от min
до max
(не включая max
).
Примеры его работы:
предупреждение(случайное(1, 5)); // 1.2345623452 предупреждение(случайное(1, 5)); // 3.7894332423 предупреждение(случайное(1, 5)); // 4.3435234525
Нам нужно «сопоставить» все значения из интервала 0…1 со значениями от min
до max
.
Это можно сделать в два этапа:
Если мы умножим случайное число от 0…1 на max-min
, то интервал возможных значений увеличится 0..1
до 0..max-min
.
Теперь, если мы добавим min
, возможный интервал станет от min
до max
.
Функция:
функция случайная(мин, макс) { вернуть мин + Math.random() * (макс – мин); } предупреждение(случайное(1, 5)); предупреждение(случайное(1, 5)); предупреждение(случайное(1, 5));
важность: 2
Создайте функцию randomInteger(min, max)
, которая генерирует случайное целое число от min
до max
включая min
и max
в качестве возможных значений.
Любое число из интервала min..max
должно появиться с одинаковой вероятностью.
Примеры его работы:
предупреждение(случайноеЦелое(1, 5)); // 1 предупреждение(случайноеЦелое(1, 5)); // 3 предупреждение(случайноеЦелое(1, 5)); // 5
В качестве основы можно использовать решение предыдущей задачи.
Самым простым, но неправильным решением было бы сгенерировать значение от min
до max
и округлить его:
функция randomInteger(мин, макс) { пусть rand = min + Math.random() * (макс – мин); вернуть Math.round(rand); } Предупреждение(случайноеЦелое(1, 3));
Функция работает, но она некорректна. Вероятность получить краевые значения min
и max
в два раза меньше любой другой.
Если вы запустите приведенный выше пример много раз, вы легко увидите, что чаще всего встречается 2
.
Это происходит потому, что Math.round()
получает случайные числа из интервала 1..3
и округляет их следующим образом:
значения от 1... до 1,4999999999 становятся 1 значения от 1,5... до 2,4999999999 становятся 2 значения от 2,5... до 2,9999999999 становятся 3
Теперь мы ясно видим, что 1
получает в два раза меньше значений, чем 2
. И то же самое с 3
.
Существует множество правильных решений задачи. Один из них — настройка границ интервалов. Чтобы обеспечить одинаковые интервалы, мы можем генерировать значения от 0.5 to 3.5
, добавляя таким образом к ребрам необходимые вероятности:
функция randomInteger(мин, макс) { // теперь rand составляет от (мин-0,5) до (макс+0,5) пусть rand = мин - 0,5 + Math.random() * (макс - мин + 1); вернуть Math.round(rand); } Предупреждение(случайноеЦелое(1, 3));
Альтернативным способом может быть использование Math.floor
для случайного числа от min
до max+1
:
функция randomInteger(мин, макс) { // здесь rand — от мин до (макс+1) пусть rand = min + Math.random() * (макс + 1 - мин); вернуть Math.floor(rand); } Предупреждение(случайноеЦелое(1, 3));
Теперь все интервалы отображаются следующим образом:
значения от 1... до 1,9999999999 становятся 1 значения от 2... до 2,9999999999 становятся 2 значения от 3... до 3,9999999999 становятся 3
Все интервалы имеют одинаковую длину, что делает окончательное распределение равномерным.