Как возвести число в степень в Java

Как возвести число в степень в Java Полезное

Возведение в степень — это умножение числа (основания) на само себя заданное количество раз (показатель степени). В Java нет отдельного оператора для этого (в отличие, например, от Python с **), поэтому используют один из нескольких инструментов: метод Math.pow, обычный цикл, класс BigInteger или побитовый сдвиг. Разберем каждый способ, их границы применимости и типичную ошибку с приведением типов.

Math.pow: базовый способ

Math.pow(double base, double exponent) — статический метод класса Math, который умеет возводить в любую вещественную степень, включая дробную и отрицательную. Вызывать его можно сразу через имя класса, без создания объекта.

public class PowExample {
    public static void main(String[] args) {
        System.out.println(Math.pow(2, 10));
        System.out.println(Math.pow(2, -1));
    }
}

Вывод:

1024.0
0.5

Метод всегда возвращает double, даже если на вход пришли целые числа. Если нужен именно целый результат, приходится приводить тип явно:

public static int powInt(int base, int exp) {
    return (int) Math.pow(base, exp);
}

Для небольших значений (вроде powInt(2, 10)) это работает корректно и возвращает 1024. Но у такого приведения есть две отдельные ловушки: переполнение диапазона int и потеря точности самого double на больших числах. Разберем обе на реальных числах.

Ловушка 1: переполнение int при приведении из double

Возьмем степень покрупнее — 3 в 30-й степени. Точное значение (можно проверить в столбик или через BigInteger) — 205891132094649. Это уже больше 9 миллиардов, то есть заведомо не влезает в int (диапазон int — от -2147483648 до 2147483647).

Неверный код:

public static int powInt(int base, int exp) {
    return (int) Math.pow(base, exp);
}

public static void main(String[] args) {
    System.out.println(powInt(3, 30));
}

Фактический результат:

2147483647

Это не переполнение в привычном смысле (когда число «перескакивает» через границу диапазона и продолжает расти с другого края, как бывает при арифметике int + int). Здесь другое правило: при сужающем преобразовании double -> int по спецификации Java (JLS, п. 5.1.3), если вещественное значение выходит за границы int, результатом становится Integer.MAX_VALUE для слишком больших положительных чисел или Integer.MIN_VALUE для слишком больших по модулю отрицательных — без исключения и без предупреждения.

Исправление — использовать более широкий тип и не полагаться на int, если показатель степени заранее не ограничен:

public static long powLong(int base, int exp) {
    return (long) Math.pow(base, exp);
}

Для 3 в 30-й степени powLong(3, 30) уже вернет верные 205891132094649 — значение помещается в long (диапазон которого — до 9223372036854775807) и, как показано ниже, здесь double еще способен хранить его точно.

Ловушка 2: потеря точности double на больших целых числах

Тип double хранит 52 бита мантиссы, поэтому точно (без пропусков) представляет подряд идущие целые числа только до 2^53, то есть до 9007199254740992. Число 205891132094649 из примера выше меньше этой границы, поэтому проблем не было. Но если взять 7 в 20-й степени, ситуация меняется.

Точное значение 7^20 (по определению степени, вычисляется без floating-point) — 79792266297612001. Оно все еще помещается в long, но уже больше границы 2^53, поэтому Math.pow не гарантирует точный результат:

public static void main(String[] args) {
    long result = (long) Math.pow(7, 20);
    System.out.println(result);
}

Фактический вывод:

79792266297612000

Обратите внимание на последнюю цифру: вместо 79792266297612001 получаем 79792266297612000 — расхождение на единицу. Это не баг конкретной реализации, а следствие того, что double физически не может хранить каждое целое число за пределами 2^53 с абсолютной точностью, а Math.pow (как и большинство реализаций pow в других языках) не обязана давать результат, округленный до последнего бита правильно, для любых входных данных.

Практический вывод: Math.pow годится для дробных степеней и для целых чисел, где итог заведомо меньше 2^53. Если нужна гарантированная точность на больших целых числах — берите ручной цикл на целых типах или BigInteger, где нет плавающей точки вообще.

Ручной цикл: точный результат для целых чисел

Цикл умножения не использует double и поэтому не теряет точность, пока итоговое значение помещается в выбранный целочисленный тип:

public static long powLoop(long base, int exp) {
    long result = 1;
    for (int i = 0; i < exp; i++) {
        result *= base;
    }
    return result;
}

public static void main(String[] args) {
    System.out.println(powLoop(7, 20));
}

Вывод:

79792266297612001

Здесь число совпадает с точным значением — никакой погрешности, потому что вся арифметика целочисленная. Обратная сторона — при отрицательном основании и целочисленном делении такой цикл не умеет считать дробные и отрицательные степени, для этого все равно нужен Math.pow или отдельная ветка кода.

Для больших показателей есть более быстрый вариант того же принципа — возведение в степень удвоением (aka быстрое/бинарное возведение в степень), которое считает результат за O(log n) умножений вместо O(n):

public static long fastPow(long base, int exp) {
    if (exp == 0) {
        return 1;
    }
    long half = fastPow(base, exp / 2);
    if (exp % 2 == 0) {
        return half * half;
    } else {
        return half * half * base;
    }
}

