Числа

Все числа в JavaScript, как целые так и дробные, имеют тип Number и хранятся в 64-битном формате IEEE-754, также известном как «double precision».

Здесь мы рассмотрим различные тонкости, связанные с работой с числами в JavaScript.

Способы записи

В JavaScript можно записывать числа не только в десятичной, но и в шестнадцатеричной (начинается с 0x) системе счисления:

alert( 0xFF ); // 255 в шестнадцатиричной системе

Также доступна запись в «научном формате» (ещё говорят «запись с плавающей точкой»), который выглядит как <число>e<кол-во нулей>.

Например, 1e3 – это 1 с 3 нулями, то есть 1000.

// еще пример научной формы: 3 с 5 нулями
alert( 3e5 ); // 300000

Если количество нулей отрицательно, то число сдвигается вправо за десятичную точку, так что получается десятичная дробь:

// здесь 3 сдвинуто 5 раз вправо, за десятичную точку.
alert( 3e-5 ); // 0.00003  <-- 5 нулей, включая начальный ноль

Деление на ноль, Infinity

Представьте, что вы собираетесь создать новый язык… Люди будут называть его «JavaScript» (или «LiveScript»… неважно).

Что должно происходить при попытке деления на ноль?

Как правило, ошибка в программе… Во всяком случае, в большинстве языков программирования это именно так.

Но создатель JavaScript решил пойти математически правильным путем. Ведь чем меньше делитель, тем больше результат. При делении на очень-очень маленькое число должно получиться очень большое. В математическом анализе это описывается через пределы, и если подразумевать предел, то в качестве результата деления на 0 мы получаем «бесконечность», которая обозначается символом или, в JavaScript: "Infinity".

alert( 1 / 0 ); // Infinity
alert( 12345 / 0 ); // Infinity

Infinity – особенное численное значение, которое ведет себя в точности как математическая бесконечность .

  • Infinity больше любого числа.
  • Добавление к бесконечности не меняет её.
alert( Infinity > 1234567890 ); // true
alert( Infinity + 5 == Infinity ); // true

Бесконечность можно присвоить и в явном виде: var x = Infinity.

Бывает и минус бесконечность -Infinity:

alert( -1 / 0 ); // -Infinity

Бесконечность можно получить также, если сделать ну очень большое число, для которого количество разрядов в двоичном представлении не помещается в соответствующую часть стандартного 64-битного формата, например:

alert( 1e500 ); // Infinity

NaN

Если математическая операция не может быть совершена, то возвращается специальное значение NaN (Not-A-Number).

Например, деление 0/0 в математическом смысле неопределено, поэтому его результат NaN:

alert( 0 / 0 ); // NaN

Значение NaN используется для обозначения математической ошибки и обладает следующими свойствами:

  • Значение NaN – единственное, в своем роде, которое не равно ничему, включая себя.

    Следующий код ничего не выведет:

    if (NaN == NaN) alert( "==" ); // Ни один вызов
    if (NaN === NaN) alert( "===" ); // не сработает
  • Значение NaN можно проверить специальной функцией isNaN(n), которая преобразует аргумент к числу и возвращает true, если получилось NaN, и false – для любого другого значения.

    var n = 0 / 0;
    
    alert( isNaN(n) ); // true
    alert( isNaN("12") ); // false, строка преобразовалась к обычному числу 12
  • Значение NaN «прилипчиво». Любая операция с NaN возвращает NaN.

    alert( NaN + 1 ); // NaN

Если аргумент isNaN – не число, то он автоматически преобразуется к числу.

Забавный способ проверки на NaN

Отсюда вытекает забавный способ проверки значения на NaN: можно проверить значение на равенство самому себе, если не равно – то NaN:

var n = 0 / 0;

if (n !== n) alert( 'n = NaN!' );

Это работает, но для наглядности лучше использовать isNaN(n).

Математические операции в JS безопасны

Никакие математические операции в JavaScript не могут привести к ошибке или «обрушить» программу.

В худшем случае, результат будет NaN.

isFinite(n)

