web-dev-qa-db-ja.com

桁数を数える-どの方法が最も効率的ですか?

与えられた数の桁数を見つけるための解決策は複数あります。

例えば:

方法1:

_int findn(int num)
{
    char snum[100];
    sprintf(snum, "%d", num);
    return strlen(snum);
}
_

方法2:

_int findn(int num)
{
    if (num == 0) return 1;
    int n = 0;
    while(num) {
        num /= 10;
        n++;
    }
    return n;
}
_

方法3:

_int findn(int num)
{
    /* math.h included */
    return (int) log10(num) + 1;
}
_

問題は-最も効率的な方法は何ですか? method-2がO(n)であることは知っていますが、method-1とmethod-3はどうですか?ライブラリ関数の実行時の複雑さを見つけるにはどうすればよいですか?

22

以下はさらに効率的です。

int findn(int num)
{
   if ( num < 10 )
      return 1;
   if ( num < 100 )
      return 2;
   //continue until max int
}

バイナリ検索を行うことでこれをさらに最適化できますが、それはやり過ぎです。

22
Luchian Grigore

現在のところ、受け入れられて最も承認されている回答は、負の数値に対して( still )が正しくありません。回答者が時間をかけてテストした場合そして、負の数では壊れていることを知ると、単にsnprintfを使用するだけで、マシンがこれまでよりも多くの時間を無駄にした可能性があります。

int count_digits(int arg) {
    return snprintf(NULL, 0, "%d", arg) - (arg < 0);
}

1980年代にはもういません。私たちのようにコーディングを停止します。私はC標準の熱狂者であり、ここでの私のお気に入りの答えは Tao Fengの答え ...でしたが、それでも why にはなりませんでしたこれまでのところ最も効率的な答え。この回答では、私は彼の回答が以下を検討することでさらに改善できることを示すつもりです:

  • プログラマーの生産性はコードの効率よりも重要です数マイクロ秒のランタイムよりも、新しい関数を適切に記述してテストするのにほぼ確実に時間がかかるためです。
  • 他のプログラムが一般的に使用しているものと同じ標準ライブラリ関数を(おそらく)再利用すると、それらの標準ライブラリがCPUキャッシュに保持されます。キャッシュミス(たとえば、コードをRAM(CPUに)は、最大50のCPU命令のコストがかかる可能性があります。言うまでもなく、他のコードは、別のキャッシュミスによりsnprintfをキャッシュに戻す可能性があります。
  • ストレージ要件を排除すると、余分な最適化が明らかになる可能性があります。

以下は、生産性を妨げるマイクロ最適化について説明しています。回答で提供した情報が不足しているため、現在のところ質問に答える人は、仮定を立てずに証拠を提供できません。 :

  • 最適化するときは、完全なソリューション(プログラムが解決するように設計されている問題)で最も重大なボトルネックを見つける必要があります。ここでは2つの可能性があります。A)これらの数字を含む文字列を格納するために割り当てるバイト数を計算したい。 B)キックの桁数などを数えたいだけです。これらについては後で詳しく説明します。今のところ、おそらくソリューションの一部について話していることを理解することが重要ですその部分は最も重要なボトルネックではないかもしれません
  • 使用しているコンパイラ、使用しているOS、および使用しているマシン(RAM速度を含む)。低速メモリの影響をより多く受ける可能性のある潜在的なキャッシュミスが発生しているため一部のコンパイラは他のコンパイラとは異なり、一部のコードを一部のOS、CPUなどに対して他よりも最適化します。

ボトルネックを測定することで、つまりこれらの各ソリューションのプロファイリング( "benchmarking" )をプロファイリングすることで、マイクロ最適化を回避できますon your system正しく。解決策が問題を解決しない場合、それは解決策ではないので、考慮されるべきではありません...正しく行われると、これはミクロ最適化を排除するはずです。一部のコンパイラは、インテリジェントなprofile-guided optimisationを提供しています。これは、一般に、ブランチとオブジェクトをキャッシュの局所性のために再編成することで20〜30%を削減しますそして自動的に行います

数字のカウントについてはすでに説明しましたが、間違いなくあなたの質問に答えると思いますが、考えるドンするときに数字をカウントする必要がある場合があります't 、および数字のカウントのオーバーヘッドを削除する機能は、人手時間およびのマシン時間の両方で、非常に望ましい最適化をもたらす可能性があります。

たとえば、これらの数字を含む文字列を格納するために割り当てるバイト数を計算する場合は、プリプロセッサマクロを使用して最大桁数(または文字、記号)、および保存しようとする一時的なストレージの貴重なバイトは、ロジックに追加されたマシンコードのバイトよりもはるかに多く、これは私にはかなりのコストのようです。プログラマーがプリプロセッサーマクロを使用する利点もあります。同じマクロを任意の整数型に使用できます。 この問題の解決策については 私の答え から この質問 を参照;結局のところ、自分を繰り返す意味はありません...

12
autistic

GCC/Clang __builtin_clz() またはMicrosoft Visual C _BitScanReverse() 組み込み関数は、多くのマシンで単一のマシン命令にコンパイルされます。これをO(1)ソリューションの基礎として使用できます。32ビット実装は次のとおりです。

#include <limits.h>
#include <stdint.h>

/* Return the number of digits in the decimal representation of n. */
unsigned digits(uint32_t n) {
    static uint32_t powers[10] = {
        0, 10, 100, 1000, 10000, 100000, 1000000,
        10000000, 100000000, 1000000000,
    };
    static unsigned maxdigits[33] = {
        1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5,
        5, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, 
    };
    unsigned bits = sizeof(n) * CHAR_BIT - __builtin_clz(n);
    unsigned digits = maxdigits[bits];
    if (n < powers[digits - 1]) {
        -- digits;
    }
    return digits;
}
10
AShelly

最初のメソッドを次のように書くことができると思います

int findn(int num)
{
    char snum[100];    
    return  sprintf(snum, "%d", num);
}

sprintfは書き込まれた文字数を返し、strlenの呼び出しを保存できるためです。

効率については、sprintfの実装に依存すると思います。sprintfのソースを見つけて、それが効率的かどうかを確認する必要があるかもしれません。

8
Tao Feng

1つのライナー:for(digits = 0; num > 0; digits++) num /= 10;

3
SatoriAmagi

バイナリ検索を試してください。明確にするために、符号付き32ビット整数を想定します。まず、_x < 10000_かどうかを確認します。次に、答えに応じて、_x < 100_または_x < 1000000_のように続きます。

それがO(log n)です。ここで、nは桁数です。

3
Bolo

これらの関数は、正でない数に対して大幅に異なる結果をもたらします(最悪は方法3)。そのため、それらの時間の複雑さを比較することは、疑わしい値です。私はすべての場合に必要な答えを与えるものを使用します。コンテキストがないと、それが何であるかを知ることができません(おそらく方法3ではありません)。

方法1の場合、findn(0) == 1、およびfindn(-n) == digits in n + 1(負符号のため)。

方法2の場合、findn(0) == 0、およびfindn(-n) == digits in n

メソッド3では、findn(0) == INT_MIN、およびfindn(-n) == INT_MINも同様です。

3
Aaron Dufour

sprintf()メソッド2を使用して数値を出力します(出力する文字列の長さを決定し、次に文字列の各文字を出力するため)、本質的に遅くなります。

番号3はおそらくln()の多項式近似を含み、1を超える除算が含まれるため、速度も遅くなります( here's afastln()実装、フロート除算がまだ含まれているため、速度が遅くなります)。

だから私の暫定的な推測は、方法2が進むべき道だということです。

これは、この問題に取り組むためのかなり自由な方法であることに注意してください。古き良き時代のミリオン反復を各関数でテストすると、結果がわかると思います。しかし、それも蜂bruteforceでしょうか?

方法2のみが実際の結果を提供することに注意してください。他の方法には、正しく調整する必要のある欠陥があります(Aaronの回答を参照)。したがって、方法2を選択するだけです。

2
Gui13

printf関数は、正常に印刷された桁数を返します。

int digits,n=898392;

digits=printf("%d",n);
printf("Number of digits:%d",digits);

出力:

898392

桁数:6

1
Asha

これが私の解決策です... 100桁までカウントされます。

max_digits = 100

int numdigits(int x) {
  for (int i = 1; i < max_digits; i++) {
    if (x < pow(10, i)) {
      return i;
    }
  }
  return 0;
}
1
d4nk1337sauce

ログの使用は良いオプションかもしれません...

  1. ターゲットマシンにハードウェアサポートがある場合
  2. intdoubleにキャストして、精度を失うことなく戻すことができると確信している場合。

実装例...

int num_digits(int arg) {
    if (arg == 0) {
        return 1;
    }

    arg = abs(arg);

    return (int)log10(arg)+1;
}
0
Autodidact