C++でpow関数を使わないで累乗の計算をする方法

AI実装検定のご案内

C++では、標準ライブラリ <cmath> にある std::pow() を使わなくても、プログラム内で累乗を計算できます。

特に 整数の累乗を扱う場合は、自分で実装する方法がよく使われます。

主な理由は次の通りです。

  • std::pow() は主に 浮動小数点計算を前提とした関数
  • 戻り値が double 系になることが多い
  • 大きな整数計算では 丸め誤差の影響を受ける可能性がある
  • 整数累乗だけなら より高速なアルゴリズムを実装できる

ここでは、C++で pow() を使わずに累乗を求める代表的な方法を、基本から高速アルゴリズムまで順に解説します。

目次

ループで掛け算を繰り返す方法(基本)

最も単純な方法は、掛け算を繰り返すことです。

数学的に

2^5 = 2 × 2 × 2 × 2 × 2

なので、これをそのままプログラムにします。

#include <iostream>
using namespace std;

long long power(int base, int exponent)
{
    long long result = 1;

    for(int i = 0; i < exponent; i++)
    {
        result *= base;
    }

    return result;
}

int main()
{
    cout << power(2,5) << endl; // 32
}

特徴

この方法は次の条件で使用できます。

  • 底(base)が整数
  • 指数(exponent)が 0以上の整数

計算量は

O(n)

です。

つまり、指数が大きくなるほど計算回数が増えます。

2^100000

のような計算では、100000回の掛け算が必要になります。

再帰関数で累乗を計算する方法

ループの代わりに、再帰関数を使って累乗を定義することもできます。

累乗は次の関係式で表せます。

a^n = a × a^(n-1)

これをそのままコードにすると次のようになります。

#include <iostream>
using namespace std;

long long power(int base, int exponent)
{
    if(exponent == 0)
        return 1;

    return base * power(base, exponent - 1);
}

int main()
{
    cout << power(2,5) << endl;
}

特徴

計算量はループと同じです。

O(n)

ただし再帰関数は

  • 関数呼び出しのオーバーヘッド
  • 深い再帰によるスタック消費

があるため、大きな指数ではあまり実用的ではありません。

二分累乗法(Binary Exponentiation)

指数計算を高速化する代表的なアルゴリズムが 二分累乗法です。

これは 指数を半分に分割して計算する方法です。

累乗には次の性質があります。

指数が偶数の場合

a^n = (a^(n/2))^2

指数が奇数の場合

a^n = a × a^(n-1)

この性質を利用すると、計算回数を大幅に減らすことができます。

再帰版の実装

#include <iostream>
using namespace std;

long long power(long long base, long long exponent)
{
    if(exponent == 0)
        return 1;

    long long half = power(base, exponent / 2);

    if(exponent % 2 == 0)
        return half * half;
    else
        return base * half * half;
}

int main()
{
    cout << power(2,10) << endl;
}

計算量

二分累乗法では計算量が

O(log n)

になります。

つまり、指数が100万でも必要な掛け算回数は 約20回程度です。

ただし、結果の値が long long に収まるとは限らないため、オーバーフローには注意が必要です。

ループ版の二分累乗(実用的)

実務や競技プログラミングでは、再帰よりも ループ版がよく使われます。

理由は次の通りです。

  • スタックを使わない
  • 実装が簡潔
  • 実行速度が安定する
#include <iostream>
using namespace std;

long long power(long long base, long long exponent)
{
    long long result = 1;

    while(exponent > 0)
    {
        if(exponent % 2 == 1)
        {
            result *= base;
        }

        base *= base;
        exponent /= 2;
    }

    return result;
}

int main()
{
    cout << power(2,10) << endl;
}

ビット演算を使った高速累乗

二分累乗法は、ビット演算を使うとさらに簡潔に書けます。

long long power(long long base, long long exponent)
{
    long long result = 1;

    while(exponent)
    {
        if(exponent & 1)
            result *= base;

        base *= base;
        exponent >>= 1;
    }

    return result;
}

ビット演算の意味

exponent & 1

→ 指数が奇数か判定

exponent >>= 1

→ 指数を2で割る

テンプレートで汎用化する方法

型を限定せずに使えるようにするため、テンプレート関数にすることもできます。

template<typename T>
T power(T base, long long exponent)
{
    T result = static_cast<T>(1);

    while(exponent > 0)
    {
        if(exponent & 1)
            result *= base;

        base *= base;
        exponent >>= 1;
    }

    return result;
}

この関数は次のような型で使用できます。

int
long long
double
float

ただし次の条件があります。

  • 乗算が定義されている型
  • 1 が乗算の単位元になる型
  • 指数は 0以上の整数

pow() を使わない理由

C++では通常

std::pow(2,10)

のように書くことができます。

しかし整数累乗では、自前実装が使われることも多いです。

主な理由は次の通りです。

浮動小数点計算になる

std::pow() は浮動小数点演算を行うため、戻り値は多くの場合

double

になります。

そのため大きな整数では

丸め誤差

が発生する可能性があります。

整数計算として扱いにくい

整数の累乗を求めたい場合、

long long result = pow(2,10);

のようなコードは

  • 型変換
  • 誤差

などの問題を起こす可能性があります。

自前の二分累乗の方が高速になることがある

二分累乗法を使えば、整数の累乗を

O(log n)

で計算できます。

整数専用の処理であるため、用途によっては std::pow() より効率的になる場合があります。

実装時の注意点

累乗関数を実装するときには、次の点に注意する必要があります。

オーバーフロー

例えば

2^63

long long に収まりません。

整数型では、範囲を超えると未定義動作になる可能性があります。

負の指数

整数型では

2^-3 = 1/8

のような値を表現できません。

負の指数を扱う場合は

double

などの型を使う必要があります。

0^0 の扱い

多くのプログラムでは

0^0 = 1

として扱われますが、数学的には文脈によって定義が異なる場合があります。

まとめ

C++で pow() を使わずに累乗を計算する方法には次のようなものがあります。

方法計算量特徴
ループO(n)実装が簡単
再帰O(n)数学的に理解しやすい
二分累乗(再帰)O(log n)高速
二分累乗(ループ)O(log n)実用的
ビット演算版O(log n)最も一般的

整数の累乗計算では、特に

二分累乗法(Binary Exponentiation)

が広く使われています。

以上、C++でpow関数を使わないで累乗の計算をする方法についてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次