Итак, в JavaScript есть обычные числа и три специальных числовых значения: NaN, Infinity и -Infinity.

Тот факт, что они, хоть и особые, но – числа, демонстрируется работой оператора +:

var value = prompt("Введите Infinity", 'Infinity');

var number = +value;

alert( number ); // Infinity, плюс преобразовал строку "Infinity" к такому "числу"

Обычно если мы хотим от посетителя получить число, то Infinity или NaN нам не подходят. Для того, чтобы отличить «обычные» числа от таких специальных значений, существует функция isFinite.

Функция isFinite(n) преобразует аргумент к числу и возвращает true, если это не NaN/Infinity/-Infinity:

alert( isFinite(1) ); // true
alert( isFinite(Infinity) ); // false
alert( isFinite(NaN) ); // false

Преобразование к числу

Большинство арифметических операций и математических функций преобразуют значение в число автоматически.

Для того, чтобы сделать это явно, обычно перед значением ставят унарный плюс '+':

var s = "12.34";
alert( +s ); // 12.34

При этом, если строка не является в точности числом, то результат будет NaN:

alert( +"12test" ); // NaN

Единственное исключение – пробельные символы в начале и в конце строки, которые игнорируются:

alert( +"  -12" ); // -12
alert( +" \n34  \n" ); // 34, перевод строки \n является пробельным символом
alert( +"" ); // 0, пустая строка становится нулем
alert( +"1 2" ); // NaN, пробел посередине числа - ошибка

Аналогичным образом происходит преобразование и в других математических операторах и функциях:

alert( '12.34' / "-2" ); // -6.17

Мягкое преобразование: parseInt и parseFloat

В мире HTML/CSS многие значения не являются в точности числами. Например, метрики CSS: 10pt или -12px.

Оператор '+' для таких значений возвратит NaN:

alert(+"12px") // NaN

Для удобного чтения таких значений существует функция parseInt:

alert( parseInt('12px') ); // 12

Функция parseInt и ее аналог parseFloat преобразуют строку символ за символом, пока это возможно.

При возникновении ошибки возвращается число, которое получилось. Функция parseInt читает из строки целое число, а parseFloat – дробное.

alert( parseInt('12px') ) // 12, ошибка на символе 'p'
alert( parseFloat('12.3.4') ) // 12.3, ошибка на второй точке

Конечно, существуют ситуации, когда parseInt/parseFloat возвращают NaN. Это происходит при ошибке на первом же символе:

alert( parseInt('a123') ); // NaN

Функция parseInt также позволяет указать систему счисления, то есть считывать числа, заданные в шестнадцатиричной и других системах счисления:

alert( parseInt('FF', 16) ); // 255

Проверка на число

Для проверки строки на число можно использовать функцию isNaN(str).

Она преобразует строку в число аналогично +, а затем вернёт true, если это NaN, т.е. если преобразование не удалось:

var x = prompt("Введите значение", "-11.5");

if (isNaN(x)) {
  alert( "Строка преобразовалась в NaN. Не число" );
} else {
  alert( "Число" );
}

Однако, у такой проверки есть две особенности:

  1. Пустая строка и строка из пробельных символов преобразуются к 0, поэтому считаются числами.
  2. Если применить такую проверку не к строке, то могут быть сюрпризы, в частности isNaN посчитает числами значения false, true, null, так как они хотя и не числа, но преобразуются к ним.
alert( isNaN(null) ); //  false - не NaN, т.е. "число"
alert( isNaN("\n  \n") ); //  false - не NaN, т.е. "число"

Если такое поведение допустимо, то isNaN – приемлемый вариант.

Если же нужна действительно точная проверка на число, которая не считает числом строку из пробелов, логические и специальные значения, а также отсекает Infinity – используйте следующую функцию isNumeric:

function isNumeric(n) {
  return !isNaN(parseFloat(n)) && isFinite(n);
}

