正の10進整数の桁数を求める方法のいくつか。

以下の方法では負数を扱うことは考えない。 負数を扱う場合は事前に符合を逆転して正数とすること。

§1 対数(log10)を取る

using System;

class Sample {
  static void Main()
  {
    foreach (var num in new[] {0, 1, 9, 10, 11, 99, 100, 500, 999, 1000, 6789, 10000, 99999}) {
      Console.WriteLine("{0} => {1}", num, unchecked((int)Math.Log10(num)) + 1);
    }
  }
}
実行結果
0 => -2147483647
1 => 1
9 => 1
10 => 2
11 => 2
99 => 2
100 => 3
500 => 3
999 => 3
1000 => 4
6789 => 4
10000 => 5
99999 => 5

Math.Log10は値が0の場合NegativeInfinityを返すので、個別に処理する必要がある。

§2 文字列にして長さを求める

using System;

class Sample {
  static void Main()
  {
    foreach (var num in new[] {0, 1, 9, 10, 11, 99, 100, 500, 999, 1000, 6789, 10000, 99999}) {
      Console.WriteLine("{0} => {1}", num, num.ToString("D").Length);
    }
  }
}
実行結果
0 => 1
1 => 1
9 => 1
10 => 2
11 => 2
99 => 2
100 => 3
500 => 3
999 => 3
1000 => 4
6789 => 4
10000 => 5
99999 => 5

§3 10で割り続けた回数から求める

using System;

class Sample {
  static void Main()
  {
    foreach (var num in new[] {0, 1, 9, 10, 11, 99, 100, 500, 999, 1000, 6789, 10000, 99999}) {
      var digit = 1;

      for (var n = num; 10 <= n; digit++) {
        n /= 10;
      }

      Console.WriteLine("{0} => {1}", num, digit);
    }
  }
}
実行結果
0 => 1
1 => 1
9 => 1
10 => 2
11 => 2
99 => 2
100 => 3
500 => 3
999 => 3
1000 => 4
6789 => 4
10000 => 5
99999 => 5

§4 桁数のテーブルと比較する

using System;

class Sample {
  static readonly int[] digits = {0, 9, 99, 999, 9999, 99999, 999999, /*9999999, 99999999, ... */};

  static void Main()
  {
    foreach (var num in new[] {0, 1, 9, 10, 11, 99, 100, 500, 999, 1000, 6789, 10000, 99999}) {
      var digit = 1;

      for (;;digit++) {
        if (num <= digits[digit])
          break;
      }

      Console.WriteLine("{0} => {1}", num, digit);
    }
  }
}
実行結果
0 => 1
1 => 1
9 => 1
10 => 2
11 => 2
99 => 2
100 => 3
500 => 3
999 => 3
1000 => 4
6789 => 4
10000 => 5
99999 => 5

§5 速度比較

ランダムな数10,000,000個について上記の方法で桁数を求めた場合の速度を比較したもの。 それぞれ3回ずつ試行。 Ubuntu 11.10 + Mono 2.10.8での実行結果。

  • 結果
    • 最速はテーブルを使った方法、最も遅いのは文字列化
    • 10で割るよりはMath.Log10を使った方が早い
    • ただし、数がある程度小さい場合は、Math.Log10より10で割る方が速くなる
    • Math.Log10の速度は数の大きさによらず一定
数の範囲が1 ~ int.MaxValueの場合
4.0.30319.1
Unix 3.0.0.14
1 ~ 2147483647

Math.Log10    : 00:00:01.1368336
String.Length : 00:00:04.1699058
divide by 10  : 00:00:01.7512138
table         : 00:00:00.7284158

Math.Log10    : 00:00:01.1065396
String.Length : 00:00:04.1047607
divide by 10  : 00:00:01.7776515
table         : 00:00:00.7468283

Math.Log10    : 00:00:01.1293331
String.Length : 00:00:04.1484966
divide by 10  : 00:00:01.7761593
table         : 00:00:00.7391820
数の範囲が1 ~ 10000の場合
4.0.30319.1
Unix 3.0.0.14
1 ~ 10000

Math.Log10    : 00:00:01.1085428
String.Length : 00:00:02.9993363
divide by 10  : 00:00:00.7884117
table         : 00:00:00.5584225

Math.Log10    : 00:00:01.1094404
String.Length : 00:00:02.9639001
divide by 10  : 00:00:00.7818080
table         : 00:00:00.5582405

Math.Log10    : 00:00:01.1111837
String.Length : 00:00:02.9534707
divide by 10  : 00:00:00.8002473
table         : 00:00:00.5687480
検証に使ったコード
using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;

class Sample {
  static readonly int[] digits = {0, 9, 99, 999, 9999, 99999, 999999, 9999999, 99999999, 999999999, int.MaxValue};

  const int min = 1;
  const int max = int.MaxValue;

  static IEnumerable<int> CreateSource(int seed, int count)
  {
    var rand = new Random(seed);

    for (; 0 < count; count--) {
      yield return rand.Next(min, max);
    }
  }

  static void Main()
  {
    Console.WriteLine(Environment.Version);
    Console.WriteLine(Environment.OSVersion);
    Console.WriteLine("{0}  {1}", min, max);
    Console.WriteLine();

    const int count = 10 * 1000 * 1000;
    int digit = 0;

    for (var i = 0; i < 3; i++) {
      var seed = unchecked((int)Environment.TickCount);

      var sw1 = Stopwatch.StartNew();

      foreach (var num in CreateSource(seed, count)) {
        digit = (int)Math.Log10(num) + 1;
      }

      sw1.Stop();

      Console.WriteLine("Math.Log10    : {0}", sw1.Elapsed);

      var sw2 = Stopwatch.StartNew();

      foreach (var num in CreateSource(seed, count)) {
        digit = num.ToString("D").Length;
      }

      sw2.Stop();

      Console.WriteLine("String.Length : {0}", sw2.Elapsed);

      var sw3 = Stopwatch.StartNew();

      foreach (var num in CreateSource(seed, count)) {
        digit = 1;
        for (var n = num; 10 <= n; digit++) {
          n /= 10;
        }
      }

      sw3.Stop();

      Console.WriteLine("divide by 10  : {0}", sw3.Elapsed);

      var sw4 = Stopwatch.StartNew();

      foreach (var num in CreateSource(seed, count)) {
        digit = 1;
        for (;;digit++) {
          if (num <= digits[digit])
            break;
        }
      }

      sw4.Stop();

      Console.WriteLine("table         : {0}", sw4.Elapsed);
      Console.WriteLine();
   }
  }
}