16

a の 10 進数の桁数をカウントする必要がありますBigInteger。例えば:

  • 99戻り値2
  • 1234戻り値4
  • 9999戻り値4
  • 12345678901234567890戻り値20

10 進数などの a に対してBigIntegerこれ184948を行う必要があります。これを高速かつスケーラブルに行うにはどうすればよいですか?

文字列に変換するアプローチは遅いです。

public String getWritableNumber(BigInteger number) {
   // Takes over 30 seconds for 184948 decimal digits
   return "10^" + (number.toString().length() - 1);
}

このループを 10 で割るアプローチはさらに遅くなります。

public String getWritableNumber(BigInteger number) {
    int digitSize = 0;
    while (!number.equals(BigInteger.ZERO)) {
        number = number.divide(BigInteger.TEN);
        digitSize++;
    }
    return "10^" + (digitSize - 1);
}

より速い方法はありますか?

4

5 に答える 5

10

これは機能しているようです。私はまだ徹底的なテストを実行していません。また、時間テストも実行していませんが、妥当な実行時間があるようです。

public class Test {
  /**
   * Optimised for huge numbers.
   *
   * http://en.wikipedia.org/wiki/Logarithm#Change_of_base
   *
   * States that log[b](x) = log[k](x)/log[k](b)
   *
   * We can get log[2](x) as the bitCount of the number so what we need is
   * essentially bitCount/log[2](10). Sadly that will lead to inaccuracies so
   * here I will attempt an iterative process that should achieve accuracy.
   *
   * log[2](10) = 3.32192809488736234787 so if I divide by 10^(bitCount/4) we
   * should not go too far. In fact repeating that process while adding (bitCount/4)
   * to the running count of the digits will end up with an accurate figure
   * given some twiddling at the end.
   * 
   * So here's the scheme:
   * 
   * While there are more than 4 bits in the number
   *   Divide by 10^(bits/4)
   *   Increase digit count by (bits/4)
   * 
   * Fiddle around to accommodate the remaining digit - if there is one.
   * 
   * Essentially - each time around the loop we remove a number of decimal 
   * digits (by dividing by 10^n) keeping a count of how many we've removed.
   * 
   * The number of digits we remove is estimated from the number of bits in the 
   * number (i.e. log[2](x) / 4). The perfect figure for the reduction would be
   * log[2](x) / 3.3219... so dividing by 4 is a good under-estimate. We 
   * don't go too far but it does mean we have to repeat it just a few times.
   */
  private int log10(BigInteger huge) {
    int digits = 0;
    int bits = huge.bitLength();
    // Serious reductions.
    while (bits > 4) {
      // 4 > log[2](10) so we should not reduce it too far.
      int reduce = bits / 4;
      // Divide by 10^reduce
      huge = huge.divide(BigInteger.TEN.pow(reduce));
      // Removed that many decimal digits.
      digits += reduce;
      // Recalculate bitLength
      bits = huge.bitLength();
    }
    // Now 4 bits or less - add 1 if necessary.
    if ( huge.intValue() > 9 ) {
      digits += 1;
    }
    return digits;
  }

  // Random tests.
  Random rnd = new Random();
  // Limit the bit length.
  int maxBits = BigInteger.TEN.pow(200000).bitLength();

  public void test() {
    // 100 tests.
    for (int i = 1; i <= 100; i++) {
      BigInteger huge = new BigInteger((int)(Math.random() * maxBits), rnd);
      // Note start time.
      long start = System.currentTimeMillis();
      // Do my method.
      int myLength = log10(huge);
      // Record my result.
      System.out.println("Digits: " + myLength+ " Took: " + (System.currentTimeMillis() - start));
      // Check the result.
      int trueLength = huge.toString().length() - 1;
      if (trueLength != myLength) {
        System.out.println("WRONG!! " + (myLength - trueLength));
      }
    }
  }

  public static void main(String args[]) {
    new Test().test();
  }

}

Celeron M ラップトップで約 3 秒かかったので、まともなキットでは 2 秒未満になるはずです。

于 2013-09-17T21:40:56.930 に答える
9

bitLength()を使用して log2 値を取得し、基数を 10 に変更できると思います。

ただし、結果は 1 桁間違っている可能性があるため、これは単なる概算です。

ただし、それが許容できる場合は、常に結果に 1 を追加して、最大でにバインドできます。または、1 を引いて、少なくともを取得します。

于 2013-09-16T12:50:33.813 に答える
1

これは、Convert-to-String メソッドよりも高速に実行する別の方法です。最適な実行時間ではありませんが、Convert-to-String メソッド (180000 桁) を使用した場合の 2.46 秒に対して、0.65 秒という妥当な時間です。

このメソッドは、10 を底とする対数の整数部分を、指定された値から計算します。ただし、ループ除算を使用する代わりに、2 乗によるべき乗と同様の手法を使用します。

前述のランタイムを実現する大まかな実装を次に示します。

public static BigInteger log(BigInteger base,BigInteger num)
{
    /* The technique tries to get the products among the squares of base
     * close to the actual value as much as possible without exceeding it.
     * */
    BigInteger resultSet = BigInteger.ZERO;
    BigInteger actMult = BigInteger.ONE;
    BigInteger lastMult = BigInteger.ONE;
    BigInteger actor = base;
    BigInteger incrementor = BigInteger.ONE;
    while(actMult.multiply(base).compareTo(num)<1)
    {
        int count = 0;
        while(actMult.multiply(actor).compareTo(num)<1)
        {
            lastMult = actor; //Keep the old squares
            actor = actor.multiply(actor); //Square the base repeatedly until the value exceeds 
            if(count>0) incrementor = incrementor.multiply(BigInteger.valueOf(2));
            //Update the current exponent of the base
            count++;
        }
        if(count == 0) break;

        /* If there is no way to multiply the "actMult"
         * with squares of the base (including the base itself)
         * without keeping it below the actual value, 
         * it is the end of the computation 
         */
        actMult = actMult.multiply(lastMult);
        resultSet = resultSet.add(incrementor);
        /* Update the product and the exponent
         * */
        actor = base;
        incrementor = BigInteger.ONE;
        //Reset the values for another iteration
    }
    return resultSet;
}
public static int digits(BigInteger num)
{
    if(num.equals(BigInteger.ZERO)) return 1;
    if(num.compareTo(BigInteger.ZERO)<0) num = num.multiply(BigInteger.valueOf(-1));
    return log(BigInteger.valueOf(10),num).intValue()+1;
}

これが役立つことを願っています。

于 2014-12-27T07:28:06.947 に答える