Разберёмся, как она работает. Начнём справа.

  • Функция isFinite(n) преобразует аргумент к числу и возвращает true, если это не Infinity/-Infinity/NaN.

    Таким образом, правая часть отсеет заведомо не-числа, но оставит такие значения как true/false/null и пустую строку '', т.к. они корректно преобразуются в числа.

  • Для их проверки нужна левая часть. Вызов parseFloat(true/false/null/'') вернёт NaN для этих значений.

    Так устроена функция parseFloat: она преобразует аргумент к строке, т.е. true/false/null становятся "true"/"false"/"null", а затем считывает из неё число, при этом пустая строка даёт NaN.

В результате отсеивается всё, кроме строк-чисел и обычных чисел.

toString(система счисления)

Как показано выше, числа можно записывать не только в 10-чной, но и в 16-ричной системе. Но бывает и противоположная задача: получить 16-ричное представление числа. Для этого используется метод toString(основание системы), например:

var n = 255;

alert( n.toString(16) ); // ff

В частности, это используют для работы с цветовыми значениями в браузере, вида #AABBCC.

Основание может быть любым от 2 до 36.

  • Основание 2 бывает полезно для отладки побитовых операций:

    var n = 4;
    alert( n.toString(2) ); // 100
  • Основание 36 (по количеству букв в английском алфавите – 26, вместе с цифрами, которых 10) используется для того, чтобы «кодировать» число в виде буквенно-цифровой строки. В этой системе счисления сначала используются цифры, а затем буквы от a до z:

    var n = 1234567890;
    alert( n.toString(36) ); // kf12oi

    При помощи такого кодирования можно «укоротить» длинный цифровой идентификатор, например чтобы выдать его в качестве URL.

Округление

Одна из самых частых операций с числом – округление. В JavaScript существуют целых 3 функции для этого.

Math.floor
Округляет вниз
Math.ceil
Округляет вверх
Math.round
Округляет до ближайшего целого
alert( Math.floor(3.1) );  // 3
alert( Math.ceil(3.1) );   // 4
alert( Math.round(3.1) );  // 3
Округление битовыми операторами

Битовые операторы делают любое число 32-битным целым, обрезая десятичную часть.

В результате побитовая операция, которая не изменяет число, например, двойное битовое НЕ – округляет его:

alert( ~~12.3 ); // 12

Любая побитовая операция такого рода подойдет, например XOR (исключающее ИЛИ, "^") с нулем:

alert( 12.3 ^ 0 ); // 12
alert( 1.2 + 1.3 ^ 0 ); // 2, приоритет ^ меньше, чем +

Это удобно в первую очередь тем, что легко читается и не заставляет ставить дополнительные скобки как Math.floor(...):

var x = a * b / c ^ 0; // читается как "a * b / c и округлить"

Округление до заданной точности

Для округления до нужной цифры после запятой можно умножить и поделить на 10 с нужным количеством нулей. Например, округлим 3.456 до 2-го знака после запятой:

var n = 3.456;
alert( Math.round(n * 100) / 100 ); // 3.456 -> 345.6 -> 346 -> 3.46

Таким образом можно округлять число и вверх и вниз.

num.toFixed(precision)

Существует также специальный метод num.toFixed(precision), который округляет число num до точности precision и возвращает результат в виде строки:

var n = 12.34;
alert( n.toFixed(1) ); // "12.3"

Округление идёт до ближайшего значения, аналогично Math.round:

var n = 12.36;
alert( n.toFixed(1) ); // "12.4"

Итоговая строка, при необходимости, дополняется нулями до нужной точности:

var n = 12.34;
alert( n.toFixed(5) ); // "12.34000", добавлены нули до 5 знаков после запятой

Если нам нужно именно число, то мы можем получить его, применив '+' к результату n.toFixed(..):

var n = 12.34;
alert( +n.toFixed(5) ); // 12.34
Метод toFixed не эквивалентен Math.round!

Например, произведём округление до одного знака после запятой с использованием двух способов: toFixed и Math.round с умножением и делением:

var price = 6.35;

alert( price.toFixed(1) ); // 6.3
alert( Math.round(price * 10) / 10 ); // 6.4

