Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Faster way to find out if a number starts with 2?

In Java - what is the faster way to find if the given integer number is starting with the digit 2 without having to convert the number into a string?

String.valueOf(number).charAt(0) == '2'
like image 840
user2652406 Avatar asked Aug 05 '13 08:08

user2652406


3 Answers

If you wanted to avoid converting it to a string, you could just keep dividing by 10 to find the most significant digit:

int getMostSignificantDigit(int x)
{
    // Need to handle Integer.MIN_VALUE "specially" as the absolute value can't
    // represented. We can hard-code the fact that it starts with 2 :)
    x = x == Integer.MIN_VALUE ? 2 : Math.abs(x);
    while (x >= 10)
    {
        x = x / 10;
    }
    return x;
}

I don't know whether this would be faster than Husman's log/pow approach.

like image 173
Jon Skeet Avatar answered Oct 21 '22 04:10

Jon Skeet


I've pitted the various solutions against each other:

public class FirstDigit
{
  static int digit;
  @GenerateMicroBenchmark public void string() {
    for (int i = 200_000_000; i < 400_000_000; i += 999961)
      digit = Integer.toString(i).charAt(0);
  }
  @GenerateMicroBenchmark public void math() {
    for (int i = 200_000_000; i < 400_000_000; i += 999961) {
      digit = (int) floor(i / pow(10, floor(log10(i))));
    }
  }
  @GenerateMicroBenchmark public void divide() {
    for (int i = 200_000_000; i < 400_000_000; i += 999961) {
      int x = i;
      while (x > 10) x /= 10;
      digit = x;
    }
  }
  @GenerateMicroBenchmark public void brokenDivide() {
    for (int i = 200_000_000; i < 400_000_000; i += 999961) {
      int x = i;
      while (x > 10) x >>= 3;
      digit = x;
    }
  }
  @GenerateMicroBenchmark public void bitTwiddling() {
    for (int i = 200_000_000; i < 400_000_000; i += 999961) {
      digit = i/powersOf10[log10(i)];
    }
  }
  @GenerateMicroBenchmark public boolean avoidDivide() {
    boolean b = true;
    for (int i = 200_000_000; i < 400_000_000; i += 999961) {
      b ^= firstDigitIsTwo(i);
    }
    return b;
  }


  private static final int[] log256 = new int[256];
  static {
    for (int i = 0; i < 256; i++) log256[i] = 1 + log256[i / 2];
    log256[0] = -1;
  }
  private static int powersOf10[] = {1, 10, 100, 1000, 10_000, 100_000,
    1_000_000, 10_000_000, 100_000_000, 1_000_000_000};

  public static int log2(int v) {
    int t, tt;
    return ((tt = v >> 16) != 0)?
        (t = tt >> 8) != 0 ? 24 + log256[t] : 16 + log256[tt]
      : (t = v >> 8) != 0 ? 8 + log256[t] : log256[v];
  }
  public static int log10(int v) {
    int t = (log2(v) + 1) * 1233 >> 12;
    return t - (v < powersOf10[t] ? 1 : 0);
  }

  static final int [] limits = new int[] {
    2_000_000_000, Integer.MAX_VALUE,
    200_000_000, 300_000_000-1,
    20_000_000, 30_000_000-1,
    2_000_000, 3_000_000-1,
    200_000, 300_000-1,
    20_000, 30_000-1,
    2000, 3000-1,
    200, 300-1,
    20, 30-1,
    2, 3-1,
  };
  public static boolean firstDigitIsTwo(int v) {
    for ( int i = 0; i < limits.length; i+= 2) {
      if ( v > limits[i+1] ) return false;
      if ( v >= limits[i] ) return true;
    }
    return false;
  }
}

And the results:

Benchmark                   Mode Thr    Cnt  Sec         Mean   Mean error    Units
FirstDigit.avoidDivide     thrpt   1      3    5     2324.271       58.145 ops/msec
FirstDigit.bitTwiddling    thrpt   1      3    5      716.453        6.407 ops/msec
FirstDigit.brokenDivide    thrpt   1      3    5      578.259        7.534 ops/msec
o.s.FirstDigit.divide      thrpt   1      3    5      125.509        2.323 ops/msec
o.s.FirstDigit.string      thrpt   1      3    5       78.233        2.030 ops/msec
o.s.FirstDigit.math        thrpt   1      3    5       14.226        0.034 ops/msec
  • the math approach is a clear loser;
  • the string approach beats math by a factor of six;
  • the simplest divide algorithm is 60% faster than that;
  • OldCurmudgeon's bitTwiddling algorithm is yet another six times faster than divide;
  • OldCurmudgeon's special-cased avoidDivide approach (which gives a yes/no answer directly, unlike all others, which actually determine the first digit) is another three times faster than the bitTwiddiling algo and is the undisputed winner;
  • for diagnostic I have also included a brokenDivide algo. Instead of dividing by ten, it just shifts by three, giving the wrong answer. The point is to underline where the bottleneck with the divide algorithm is: brokenDivide is 4.6 times faster than divide, and just 0.2 times slower than bitTwiddling.

Note that I have used quite large numbers; the relative speeds vary with the magnitude.

like image 24
Marko Topolnik Avatar answered Oct 21 '22 04:10

Marko Topolnik


I would be tempted to do something like this:

  x = Math.abs(x);
  if ( ((int) Math.floor(x / Math.pow(10, Math.floor(Math.log10(x))))) == 2 )
  {
     ... // x equals 2
  }
like image 25
Husman Avatar answered Oct 21 '22 06:10

Husman