fastPow(2, 10) дает 1024 всего за 4 рекурсивных вызова вместо 10 итераций обычного цикла — разница малозаметна на маленьких числах, но существенна, когда показатель степени исчисляется тысячами (например, в модульной арифметике для криптографии, где обычно считают fastPow по модулю на каждом шаге).

BigInteger.pow: когда результат не помещается в long

long тоже не бесконечен — его максимум 9223372036854775807. Если показатель степени большой, а тип BigInteger работает с числами произвольной длины (ограниченными только памятью), возьмем 3 в 40-й степени, точное значение которой — 12157665459056928801. Это уже больше Long.MAX_VALUE, поэтому ни long-цикл, ни Math.pow с приведением к long не дадут верного ответа.

import java.math.BigInteger;

public class BigPowExample {
    public static void main(String[] args) {
        BigInteger base = BigInteger.valueOf(3);
        BigInteger result = base.pow(40);
        System.out.println(result);
    }
}

Вывод:

12157665459056928801

Значение точное — BigInteger хранит число как массив цифр произвольной длины, а не как биты фиксированного размера, поэтому границ int/long для него не существует. Метод pow принимает показатель степени только типом int (отрицательные показатели он не принимает — для дробных степеней BigInteger не подходит вовсе, там нужен Math.pow или BigDecimal с приближенными алгоритмами).

Побитовый сдвиг: самый быстрый способ для степеней двойки

Если основание — именно число 2, возведение в степень сводится к сдвигу бит влево: 1 << n эквивалентно 2^n. Это самая быстрая из всех операций — один такт процессора, без циклов и без вызовов методов.

public static void main(String[] args) {
    System.out.println(1 << 10);
    System.out.println(Math.pow(2, 10) == (1 << 10));
}

Вывод:

1024
true

У способа есть строгая граница применимости — он работает только для основания 2 и только в пределах ширины типа. Для int (32 бита, один бит уходит под знак) безопасный диапазон показателя степени — от 0 до 30: 1 << 31 дает не 2147483648, а -2147483648 (Integer.MIN_VALUE), потому что 31-й бит — знаковый. Для long (64 бита) граница сдвигается до 62, а 1L << 63 аналогично дает Long.MIN_VALUE. Кроме того, оператор сдвига в Java берет только младшие биты правого операнда (5 бит для int, 6 бит для long), поэтому 1 << 32 в Java равно не 0, а 1 << 0, то есть 1 — неочевидное поведение, если ожидать сдвиг «на ноль позиций эффективно занулит число».

Какой способ выбрать

Способ Тип результата Точность на больших числах Когда использовать
Math.pow double теряется за пределами 2^53 (9007199254740992) дробная или отрицательная степень, точность не критична
Ручной цикл long точна, пока итог <= Long.MAX_VALUE целая степень, важна точность, без внешних классов
BigInteger.pow BigInteger точна всегда (ограничена только памятью) результат не помещается в long: факториалы, криптография
Побитовый сдвиг << int/long точна в пределах ширины типа только основание 2, максимальная скорость

Выводы

  • Math.pow возвращает double и подходит для дробных/отрицательных степеней; для целых чисел годится, только если результат заведомо меньше 2^53.
  • Приведение (int) Math.pow(...) при выходе за границы int не бросает исключение, а насыщается до Integer.MAX_VALUE/MIN_VALUE — это ловушка, а не переполнение в привычном смысле.
  • Ручной цикл на long дает точный целочисленный результат, пока итог помещается в long; для больших показателей есть более быстрый вариант — возведение удвоением за O(log n).
  • BigInteger.pow — единственный точный способ, когда результат превышает Long.MAX_VALUE.
  • Побитовый сдвиг << — самый быстрый вариант, но работает только для основания 2 и в пределах ширины типа (до 30 для int, до 62 для long).

Где применяется / связь с практикой

Освойте тему на практике

Работа с числовыми типами, границами точности и приведением типов — база, которая нужна в любой Java-задаче: от школьных алгоритмов до продакшен-кода, где ошибка вроде тихого насыщения int при приведении из double может неделями жить в коде и портить отчеты или расчеты. На курсе Java Basic разбирают типы данных, работу с числами и типичные ошибки приведения на практических задачах с разбором кода. Если хочется сначала посмотреть на формат занятий — можно записаться на открытые уроки Otus.

FAQ

Есть ли в Java оператор возведения в степень, как ** в Python?
Нет, в Java такого оператора нет ни для int, ни для double. Единственный встроенный инструмент — статический метод Math.pow, остальные способы (цикл, BigInteger, сдвиг) пишутся вручную или берутся из стандартной библиотеки классов.

Можно ли возвести число в отрицательную степень?
Да, но только через Math.pow — он возвращает дробный double (например, Math.pow(2, -1) дает 0.5). Ручной цикл на целых типах и BigInteger.pow из примеров выше рассчитаны только на неотрицательные целые показатели степени.

Что вернет возведение числа в нулевую степень?
Math.pow(x, 0) возвращает 1.0 для любого x, кроме случая с NaN в основании. Ручной цикл из примера выше тоже корректно вернет 1, потому что просто не войдет в тело цикла ни разу и оставит стартовое значение result = 1.

OTUS Журнал
Скидка 5% 12-19 октября на курсы (popup)
Бесплатные открытые уроки (поп-ап)