整数版powについて

C言語、あるいはC++言語において累乗を計算する場合powを使う。幾つか亜種があるが標準で用意されているのは実数版のみだ。
最も一般的なpowの定義は以下の通り

  double pow( double x, double expr );

整数版は用意されていないので、累乗が欲しいときには実数にキャストして、整数に戻す必要がある。
あまり気にすることもないかもしれないが、値によってはキャストで正しい値でなくなる場合もあるので注意が必要だ。

パフォーマンスに関する疑問もあるので、今回整数版のpowを自作し、計算時間の比較をした。
コンパイル環境はC++ Builder XE 10.2.3で、 32bit版はbcc32、64bit版はbcc64(CLang版)を使った。
計算時間はそれぞれ1億回のミリ秒だ。
また、整数はintと__int64の両方で検討した。

整数版の累乗関数は、単純なかけ算置き換え(かけ算)と、2の累乗で計算をまとめる方法(かけ算2)の2種類を試した。
かけ算は計算量がO(n)、かけ算2は計算量がO(logn)だ。

かけ算のルーチンは以下の通り

int pow( int n, int exp )
{
  int a;

  if( exp <  0 ) return 0;

  a = 1;
  for( ; exp >= 1; --exp )
  {
    a *= n;
  }

  return a;
}

かけ算2のルーチンは以下の通り。

int pow( int n, int exp )
{
  int a;

  if( exp <  0 ) return 0;

  a = 1;
  for( ; exp >= 1; exp >>= 1 )
  {
    if( exp & 1 )
      a *= n;
    n *= n;
  }

  return a;
}

累乗(x^expr)の基数xを13として計算時間を見る。 オリジナルのpowはキャストの時間を入れないために値をdoubleに変換してから時間を計測している。
64bit版のpowはなぜか異常に遅いので、試行回数を10万回に減らして100倍した値を掲載している。

大体5~6乗まではかけ算が最も早い場合が多いという結果になった。7乗以上だとかけ算2の方が早い。
かけ算2はどの場合でも概ねコンスタントに早い結果となった。
32bit版で__int64を用いる場合は、20乗以上ではオリジナルのpowが最も早いという結果になった。
64bit版(CLang版)のpowは非常に遅く、13^expではexp>4でさらに遅くなっている。ここに結果は掲載しないが累乗の基数を替えると遅くなる乗数は変わるようだ。

実際上はintは32ビット、__int64ビットは64ビット幅しかないので、2の累乗でも31乗及び63乗より大きくなればオーバーフローになるため、100乗などと言う高い乗数には意味がない。
intであれば実用上は10~20乗、__int64で40乗位がせいぜいではないだろうか。
また、今回の自作ルーチンにはオーバーフローのチェック機構がないので、汎用化ではその辺を組み込む必要がある。

このほか、0の0乗の扱いをどうするかという問題があり、解析的に1とするか(今回のルーチンはそうなっている)、それとも0にするかが問題となる。他の言語では1とする場合が多いようだ。Excelではpower(0,0)はNUM!エラーが発生する。

f:id:konishih:20201017162504j:plain
32bit int
f:id:konishih:20201017162519j:plain
32bit __int64
f:id:konishih:20201017162533j:plain
64bit int
f:id:konishih:20201017162546j:plain
64bit __int64

以下計算結果一覧表

表. 32bit int

乗数 pow かけ算 かけ算2
0 656 219 203
1 2079 203 203
2 2141 234 328
3 2156 250 281
4 2157 328 390
5 2188 328 406
6 2219 344 437
7 2265 360 375
8 2188 375 468
9 2265 407 485
10 2250 437 563
15 2500 562 391
20 2390 750 625
25 2500 969 594
30 2625 1172 562
35 2687 1391 672
40 2516 1703 656
45 2765 2063 672
50 2625 2375 735
75 2891 3843 813
100 2765 5250 828

表. 32bit __int64

乗数 pow かけ算 かけ算2
0 609 266 250
1 2094 500 719
2 2125 765 1094
3 2156 1078 1313
4 2172 1359 1453
5 2188 1656 1656
6 2218 1985 1703
7 2234 2266 1875
8 2203 2563 1781
9 2250 2891 2016
10 2250 3218 2016
15 2516 4937 2500
20 2391 6531 2391
25 2516 8218 2609
30 2625 9938 2828
35 2656 12047 2969
40 2500 13890 2735
45 2781 15578 3172
50 2625 17359 2969
75 2922 26297 3484
100 2781 35688 3344

表. 64bit int

乗数 pow かけ算 かけ算2
0 1600 94 47
1 7800 125 109
2 7800 172 125
3 7800 203 141
4 7800 281 250
5 248500 297 187
6 239100 297 171
7 245300 329 172
8 231200 359 391
9 243700 391 297
10 246800 438 282
15 253100 656 203
20 246800 860 344
25 243700 1063 328
30 251500 1235 281
35 248500 2422 453
40 250000 2656 516
45 253100 2125 422
50 243700 2297 469
75 248400 4891 422
100 243800 6265 500


表. 64bit __int64

乗数 pow かけ算 かけ算2
0 93 63
1 7800 125 109
2 7800 157 140
3 7800 188 141
4 7800 281 266
5 248400 297 203
6 239100 281 172
7 246900 312 172
8 231300 359 390
9 243800 391 296
10 248400 454 281
15 253100 672 203
20 246900 875 344
25 243800 1062 344
30 250000 1234 266
35 248400 2422 453
40 248500 2656 500
45 251600 2031 437
50 243700 2297 469
75 245300 4938 437
100 242200 6219 500