简体   繁体   English

更快地找出一个数字是否以2开头?

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

在Java中 - 如果给定的整数是以数字2开头而不必将数字转换为字符串,那么更快的方法是什么?

String.valueOf(number).charAt(0) == '2'

If you wanted to avoid converting it to a string, you could just keep dividing by 10 to find the most significant digit: 如果您想避免将其转换为字符串,您可以继续除以10以找到最重要的数字:

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. 我不知道这是否会比Husman的log / pow方法更快。

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; math方法是一个明显的失败者;
  • the string approach beats math by a factor of six; string方法比math六倍;
  • the simplest divide algorithm is 60% faster than that; 最简单的divide算法比这快60%;
  • OldCurmudgeon's bitTwiddling algorithm is yet another six times faster than divide ; OldCurmudgeon的bitTwiddling算法是另一种比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; OldCurmudgeon的特殊装置的avoidDivide方法(直接给出是/否答案,与所有其他方式不同,实际上确定第一位数字)是比bitTwiddiling算法快bitTwiddiling并且是无可争议的赢家;
  • for diagnostic I have also included a brokenDivide algo. 为了诊断,我还包括了一个brokenDivide算法。 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 . 重点是使用divide算法的瓶颈在下划线: brokenDividedivide快4.6倍,比bitTwiddling慢0.2倍。

Note that I have used quite large numbers; 请注意,我使用了相当多的数字; the relative speeds vary with the magnitude. 相对速度随幅度而变化。

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
  }

Derived from Bit Twiddling Hacks By Sean Eron Anderson 来自Sean Eron Anderson的Bit Twiddling Hacks

/*
 * Log(2) of an int.
 * 
 * See: http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogLookup
 */
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;
}

public static int log2(int v) {
  int t, tt;
  if ((tt = v >> 16) != 0) {
    return (t = tt >> 8) != 0 ? 24 + Log256[t] : 16 + Log256[tt];
  } else {
    return = (t = v >> 8) != 0 ? 8 + Log256[t] : Log256[v];
  }
}

/*
 * Log(10) of an int.
 * 
 * See: http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog10
 */
private static int PowersOf10[] = {1, 10, 100, 1000, 10000, 100000,
                                   1000000, 10000000, 100000000, 1000000000};

public static int log10(int v) {
  int t = (log2(v) + 1) * 1233 >> 12;
  return t - (v < PowersOf10[t] ? 1 : 0);
}

// Returns the top digit of the integer.
public static int topDigit(int n) {
  return n / PowersOf10[log10(n)];
}

I've tested this and it seems to work - how does it benchmark? 我已经对此进行了测试,似乎有效 - 它如何进行基准测试?

UPDATED : I now test with a different number on each iteration and I added OldCurmudgeon's solution (the one derived from Bit Twiddling Hacks By Sean Eron Anderson). 更新 :我现在在每次迭代时使用不同的数字进行测试,并添加了OldCurmudgeon的解决方案(来自Sean Eron Anderson的Bit Twiddling Hacks)。

I benchmarked different solutions, including Jon Skeet's :) : 我对不同的解决方案进行了基准测试,包括Jon Skeet的:):

Here is my main method used for this test: 这是我用于此测试的主要方法:

public static void main(String[] args){

  long tip = System.currentTimeMillis();

  //Call the test method 10 000 000 times.
  for(int i= 0 ; i< 10_000_000 ; i++){
    //Here I call the method representing the algorithm.
    foo3(i);
  }

  long top = System.currentTimeMillis();

  System.out.println("Total time : "+(top-tip));

}

1) With the OP's solution : 1)使用OP的解决方案:

public static boolean foo1(Integer i){
  return String.valueOf(i).charAt(0) == '2';
}

It takes 425 ms on my computer. 我的电脑需要425毫秒

2) Try with Integer.toString().startsWith() : 2)尝试使用Integer.toString()。startsWith():

public static boolean foo2(Integer i){
  return Integer.toString(i).startsWith("2");
}

It takes 410 ms on my computer. 我的电脑需要410毫秒

3) With Husman's solution : 3)借助Husman的解决方案:

public static boolean foo3(Integer i){
  i = Math.abs(i);
  return ((int) Math.floor(i / Math.pow(10, Math.floor(Math.log10(i))))) == 2;
}

It takes 2020 ms . 需要2020毫秒

4) With Jon's solution : 4)使用Jon的解决方案:

public static boolean foo4(Integer i){
  return getMostSignificantDigit(i)==2;
}