Как видно, результат разный! Вариант округления через Math.round получился более корректным, так как по общепринятым правилам 5 округляется вверх. А toFixed может округлить его как вверх, так и вниз. Почему? Скоро узнаем!

Неточные вычисления

Запустите этот пример:

alert( 0.1 + 0.2 == 0.3 );

Запустили? Если нет – все же сделайте это.

Ок, вы запустили его. Он вывел false. Результат несколько странный, не так ли? Возможно, ошибка в браузере? Поменяйте браузер, запустите еще раз.

Хорошо, теперь мы можем быть уверены: 0.1 + 0.2 это не 0.3. Но тогда что же это?

alert( 0.1 + 0.2 ); // 0.30000000000000004

Как видите, произошла небольшая вычислительная ошибка, результат сложения 0.1 + 0.2 немного больше, чем 0.3.

alert( 0.1 + 0.2 > 0.3 ); // true

Всё дело в том, что в стандарте IEEE 754 на число выделяется ровно 8 байт(=64 бита), не больше и не меньше.

Число 0.1 (одна десятая) записывается просто в десятичном формате. Но в двоичной системе счисления это бесконечная дробь, так как единица на десять в двоичной системе так просто не делится. Также бесконечной дробью является 0.2 (=2/10).

Двоичное значение бесконечных дробей хранится только до определенного знака, поэтому возникает неточность. Её даже можно увидеть:

alert( 0.1.toFixed(20) ); // 0.10000000000000000555

Когда мы складываем 0.1 и 0.2, то две неточности складываются, получаем незначительную, но всё же ошибку в вычислениях.

Конечно, это не означает, что точные вычисления для таких чисел невозможны. Они возможны. И даже необходимы.

Например, есть два способа сложить 0.1 и 0.2:

  1. Сделать их целыми, сложить, а потом поделить:

    alert( (0.1 * 10 + 0.2 * 10) / 10 ); // 0.3

    Это работает, т.к. числа 0.1*10 = 1 и 0.2*10 = 2 могут быть точно представлены в двоичной системе.

  2. Сложить, а затем округлить до разумного знака после запятой. Округления до 10-го знака обычно бывает достаточно, чтобы отсечь ошибку вычислений:

    var result = 0.1 + 0.2;
    alert( +result.toFixed(10) ); // 0.3
Забавный пример

Привет! Я – число, растущее само по себе!

alert( 9999999999999999 ); // выведет 10000000000000000

Причина та же – потеря точности.

Из 64 бит, отведённых на число, сами цифры числа занимают до 52 бит, остальные 11 бит хранят позицию десятичной точки и один бит – знак. Так что если 52 бит не хватает на цифры, то при записи пропадут младшие разряды.

Интерпретатор не выдаст ошибку, но в результате получится «не совсем то число», что мы и видим в примере выше. Как говорится: «как смог, так записал».

Ради справедливости заметим, что в точности то же самое происходит в любом другом языке, где используется формат IEEE 754, включая Java, C, PHP, Ruby, Perl.

Другие математические методы

JavaScript предоставляет базовые тригонометрические и некоторые другие функции для работы с числами.

Тригонометрия

Встроенные функции для тригонометрических вычислений:

Math.acos(x)
Возвращает арккосинус x (в радианах)
Math.asin(x)
Возвращает арксинус x (в радианах)
Math.atan(x)
Возвращает арктангенс x (в радианах)
Math.atan2(y, x)
Возвращает угол до точки (y, x). Описание функции: Atan2.
Math.sin(x)
Вычисляет синус x (в радианах)
Math.cos(x)
Вычисляет косинус x (в радианах)
Math.tan(x)
Возвращает тангенс x (в радианах)

Функции общего назначения

Разные полезные функции:

