JavaRush/Java блог/Java Developer/Факториал в Java-программировании
Автор
Александр Выпирайленко
Java-разработчик в Toshiba Global Commerce Solutions

Факториал в Java-программировании

Статья из группы Java Developer
участников
Сегодня мы поговорим о факториалах. Это одна из самых базовых функций, которые программисту нужно знать, а заодно — уметь с этим работать. Итак, приступим. Факториал числа n — это значение произведения (умножения) всех натуральных чисел от 1 до n, которое обозначается как n! Как это выглядит:
1! =  1
2! =  1 * 2 = 2
3! =  1 * 2 * 3 = 6
4! =  1 * 2 * 3 * 4 = 24
5! =  1 * 2 * 3 * 4 * 5  = 120
И ещё одно небольшое правило, для 0:
!0  = 1
Если мы хотим, к примеру, получить разницу факториалов 6! и 4!:
6!-4! = 1⋅2⋅3⋅4⋅5⋅6 - 1⋅2⋅3⋅4 = 720 - 24 = 696
Давайте взглянем, как это будет выглядеть в Java и разберемся с несколькими способами того, как можно вычислить факториал.

Обычное решение

public static int getFactorial(int f) {
  int result = 1;
  for (int i = 1; i <= f; i++) {
     result = result * i;
  }
  return result;
}
Ничего сложного: используем пришедшее число как размер нашего цикла, в котором умножаем на все предыдущие числа, пока не дойдём до f. И в main:

System.out.println(getFactorial(6) - getFactorial(4));
Проверяем и получаем искомый результат: 696.

Рекурсивное решение

Рекурсия — это выполнения некоторой логики путем вызова методом самого себя. Такой метод называют рекурсивным. Как правило он состоит из двух частей:
  1. условие выхода из метода, при достижении которого метод должен перестать вызывать самого себя и начать отдавать значения наверх. Ведь иначе мы получим бесконечный цикл из вызова методом самого себя и как следствие — StackOverflowError.

  2. некоторая обработка (логика), необходимая в данной ситуации + вызов самого себя, но уже с другим входящим значением.

Идеальным примером для рекурсии у нас сегодня и послужит нахождение факториала:
public static int getFactorial(int f) {
  if (f <= 1) {
     return 1;
  }
  else {
     return f * getFactorial(f - 1);
  }
}
Задаем условие выхода из рекурсии при достижении 1. Если же аргумент не 1, то умножаем текущее значение на результат выполнения следующего захождения в данный метод (куда мы посылаем текущее значение -1).

Решение со Stream

Для не знающих stream функционал или для желающих освежить его в памяти, будет полезно прочитать вот это.
public static int getFactorial(int f) {
  if (f <= 1) {
     return 1;
  }
  else {
     return IntStream.rangeClosed(2, f).reduce((x, y) -> x * y).getAsInt();
  }
}
Тут мы используем специальный класс IntStream , дающий дополнительные возможности при работе со stream из int значений. Для создания такого stream мы используем его статический метод rangeClosed, который создаёт значения с 2 до f включительно с шагом 1. Далее мы объединяем все значения с помощью метода reduce, а именно — показываем в нем, как мы хотим их объединить. Наконец, мы получаем результирующее значение с помощью терминального метода getAsInt.

Применение BigInteger

В Java часто для обработки чисел, особенно БОЛЬШИХ, используется класс BigInteger. Ведь если мы используем int, то максимальный факториал, который мы можем взять без потери данных, — 31, для long — 39. А что если нам нужен будет факториал 100? Давайте же рассмотрим предыдущие варианты решения, но с использованием BigInteger. Факториал в Java-программировании - 2

Обычное решение

public static BigInteger getFactorial(int f) {
  BigInteger result = BigInteger.ONE;
  for (int i = 1; i <= f; i++)
     result = result.multiply(BigInteger.valueOf(i));
  return result;
}
Алгоритм подсчёта по сути тот же, но здесь мы используем возможности BigInteger: BigInteger.ONE — для задания стартового значения 1. multiply — умножение между предыдущим значением факториала и текущим числом.