public static int getMostSignificantDigit(int x)
{
  // TODO: Negative numbers :)
  while (x > 10)
  {
    x = x / 10;
  }
  return x;
}

It takes 125 ms 需要125毫秒

5) With OldCurmudgeon's solution (see her answer): 5)使用OldCurmudgeon的解决方案(见她的回答):

public static boolean foo5(Integer i){
  return OldCurmudgeon.topDigit(i)==2;
}

It takes 97 ms 这需要97毫秒

One more possibility - because division is clearly a bottleneck (or is it?): 还有一种可能性 - 因为分裂显然是一个瓶颈(或者是它?):

// Pairs of range limits.
// Reverse order to put the widest range at the top.
static final int [] limits = new int[] {
  // Can hard-code this one to avoid one comparison.
  //2000000000, Integer.MAX_VALUE,
  200000000, 300000000-1,
  20000000, 30000000-1,
  2000000, 3000000-1,
  200000, 300000-1,
  20000, 30000-1,
  2000, 3000-1,
  200, 300-1,
  20, 30-1,
  2, 3-1,
};

public static boolean firstDigitIsTwo(int v) {
  // All ints from there up start with 2.
  if ( v >= 2000000000 ) return true;
  for ( int i = 0; i < limits.length; i += 2 ) {
    // Assumes array is decreasing order.
    if ( v > limits[i+1] ) return false;
    // In range?
    if ( v >= limits[i] ) return true;
  }
  return false;
}

I am answering here just so I can post the test code and results: 我在这里回答,所以我可以发布测试代码和结果:

public class NumberStart extends AbstractBenchmark {

private static final int START = 10000000;
private static final int END = 50000000;

private static int result;

@Test
@BenchmarkOptions(benchmarkRounds = 1, warmupRounds = 1)
public void divide() {
    for (int x = START; x < END; x++) {
        int i = x;
        while (i > 10) {
            i = i / 10;
        }
        result = i;
    }
}

@Test
@BenchmarkOptions(benchmarkRounds = 1, warmupRounds = 1)
public void math() {
    for (int x = START; x < END; x++) {
        result = (int) Math.floor(x / Math.pow(10, Math.floor(Math.log10(x))));
    }
}

@Test
@BenchmarkOptions(benchmarkRounds = 1, warmupRounds = 1)
public void string() {
    for (int x = START; x < END; x++) {
        result = (int) Integer.toString(x).charAt(0);
    }
}

@Test
@BenchmarkOptions(benchmarkRounds = 1, warmupRounds = 1)
public void bitmath() {
    for (int x = START; x < END; x++) {
        result = (int) BitMath.topDigit(x);
    }
}
}

bitmath() method uses the code posted by OldCurmudgeon. bitmath()方法使用OldCurmudgeon发布的代码。

And here are the results: 以下是结果:

NumberStart.divide: [measured 1 out of 2 rounds, threads: 1 (sequential)]
 round: 0.36 [+- 0.00], round.block: 0.00 [+- 0.00], round.gc: 0.00 [+- 0.00], GC.calls: 0, GC.time: 0.00, time.total: 0.71, time.warmup: 0.36, time.bench: 0.35
NumberStart.string: [measured 1 out of 2 rounds, threads: 1 (sequential)]
 round: 1.64 [+- 0.00], round.block: 0.00 [+- 0.00], round.gc: 0.00 [+- 0.00], GC.calls: 147, GC.time: 0.08, time.total: 3.31, time.warmup: 1.68, time.bench: 1.64
NumberStart.bitmath: [measured 1 out of 2 rounds, threads: 1 (sequential)]
 round: 0.22 [+- 0.00], round.block: 0.00 [+- 0.00], round.gc: 0.00 [+- 0.00], GC.calls: 0, GC.time: 0.00, time.total: 0.45, time.warmup: 0.23, time.bench: 0.22
NumberStart.math: [measured 1 out of 2 rounds, threads: 1 (sequential)]
 round: 4.93 [+- 0.00], round.block: 0.00 [+- 0.00], round.gc: 0.00 [+- 0.00], GC.calls: 0, GC.time: 0.00, time.total: 9.89, time.warmup: 4.95, time.bench: 4.93

bitmath() method is the fastest method so far. bitmath()方法是目前为止最快的方法。

    int intValue=213;
    if(String.valueOf(intValue).startsWith("2"))
    {
        System.out.println("Yes");
    }
    else
    {
        System.out.println("No");
    }

((int) number / 10^(numberlength-1))将给出第一个数字。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM