Don P: Nächste Zweierpotenz berechnen

Beitrag lesen

Hallo,

Die Dikussion um das beste Verfahren, die nächste größere Zweierpotenz für eine Zahl zu ermitteln, die keine Potenz von 2 ist, hat mich zu weiteren Experimenten angeregt.

Obwohl man mit dem einfachen Test (n-1)&n == 0 schnell ermitteln kann, ob n eine Potenz von 2 ist, war das leider wenig hilfreich zur Entwicklung eines schnellen Algorithmus', der ggf. nächst größere Zweierpotenz liefert.

Længlich hat gemessen, dass sein Algorithmus (PHP) um einiges schneller ist als meiner. Seiner braucht immer gerade so viele Schleifendurchläufe, wie die Zahl Binärstellen hat. Hier ist er in JavaScript:

  
var leanglich = function(zahl){        // Ermittelt die nächste Zweierpotenz nach Længlich.  
  
    var potenz = 1;                    // Wir fangen mit der kleinsten an  
  
    while (zahl > potenz)              // Solange sie noch zu klein ist...  
    {  
        potenz <<= 1;                  // ... versuchen wir die nächsthöhere  
    }  
    return potenz;  
}

Und hier meiner:

  
var donP = function(zahl) {            // Ermittelt die nächste Zweierpotenz nach Don P.  
  
  if (!((zahl - 1) & zahl)) { return zahl; }  // Für Zweierpotenzen gilt: (n-1)&n === 0  
  
  var shr = 1;                                // Anzahl Rechts-Shifts bis Zweierpotenz erreicht  
  
  while ((zahl - 1) & zahl) {                 // Solange keine Zweierpotenz erreicht ist:  
  
    zahl >>= 1;                               //  Verschiebung um 1 nach rechts,  
    shr++;                                    //  Anzahl Verschiebungen merken.  
  }  
  
  return zahl << shr;                         // Enprechend wieder zurückschieben.  
}

Obwohl hier oft weniger Schleifendurchläufe nötig sind, im Idealfall gar keiner, ist dieser Code um die Hälfte langsamer, wohl hauptsächlich wegen der vielen Berechnungen von (n-1)&n.

Hier nochmal Længlichs Messergebnisse (PHP) für 5 mal 60000 Berechnungen in sec:

1. Don P: 0.44359087944
   Længlich: 0.278346061707
2. Don P: 0.409577131271
   Længlich: 0.289813995361
3. Don P: 0.392857074738
   Længlich: 0.295516967773
4. Don P: 0.398221969604
   Længlich: 0.284239053726
5. Don P: 0.409832954407
   Længlich: 0.280338048935

Wie ich herausfand, gibt es aber einen noch schnelleren Weg (jedenfalls in JavaScript): Man bestimmt das höchste gesetzte Bit einer Zahl mit Hilfe eines geeigneten Näherungsverfahrens, das stets nur wenige Iterationen braucht:

  
var newton = function(zahl) {          // Ermittelt die nächste Zweierpotenz durch Näherungsverfahren.  
  
  var step = 32,                       // Schrittweite in Bits für nächste zu testende Zweierpotenz  
      test = 1 << 62;                  // Unsere größte zu testende Zweierpotenz sei 2^30  
  
  while (step && test>zahl) {          // während Schrittweite>0 und Testzahl ist größer  
  
    step >>= 1;                        //  Schrittweite halbieren,  
    test >>= step;                     //  mit nächst kleinerer Testzahl weitermachen.  
  
    while (step && test<zahl) {        //  während Schrittweite>0 und Testzahl ist kleiner  
  
      step >>= 1;                      //   Schrittweite halbieren,  
      test <<= step;                   //   mit nächst gößerer Testzahl weitermachen.  
    }  
  }  
  
  if (test<zahl) { return test << 1; } // Zahl ist keine Zweierpotenz => nächst größere zurückgeben  
  return test;                         // Zahl ist Zweierpotenz => diese zurückgeben  
}  

Hier ein paar typische Messergebnisse für jeweils 60000 Berechnungen (JavaScript) in ms:

1. 60000 Zahlen 1...60001
   Don P: 500
   Newton: 344
   Længlich: 375

2. 60000 Zahlen 89940000...90000000
   Don P: 781
   Newton: 344
   Længlich: 563

3. 60000 Zahlen 1073681823...1073741823
   Don P: 859
   Newton: 329
   Længlich: 610

Wie man sieht, ist der Geschwindigkeitsvorteil ist umso deutlicher, je größer die Zahlen werden. Das war zu erwarten, weil die Anzahl der Schleifendurchläufe bei den bisherigen Lösungen linear mit den Testzahlen steigt, während es beim Näherungsverfahren immer nur max. 6 Iterationen sind.

Gruß, Don P

--
sh:( fo:) ch:? rl:( br:] n4:~ ie:% mo:? va:{ js:) de:/ zu:] fl:( ss:| ls:&