6

Was ist der schnellste Weg,Was ist der schnellste Weg, um die höchste Dezimalstelle einer ganzen Zahl zu erhalten?

template <typename T> 
unsigned highest_decimal_digit(T x); 

(die beispielsweise 3 für 356431, 7 für 71 und 9 für 9 zurückkehrt) zu implementieren?

Das Beste, was ich denken kann ist:

  • constexpr-Berechnung die „mittlere Größe“ Leistung von 10, die in T. paßt
  • eine binäre Suche durchführt (über die Befugnisse von 10, möglicherweise Verwenden einer consExpr-konstruierten Nachschlagetabelle, um p zu finden, die höchste Potenz von 10 niedriger als x.
  • return x von p
  • geteilt

... aber vielleicht gibt es einen anderen Ansatz.

Hinweise:

  • drückte ich die Frage und meinen Ansatz in C++ 14ish Bedingungen, und eine Lösung im Code wäre schön, aber eine abstrakte Lösung (oder sogar eine Lösung in x86_64 Montage) wäre in Ordnung. Ich möchte jedoch etwas, das für alle (unsigned) Integer-Typen funktioniert.
  • Sie können signierte Integraltypen ignorieren.
  • Ich habe nicht angegeben, was "schnell" ist, aber bitte Hardware-bewusst sein.
+0

Ist die Verwendung von Strings nicht erlaubt ?? ..... – yobro97

+0

@manlio in der Tat, und sogar die beste Antwort entspricht mir: P – Vesper

+0

@ yobro97: Es gibt keine Möglichkeit, dass jede Arbeit mit Strings für eine schnelle Lösung ermöglicht. – einpoklum

Antwort

0

Optionen, die mir vorkommen;

Brute Kraft: halten Sie die ganze Zahl durch 10, bis Sie Null erhalten; das sagt Ihnen, welche Reihenfolge der Nummer Sie betrachten (zB 860 dauert 3 Schichten (86, 8, 0) also ist es ein 10^3) dann Rückkehr n/(10^Reihenfolge)

binäre Suche: wie Sie sagen, suchen Sie über 10er-Potenzen, aber es erfordert zusätzliche Variablen und Zuordnungen und die Sorge wäre, zahlt sich diese zusätzliche Tracking-Info für sich auf die Arten von Zahlen, die Sie interessieren? Zum Beispiel, wenn die meisten Ihrer Zahlen klein sind, kann Brute-Force einfach schneller sein.

Bitshift-Optimierung: zählen, wie oft Sie x >> 1 tun müssen, bis Sie zu Null kommen; Dies legt den Bereich für Ihre Suche fest. Zum Beispiel, 94 braucht 7 Schichten, um die Nummer zu löschen. Daher ist es < 128. Daher starten Brute-Force-Suche bei 10^3. Sie benötigen eine Suche nach Bits => Reihenfolge.

+0

Division ist irgendwie teuer ... Ich würde denken, dass es zumindest für 32-Bit- und 64-Bit-Nummern lohnt, etwas klüger zu machen als. Was das "Zählen von Schichten" betrifft, so brauchen wir das nicht, wir haben CLZ heutzutage in Hardware. – einpoklum

+0

Ich habe nicht wirklich genug Assembler, um zu helfen. Abgesehen davon, dass du Müll anbieten könntest, könntest du vielleicht retten;) –

+1

Das heißt, wenn du 'clz' machst, dann wandle diese Zahl in die maximale Potenz von zehn um, du musst nur eine kleine Anzahl von Tests ausprobieren, denke ich. Ich weiß nicht genug, um mehr zu helfen. –

1

Neuere x86-Chips unterstützen eine lzcnt-Anweisung, die Ihnen die Anzahl der freien Bits am Anfang einer Ganzzahl angibt. Sie können es eingebaute Compiler Funktionen zuzugreifen unter Verwendung von wie die folgenden (aus GCC):

unsigned short __builtin_ia32_lzcnt_16(unsigned short); 
unsigned int __builtin_ia32_lzcnt_u32(unsigned int); 
unsigned long long __builtin_ia32_lzcnt_u64 (unsigned long long); 

Sie dies mit jeder Ziffer beginnen, die untere und obere Grenze der ganzen Zahlen mit einer Verweistabelle von 640 Werten kombinieren könnten angeben, von 0-9, die mit der entsprechenden Anzahl von freien Bits beginnen. Tatsächlich könnten Sie Platz sparen, indem Sie den lzcnt Wert um 3 Stellen nach rechts verschieben; die Übereinstimmung mit den ersten Dezimalziffern ist immer noch eindeutig.

1

Mit einer lzcnt Anweisung können Sie eine Tabelle von Divisoren für jede Anzahl führender Null-Bits erstellen. Zum Beispiel für nicht signierte 64-Bit-Zahlen:

lz | range | div 
---+---------+---- 
64 | 0  | 1 
63 | 1  | 1 
62 | 2-3 | 1 
61 | 4-7 | 1 
60 | 8-15 | 1 
59 | 16-31 | 10 
58 | 32-63 | 10 
57 | 64-127 | 10 
56 | 128-255 | 100 
... 
0 | 9223372036854775808-18446744073709551615 | 1000000000000000000 

Dann wird die Berechnung:

leading_zero_bits = lzcnt(x); 
leading_digit = x/divisor_table[leading_zero_bits]; 
if (leading_digit >= 10) leading_digit = 1; 

Das Ergebnis der Division wird immer weniger als 20, so dass nur eine einfache Prüfung für Quotienten benötigt wird zwischen 10 und 19. Die Division durch eine Konstante kann ebenfalls optimiert werden.

+0

Sie müssen sicherstellen, dass sich die Tabelle in Ihrem L1-Cache befindet, damit dies schnell geht. – einpoklum

2

Die beste Option scheint durch vorberechnete Leistung von 10. Also, in Pseudo-Code zu kombinieren CLZ Ansatz und dividieren zu sein:

powers10=[1,1,1,1,10,10,10,10,100,100...]; // contains powers of 10 map to CLZ values 
int firstDigit(unsigned INT_TYPE a) { 
    if (a<10) return a; // one digit, screw it 
    int j=typesize(a)-clz(a); 
    if (j==3) return 1; // 10-16 hit this, return 1 
    int k=a/powers10[j]; 
    if (k>9) return 1; else return k; 
} 

typesize() Renditen 64 für long long, 32 für int und 16 für short int.

+0

Das sollte wirklich langsam sein, da es Lesen aus dem RAM erfordert. Wenn Sie es wiederholt ausführen, wird es etwas besser (L1 hoffentlich), aber ich bezweifle immer noch sehr, dass das der schnellste Weg ist, es zu tun. – einpoklum

+0

@einpoklum Nun, Hardware-weise, es ist billiger, die ganze Tabelle in L1 stopfen als Berechnung der Zehnerpotenz, die benötigt wird, um zu teilen, und Dividieren ist immer teurer als Multiplizieren, vor allem durch konstant, so durch 10 wiederholt wird teurer sein als einmaliges Teilen mit einem RAM-Zugriff. Außerdem können die 'powers10' im Stack liegen, da sie ziemlich klein sind, nur 64x8 = 512 Bytes, und der Stack befindet sich wahrscheinlich auf L1 auf jedem System mit L1-Cache. – Vesper

+0

Es gibt Alternativen ohne die wiederholte Teilung durch 10, die kein Lesen von RAM erfordern - z. Exponentiation von 10, mit nur Multiplikationen, die billig sind. Wie für L1 - Sie könnten Ihren Tisch dort halten, oder Sie könnten nicht. – einpoklum