Math.sqrt(x)
Возвращает квадратный корень из x.
Math.log(x)
Возвращает натуральный (по основанию e) логарифм x.
Math.pow(x, exp)
Возводит число в степень, возвращает xexp, например Math.pow(2,3) = 8. Работает в том числе с дробными и отрицательными степенями, например: Math.pow(4, -1/2) = 0.5.
Math.abs(x)
Возвращает абсолютное значение числа
Math.exp(x)
Возвращает ex, где e – основание натуральных логарифмов.
Math.max(a, b, c...)
Возвращает наибольший из списка аргументов
Math.min(a, b, c...)
Возвращает наименьший из списка аргументов
Math.random()
Возвращает псевдо-случайное число в интервале [0,1) – то есть между 0(включительно) и 1(не включая). Генератор случайных чисел инициализуется текущим временем.

Форматирование

Для красивого вывода чисел в стандарте ECMA 402 есть метод toLocaleString():

var number = 123456789;

alert( number.toLocaleString() ); // 123 456 789

Его поддерживают все современные браузеры, кроме IE10- (для которых нужно подключить библиотеку Intl.JS). Он также умеет форматировать валюту и проценты. Более подробно про устройство этого метода можно будет узнать в статье Intl: интернационализация в JavaScript, когда это вам понадобится.

Итого

  • Числа могут быть записаны в шестнадцатиричной, восьмеричной системе, а также «научным» способом.
  • В JavaScript существует числовое значение бесконечность Infinity.
  • Ошибка вычислений дает NaN.
  • Арифметические и математические функции преобразуют строку в точности в число, игнорируя начальные и конечные пробелы.
  • Функции parseInt/parseFloat делают числа из строк, которые начинаются с числа.
  • Есть четыре способа округления: Math.floor, Math.round, Math.ceil и битовый оператор. Для округления до нужного знака используйте +n.toFixed(p) или трюк с умножением и делением на 10p.
  • Дробные числа дают ошибку вычислений. При необходимости ее можно отсечь округлением до нужного знака.
  • Случайные числа от 0 до 1 генерируются с помощью Math.random(), остальные – преобразованием из них.

Существуют и другие математические функции. Вы можете ознакомиться с ними в справочнике в разделах Number и Math.

Задачи

важность: 5
var a = +prompt("Введите первое число", "");
var b = +prompt("Введите второе число", "");

alert( a + b );

Обратите внимание на оператор + перед prompt, он сразу приводит вводимое значение к числу. Если бы его не было, то a и b были бы строками и складывались бы как строки, то есть "1" + "2" = "12".

Создайте страницу, которая предлагает ввести два числа и выводит их сумму.

Запустить демо

P.S. Есть «подводный камень» при работе с типами.

важность: 4

Во внутреннем двоичном представлении 6.35 является бесконечной двоичной дробью. Хранится она с потерей точности… А впрочем, посмотрим сами:

alert( 6.35.toFixed(20) ); // 6.34999999999999964473

Интерпретатор видит число как 6.34..., поэтому и округляет вниз.

В математике принято, что 5 округляется вверх, например:

alert( 1.5.toFixed(0) ); // 2
alert( 1.35.toFixed(1) ); // 1.4

Но почему в примере ниже 6.35 округляется до 6.3?

alert( 6.35.toFixed(1) ); // 6.3
важность: 5

Есть два основных подхода.

  1. Можно хранить сами цены в «копейках» (центах и т.п.). Тогда они всегда будут целые и проблема исчезнет. Но при показе и при обмене данными нужно будет это учитывать и не забывать делить на 100.

  2. При операциях, когда необходимо получить окончательный результат – округлять до 2-го знака после запятой. Все, что дальше – ошибка округления:

    var price1 = 0.1, price2 = 0.2;
    alert( +(price1 + price2).toFixed(2) );

Представьте себе электронный магазин. Цены даны с точностью до копейки(цента, евроцента и т.п.).

Вы пишете интерфейс для него. Основная работа происходит на сервере, но и на клиенте все должно быть хорошо. Сложение цен на купленные товары и умножение их на количество является обычной операцией.

Получится глупо, если при заказе двух товаров с ценами 0.10$ и 0.20$ человек получит общую стоимость 0.30000000000000004$:

alert( 0.1 + 0.2 + '$' );

Что можно сделать, чтобы избежать проблем с ошибками округления?

важность: 4

Потому что i никогда не станет равным 10.

Запустите, чтобы увидеть реальные значения i:

var i = 0;
while (i < 11) {
  i += 0.2;
  if (i > 9.8 && i < 10.2) alert( i );
}

Ни одно из них в точности не равно 10.

Этот цикл – бесконечный. Почему?

var i = 0;
while (i != 10) {
  i += 0.2;
}
важность: 4

Функция

Первая идея может быть такой:

function getDecimal(num) {
  return num - Math.floor(num);
}

alert( getDecimal(12.5) ); // 0.5
alert( getDecimal(-1.2) ); // 0.8, неверно!

Как видно из примера выше, для отрицательных чисел она не работает.

Это потому, что округление Math.floor происходит всегда к ближайшему меньшему целому, то есть Math.floor(-1.2) = -2, а нам бы хотелось убрать целую часть, т.е. получить -1.

Можно попытаться решить проблему так:

function getDecimal(num) {
  return num > 0 ? num - Math.floor(num) : Math.ceil(num) - num;
}

alert( getDecimal(12.5) ); // 0.5
alert( getDecimal(-1.2) ); // 0.19999999999999996, неверно!
alert( getDecimal(1.2) ); // 0.19999999999999996

Проблема с отрицательными числами решена, но результат, увы, не совсем тот.

Внутреннее неточное представление чисел приводит к ошибке в вычислениях, которая проявляется при работе и с положительными и с отрицательными числами.

Давайте попробуем ещё вариант – получим остаток при делении на 1. При таком делении от любого числа в остатке окажется именно дробная часть:

function getDecimal(num) {
  return num > 0 ? (num % 1) : (-num % 1);
}

alert( getDecimal(12.5) ); // 0.5
alert( getDecimal(1.2) ); // 0.19999999999999996, неверно!

В общем-то, работает, функция стала короче, но, увы, ошибка сохранилась.

Что делать?

Увы, операции с десятичными дробями подразумевают некоторую потерю точности.

Зависит от ситуации.

  • Если внешний вид числа неважен и ошибка в вычислениях допустима – она ведь очень мала, то можно оставить как есть.
  • Перейти на промежуточные целочисленные вычисления там, где это возможно.
  • Если мы знаем, что десятичная часть жёстко ограничена, к примеру, может содержать не более 2 знаков то можно округлить число, то есть вернуть +num.toFixed(2).

Если эти варианты не подходят, то можно работать с числом как со строкой:

function getDecimal(num) {
  var str = "" + num;
  var zeroPos = str.indexOf(".");
  if (zeroPos == -1) return 0;
  str = str.slice(zeroPos);
  return +str;
}

alert( getDecimal(12.5) ); // 0.5
alert( getDecimal(1.2) ); // 0.2

Открыть решение с тестами в песочнице.

Напишите функцию getDecimal(num), которая возвращает десятичную часть числа:

alert( getDecimal(12.345) ); // 0.345
alert( getDecimal(1.2) ); // 0.2
alert( getDecimal(-1.2) ); // 0.2

Открыть песочницу с тестами для задачи.

важность: 4
function fibBinet(n) {
  var phi = (1 + Math.sqrt(5)) / 2;
  // используем Math.round для округления до ближайшего целого
  return Math.round(Math.pow(phi, n) / Math.sqrt(5));
}

function fib(n) {
  var a = 1,
    b = 0,
    x;
  for (i = 0; i < n; i++) {
    x = a + b;
    a = b
    b = x;
  }
  return b;
}

alert( fibBinet(2) ); // 1, равно fib(2)
alert( fibBinet(8) ); // 21, равно fib(8)
alert( fibBinet(77) ); // 5527939700884755
alert( fib(77) ); // 5527939700884757, не совпадает!

Результат вычисления F77 получился неверным!

Причина – в ошибках округления, ведь √5 – бесконечная дробь.

Ошибки округления при вычислениях множатся и, в итоге, дают расхождение.

Последовательность чисел Фибоначчи имеет формулу Fn = Fn-1 + Fn-2. То есть, следующее число получается как сумма двух предыдущих.

Первые два числа равны 1, затем 2(1+1), затем 3(1+2), 5(2+3) и так далее: 1, 1, 2, 3, 5, 8, 13, 21....

Код для их вычисления (из задачи Числа Фибоначчи):

function fib(n) {
  var a = 1,
    b = 0,
    x;
  for (i = 0; i < n; i++) {
    x = a + b;
    a = b
    b = x;
  }
  return b;
}

Существует формула Бине, согласно которой Fn равно ближайшему целому для ϕn/√5, где ϕ=(1+√5)/2 – золотое сечение.

Напишите функцию fibBinet(n), которая будет вычислять Fn, используя эту формулу. Проверьте её для значения F77 (должно получиться fibBinet(77) = 5527939700884757).

Одинаковы ли результаты, полученные при помощи кода fib(n) выше и по формуле Бине? Если нет, то почему и какой из них верный?

важность: 2

Сгенерируем значение в диапазоне 0..1 и умножим на max:

var max = 10;

alert( Math.random() * max );

Напишите код для генерации случайного значения в диапазоне от 0 до max, не включая max.

важность: 2

Сгенерируем значение из интервала 0..max-min, а затем сдвинем на min:

var min = 5,
  max = 10;

alert( min + Math.random() * (max - min) );

Напишите код для генерации случайного числа от min до max, не включая max.

важность: 2

Очевидное неверное решение (round)

Самый простой, но неверный способ – это сгенерировать значение в интервале min..max и округлить его Math.round, вот так:

function randomInteger(min, max) {
  var rand = min + Math.random() * (max - min)
  rand = Math.round(rand);
  return rand;
}

alert( randomInteger(1, 3) );

Эта функция работает. Но при этом она некорректна: вероятность получить крайние значения min и max будет в два раза меньше, чем любые другие.

При многократном запуске этого кода вы легко заметите, что 2 выпадает чаще всех.

Это происходит из-за того, что Math.round() получает разнообразные случайные числа из интервала от 1 до 3, но при округлении до ближайшего целого получится, что:

значения из диапазона 1   ... 1.49999..  станут 1
значения из диапазона 1.5 ... 2.49999..  станут 2
значения из диапазона 2.5 ... 2.99999..  станут 3

Отсюда явно видно, что в 1 (как и 3) попадает диапазон значений в два раза меньший, чем в 2. Из-за этого такой перекос.

Верное решение с round

Правильный способ: Math.round(случайное от min-0.5 до max+0.5)

function randomInteger(min, max) {
    var rand = min - 0.5 + Math.random() * (max - min + 1)
    rand = Math.round(rand);
    return rand;
  }

alert( randomInteger(5, 10) );

В этом случае диапазон будет тот же (max-min+1), но учтена механика округления round.

Решение с floor

Альтернативный путь – применить округление Math.floor() к случайному числу от min до max+1.

Например, для генерации целого числа от 1 до 3, создадим вспомогательное случайное значение от 1 до 4 (не включая 4).

Тогда Math.floor() округлит их так:

1 ... 1.999+ станет 1
2 ... 2.999+ станет 2
3 ... 3.999+ станет 3

Все диапазоны одинаковы. Итак, код:

function randomInteger(min, max) {
    var rand = min + Math.random() * (max + 1 - min);
    rand = Math.floor(rand);
    return rand;
  }

alert( randomInteger(5, 10) );

Напишите функцию randomInteger(min, max) для генерации случайного целого числа между min и max, включая min,max как возможные значения.

Любое число из интервала min..max должно иметь одинаковую вероятность.

Карта учебника

Комментарии

перед тем как писать…
  • Приветствуются комментарии, содержащие дополнения и вопросы по статье, и ответы на них.
  • Для одной строки кода используйте тег <code>, для нескольких строк кода — тег <pre>, если больше 10 строк — ссылку на песочницу (plnkr, JSBin, codepen…)
  • Если что-то непонятно в статье — пишите, что именно и с какого места.