Рекурсивное решение

public static BigInteger getFactorial(int f) {
  if (f <= 1) {
     return BigInteger.valueOf(1);
  }
  else {
     return BigInteger.valueOf(f).multiply(getFactorial(f - 1));
  }
}
Общая логика решения не изменяется, разве что добавляются некоторые методы для работы с BigInteger.

Решение cо Stream

public static BigInteger getFactorial(int f) {
  if (f < 2) {
     return BigInteger.valueOf(1);
  }
  else {
     return IntStream.rangeClosed(2, f).mapToObj(BigInteger::valueOf).reduce(BigInteger::multiply).get();
  }
}
По сути всё то же самое, но с BigInteger. В stream у нас появился метод mapToObj, которым мы преобразуем значения int в BigInteger, чтобы в дальнейшем перемножить их между собой с помощью multiply (ну и добавился get для взятия объекта из обёртки Optional). Если же мы запустим любой из этих трёх методов с аргументом 100, то у нас не случится переполнения и мы получим:

93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000
Комментарии (26)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
Сергей Продавец в магазине в ВсеИнструменты Expert
23 апреля 2023, 08:19
Спасибо, Алекс.
ESTR
Уровень 2
26 марта 2023, 20:20
И ещё одно небольшое правило, для 0: !0 = 1 Вот тут поправьте, явно опечатка
Skotique
Уровень 35
26 июля 2023, 06:10
кекнул с этого эксперта
Егор Щигреев
Уровень 1
1 февраля 2023, 09:11
Раздел BigInteger, подраздел Обычное Решение
public static BigInteger getFactorial(int f) {
  BigInteger result = BigInteger.ONE;
  for (int i = 1; i <= f; i++)
     result = result.multiply(BigInteger.valueOf(i));
  return result;
}
Разве здесь в 4-й строке метод valueOf(); не принимает long? Но i у нас имеет тип int, мы не можем его туда засунуть Пример взят один, но такое во всем разделе. Либо это я что-то путаю, проверьте меня
Виктор
Уровень 40
16 марта 2023, 18:01
происходит расширение типа с int до long. Поэтому все работает. Вот если наоборот, в методе был бы int, а в цикле long, то тогда бы код не сработал.
24 декабря 2022, 18:42
А что за тетя на картинке, грустная такая, почему? На Лару Фабиан похожа, но постаревшую и уставшую.
Сергей Student в EPAM
14 января 2022, 07:50
Здравствуйте! "В Java часто для обработки чисел, особенно БОЛЬШИХ, используется класс BigInteger. Ведь если мы используем int, то максимальный факториал, который мы можем взять без потери данных, — 31, для long — 39. А что если нам нужен будет факториал 100?" Ведь максимальный факториал при int это 12! а long: 20!. Что за 31 и 39?) Решаю сейчас задачу и прочитав статью использовал значение 31. Изрядно намучался не понимая что не так)
Иван Петров
Уровень 0
5 января 2022, 03:12
public class Factorial {

    public static void main(String[] args) throws IOException {
        String s = "543219"; // Любое число в виде строки. Здесь можно args[0], например
        Files.write(
                Paths.get("factorial of " + s),
                Factorial(s).toString().getBytes()); // Вывод результата в файл
    }

    public static BigInteger Factorial(String targetNumberString) {
        BigInteger target = new BigInteger(targetNumberString).abs();
        System.out.printf("target: %s", target);

        if(!target.equals(BigInteger.ZERO)) {
            BigInteger factorial = target;
            while (!target.equals(BigInteger.ONE)) {
                target = target.subtract(BigInteger.ONE);
                System.out.printf("\nnext factor: %s", target);
                factorial = factorial.multiply(target);
            }
            return factorial;
        }
        return BigInteger.ONE;
    }
}
Lexus12321
Уровень 23
11 ноября 2021, 07:29
в int - 4 байта в long - 8 вроде а в BigInteger сколько байт? Или там может быть сколько угодно?
5 июня 2023, 13:59
В Java класс BigInteger представляет произвольно большие целые числа. Он не имеет фиксированного размера, как типы данных int и long. Вместо этого, BigInteger может содержать целые числа любого размера, которые ограничены только доступным объемом памяти. BigInteger использует динамическое выделение памяти для хранения чисел и автоматически масштабирует свой размер в зависимости от значения числа. Таким образом, размер BigInteger может изменяться в зависимости от числа, которое он представляет. Поскольку BigInteger не имеет фиксированного размера, его объем памяти зависит от конкретного числа, которое он хранит. При создании BigInteger память выделяется динамически в зависимости от числового значения, поэтому объем памяти, занимаемый BigInteger, может быть значительно больше, чем размеры int и long.
Людмила
Уровень 51
7 мая 2021, 08:59
Вот не понимаю почему во всех примерах что встречала начинают в цикле с 1 , а нес 2. Конечно это мелочь, результат получается тот же, но ведь 1*2*3*4, это не 1*1*2*3*4. int result = 1; for (int i = 1; i <= f; i++) { result = result * i; _________________________ int a = 1; for (int i = 2; i <= n; i++) { a = a * i;
Roman Merkulov
Уровень 22
6 августа 2021, 14:04
А если в качестве параметра f вам в метод залетит ноль? Тогда просто условие не будет соблюдено и цикл не запустится. Поэтому если начнете с 2, то придется добавлять перед циклом проверку на ноль.
fedyaka
Уровень 36
7 ноября 2021, 17:05
ну вообще если тебе залетит 0, то тебе его в любом случае нужно будет выловить до этого, ибо факториал 0 это никак не 0, а единица.
5 июня 2023, 14:04
Когда параметр f равен 0, то в обоих вариантах цикл не будет выполнен, так как условие i <= f не будет соблюдено. Результат возвращается как 1, потому что факториал 0 по определению равен 1.
Андрей Чубов
Уровень 41
19 апреля 2021, 08:50
result = result.multiply(BigInteger.valueOf(i)); Почему в этом коде result изменяется раз класс BigInteger immutable?
Константин
Уровень 40
31 мая 2021, 22:12
String result = "result"; result = result + " new"; Как думаете строка result в данном примере тоже изменяется? Или же каждый раз создается новая? На самом деле происходит примерно такое: String result = new String("result"); result = new String (result + " new"); То есть каждый раз мы нашей ссылке result присваиваем новый объект. В том и дело, что он не изменяется. Так же и с BigInteger. Ссылка остается, а объект меняется.
Андрей Чубов
Уровень 41
1 июня 2021, 09:05
Спасибо
Mikhail Java Developer в Akron Holding
25 августа 2023, 07:11
Получается, что старые объекты, на которых потом нет ссылок, удаляются GC?
Александр Плохой Senior Java Developer в freelance
25 июня 2020, 13:23
получение на вход отрицательных чисел не учтено, кэша нет(будет все время долго считаться) - так себе решение
Константин Backend Developer в Andersen
26 июня 2020, 08:25
Кеш? В теме для новичков? Серьёзно?
Justinian Judge в Mega City One Master
26 июня 2020, 21:19
Можно в конце статьи было сделать отдельным разделом для тех, кто например собесы проходит, обозначить звездочкой, мол, это не для совсем новичков. Все-таки название статьи не "Факториал - что такое, как к нему подступиться", а "факториал в программировании", а программирование новичками не ограничивается. На собесах про кеширование в подобных алгоритмах на раз два спрашивают. Да и в целом было бы полезно для понимания что бывает, на сайте много свитчеров, лучше пусть подобные моменты узнают здесь, чем на собесе. Но с другой стороны, автор статьи сам определяет ЦА и уровень подачи материала. Жираф большой, ему видней (с)