正の整数の桁数を見つける最良の方法は何ですか?
私はこの3つの基本的な方法を見つけました:
文字列への変換
String s = new Integer(t).toString();
int len = s.length();
for loop
for(long long int temp = number; temp >= 1;)
{
temp/=10;
decimalPlaces++;
}
対数計算
digits = floor( log10( number ) ) + 1;
ここで、ほとんどの言語でlog10(x)= ln(x)/ ln(10)を計算できます。
最初に、文字列メソッドは最もダーティなメソッドであると考えましたが、考えれば考えるほど、最速の方法だと思います。またはそれは?
このメソッドは常にあります:
n = 1;
if ( i >= 100000000 ) { n += 8; i /= 100000000; }
if ( i >= 10000 ) { n += 4; i /= 10000; }
if ( i >= 100 ) { n += 2; i /= 100; }
if ( i >= 10 ) { n += 1; }
私は知りません、そして、あなたの個々の言語がどのように実装されるかによって答えは異なるかもしれません。
だから、ストレステストを! 3つのソリューションをすべて実装します。 1から1,000,000(またはソリューションが実行される数値を表す他の巨大な数値セット)でそれらを実行し、それぞれにかかる時間を計ります。
あなたのソリューションをお互いにピットインし、彼らにそれを戦わせます。知的剣闘士のように。 3つのアルゴリズムが登場! 1つのアルゴリズムが残ります!
正しい答えはそれを測定することです-しかし、文字列の変換とエンドマーカーの検索に必要なCPUステップの数を推測できるはずです。
次に、プロセッサが実行できるFPU操作の数と1つのログを計算するのがどれほど簡単かを考えます。
編集:月曜日の朝にもう少し時間を無駄に:-)
String s = new Integer(t).toString();
int len = s.length();
高水準言語の問題の1つは、明らかに単純なステートメントの裏でシステムがどの程度の作業を行っているかを推測することです。必須 ジョエルリンク
このステートメントには、文字列のメモリの割り当てと、場合によっては文字列の一時的なコピーがいくつか含まれます。整数を解析し、その数字を文字列にコピーする必要があります。数値が大きい場合は、既存のメモリを再割り当てして移動する必要があります。多数のロケール設定をチェックして、国が「、」または「。」を使用しているかどうかを判断する必要がある場合があります。
次に、長さを見つけるには文字列全体をスキャンする必要がありますが、ユニコードやローカル固有の設定などを考慮します。
代わりに:
digits = floor( log10( number ) ) + 1;
紙の上でこれを行うのが難しいからといって、コンピューターにとって難しいというわけではありません!実際、高性能コンピューティングの良いルールはそうでした-人間にとって何かが難しい場合(流体力学、3Dレンダリング)はコンピューターにとっては簡単で、人間にとっては簡単なら(顔認識、音声の検出騒々しい部屋)それはコンピュータにとって難しいです!
一般に、組み込みの数学関数log/sin/cosなどは、50年にわたってコンピューター設計の重要な部分であると想定できます。そのため、FPUのハードウェア関数に直接マッピングしていなくても、代替の実装は非常に効率的であると確信できます。
このアルゴリズムは、次のことを前提としても適切です。
数字の境界はわかりません
var num = 123456789L;
var len = 0;
var tmp = 1L;
while(tmp < num)
{
len++;
tmp = (tmp << 3) + (tmp << 1);
}
このアルゴリズムは、for-loop(2)に匹敵する速度を提供する必要がありますが、(除算の代わりに2ビットシフト、加算および減算)により少し高速になります。
Log10アルゴリズムに関しては、Log関数を計算するための解析式には無限ループがあり、正確に計算できないため、近似的な答えしか得られません(実際に近いが、それでも)。 Wiki .
テスト条件
結果
桁:[1,10]、
いや実行回数:1,000,000
ランダムサンプル:8777509,40442298,477894,329950,513,91751410,313,3159,131309,2
結果:7,8,6,6,3,8,3,4,6,1
文字列への変換:724ms
対数計算:349ms
DIV 10反復:229ms
手動調整:136ms
注:著者は、10桁を超える数字について結論を出すことを控えています。
スクリプト
package {
import flash.display.MovieClip;
import flash.utils.getTimer;
/**
* @author Daniel
*/
public class Digits extends MovieClip {
private const NUMBERS : uint = 1000000;
private const DIGITS : uint = 10;
private var numbers : Array;
private var digits : Array;
public function Digits() {
// ************* NUMBERS *************
numbers = [];
for (var i : int = 0; i < NUMBERS; i++) {
var number : Number = Math.floor(Math.pow(10, Math.random()*DIGITS));
numbers.Push(number);
}
trace('Max digits: ' + DIGITS + ', count of numbers: ' + NUMBERS);
trace('sample: ' + numbers.slice(0, 10));
// ************* CONVERSION TO STRING *************
digits = [];
var time : Number = getTimer();
for (var i : int = 0; i < numbers.length; i++) {
digits.Push(String(numbers[i]).length);
}
trace('\nCONVERSION TO STRING - time: ' + (getTimer() - time));
trace('sample: ' + digits.slice(0, 10));
// ************* LOGARITMIC CALCULATION *************
digits = [];
time = getTimer();
for (var i : int = 0; i < numbers.length; i++) {
digits.Push(Math.floor( Math.log( numbers[i] ) / Math.log(10) ) + 1);
}
trace('\nLOGARITMIC CALCULATION - time: ' + (getTimer() - time));
trace('sample: ' + digits.slice(0, 10));
// ************* DIV 10 ITERATION *************
digits = [];
time = getTimer();
var digit : uint = 0;
for (var i : int = 0; i < numbers.length; i++) {
digit = 0;
for(var temp : Number = numbers[i]; temp >= 1;)
{
temp/=10;
digit++;
}
digits.Push(digit);
}
trace('\nDIV 10 ITERATION - time: ' + (getTimer() - time));
trace('sample: ' + digits.slice(0, 10));
// ************* MANUAL CONDITIONING *************
digits = [];
time = getTimer();
var digit : uint;
for (var i : int = 0; i < numbers.length; i++) {
var number : Number = numbers[i];
if (number < 10) digit = 1;
else if (number < 100) digit = 2;
else if (number < 1000) digit = 3;
else if (number < 10000) digit = 4;
else if (number < 100000) digit = 5;
else if (number < 1000000) digit = 6;
else if (number < 10000000) digit = 7;
else if (number < 100000000) digit = 8;
else if (number < 1000000000) digit = 9;
else if (number < 10000000000) digit = 10;
digits.Push(digit);
}
trace('\nMANUAL CONDITIONING: ' + (getTimer() - time));
trace('sample: ' + digits.slice(0, 10));
}
}
}
文字列への変換:これは、各数字を反復処理し、現在の数字にマップする文字を見つけ、文字のコレクションに文字を追加する必要があります。次に、結果のStringオブジェクトの長さを取得します。 n =#digitsの場合、O(n)で実行されます。
for-loop:2つの数学演算を実行します:数値を10で除算し、カウンターをインクリメントします。 n =#digitsの場合、O(n)で実行されます。
logarithmic:log10とfloorを呼び出して、1を追加します。O(1)のように見えますが、log10またはfloor関数がどれほど高速であるかはよくわかりません。使用不足で萎縮しているため、これらの関数にhiddenの複雑さがあります。
つまり、複数の数学演算やlog10
で何が起こっているよりも、数字マッピングを高速に検索しているのでしょうか。答えはおそらく異なるでしょう。文字マッピングが高速なプラットフォームと、計算を高速化するプラットフォームがあります。また、最初のメソッドは、長さを取得する目的でのみ存在する新しいStringオブジェクトを作成することにも留意してください。これはおそらく他の2つの方法よりも多くのメモリを使用しますが、問題になる場合もあれば、そうでない場合もあります。
使用するatoi/toStringアルゴリズムは方法2と似ているため、明らかに方法1を競合から排除できます。
方法3の速度は、命令セットにログベース10が含まれるシステム用にコードがコンパイルされているかどうかによって異なります。
使用しているプログラミング言語で最も簡単なソリューションを使用してください。整数の桁数をカウントすることが(有用な)プログラムのボトルネックになるケースは考えられません。
char buffer[32];
int length = sprintf(buffer, "%ld", (long)123456789);
len = (length . show) 123456789
length = String(123456789).length;
$length = strlen(123456789);
length = Len(str(123456789)) - 1
「特定の基数で特定の数を表すために必要な桁数を決定する」ために提案する3つの方法については、実際にはそれらのどれも好きではありません。代わりに以下に示す方法を好みます。
あなたの方法#1(文字列)に関して:文字列と数値の間で前後に変換することを伴うものは通常非常に遅いです。
方法#2(temp/= 10)について:これは、x/10が常に「10で割ったx」を意味すると想定しているため、致命的な欠陥があります。しかし、多くのプログラミング言語(例:C、C++)では、「x」が整数型の場合、「x/10」は「整数除算」を意味し、浮動小数点除算とは異なります。反復ごとに丸め誤差が発生し、ソリューション#2が使用するような再帰式に蓄積されます。
方法#3(ログ)について:浮動小数点データ型は64ビット整数ほど正確ではない傾向があるため、大きな数値(少なくともC、およびおそらく他の言語でも)にはバグがあります。
したがって、これらのメソッドのうち3つすべてが嫌いです。#1は動作しますが、速度が遅く、#2は破損し、#3は多数の場合にバグがあります。代わりに、私はこれが好きです。これは、0から約18.44キンテリオンまでの数で機能します。
unsigned NumberOfDigits (uint64_t Number, unsigned Base)
{
unsigned Digits = 1;
uint64_t Power = 1;
while ( Number / Power >= Base )
{
++Digits;
Power *= Base;
}
return Digits;
}
import math
def numdigits(n):
return ( int(math.floor(math.log10(n))) + 1 )
非常に大きな整数の場合、logメソッドははるかに高速です。例えば、2491327桁の数字(気になるなら11920928番目のフィボナッチ数)の場合、Pythonは10分周アルゴリズムを実行するのに数分かかり、1+floor(log(n,10))
。
複雑にしないでおく:
long long int a = 223452355415634664;
int x;
for (x = 1; a >= 10; x++)
{
a = a / 10;
}
printf("%d", x);
ループの代わりに再帰的なソリューションを使用できますが、何らかの形で似ています:
@tailrec
def digits (i: Long, carry: Int=1) : Int = if (i < 10) carry else digits (i/10, carry+1)
digits (8345012978643L)
Longsを使用すると、状況が変わる可能性があります。通常の入力に応じて、小さなアルゴリズムと長いアルゴリズムを別々のアルゴリズムに対して個別に測定し、適切なアルゴリズムを選択します。 :)
もちろん、スイッチに勝るものはありません。
switch (x) {
case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 9: return 1;
case 10: case 11: // ...
case 99: return 2;
case 100: // you get the point :)
default: return 10; // switch only over int
}
プレーンOアレイを除く:
int [] size = {1,1,1,1,1,1,1,1,1,2,2,2,2,2,... };
int x = 234561798;
return size [x];
一部の人々は、コードサイズを最適化するようにあなたに言うでしょうが、やや、早すぎる最適化...
Swift 4。
アルゴリズムコード:
extension Int {
var numberOfDigits0: Int {
var currentNumber = self
var n = 1
if (currentNumber >= 100000000) {
n += 8
currentNumber /= 100000000
}
if (currentNumber >= 10000) {
n += 4
currentNumber /= 10000
}
if (currentNumber >= 100) {
n += 2
currentNumber /= 100
}
if (currentNumber >= 10) {
n += 1
}
return n
}
var numberOfDigits1: Int {
return String(self).count
}
var numberOfDigits2: Int {
var n = 1
var currentNumber = self
while currentNumber > 9 {
n += 1
currentNumber /= 10
}
return n
}
}
測定コード:
var timeInterval0 = Date()
for i in 0...10000 {
i.numberOfDigits0
}
print("timeInterval0: \(Date().timeIntervalSince(timeInterval0))")
var timeInterval1 = Date()
for i in 0...10000 {
i.numberOfDigits1
}
print("timeInterval1: \(Date().timeIntervalSince(timeInterval1))")
var timeInterval2 = Date()
for i in 0...10000 {
i.numberOfDigits2
}
print("timeInterval2: \(Date().timeIntervalSince(timeInterval2))")
出力
timeInterval0:1.92149806022644
timeInterval1:0.557608008384705
timeInterval2:2.83262193202972
この測定に基づいて、文字列変換はSwift言語の最適なオプションです。
@ daniel.sedlacekの結果を見て興味があったので、Swiftを使用して10桁以上の数字に対していくつかのテストを行いました。遊び場で次のスクリプトを実行しました。
let base = [Double(100090000000), Double(100050000), Double(100050000), Double(100000200)]
var rar = [Double]()
for i in 1...10 {
for d in base {
let v = d*Double(arc4random_uniform(UInt32(1000000000)))
rar.append(v*Double(arc4random_uniform(UInt32(1000000000))))
rar.append(Double(1)*pow(1,Double(i)))
}
}
print(rar)
var timeInterval = NSDate().timeIntervalSince1970
for d in rar {
floor(log10(d))
}
var newTimeInterval = NSDate().timeIntervalSince1970
print(newTimeInterval-timeInterval)
timeInterval = NSDate().timeIntervalSince1970
for d in rar {
var c = d
while c > 10 {
c = c/10
}
}
newTimeInterval = NSDate().timeIntervalSince1970
print(newTimeInterval-timeInterval)
80要素の結果
.10506987571716 floor(log10(x))の場合
.867973804473877 div 10反復
log(x,n)-mod(log(x,n),1)+1
ここで、xは基数、nは数値です。