c++ Binomialkoeffizient berechnen und Überlauf verhindern

3 Antworten

Die Standard-C++ Typen haben eine maximale Größe, und wenn du - wie in deinem Fall - sehr große Zahlen berechnen willst, die nichtmehr innerhalb diese Größe passen, dann kannst du auch einen Überlauf nicht verhindern, wenn du weiterhin diese Datentypen benutzt.

Abhilfe für dein Problem schaffen sog. Big Integer Librarys. Eine bekannte und weit-verbreitete solche Library ist Gnu MP https://gmplib.org/ - diese wird unter anderem auch für die Boost.Multiprecision-Lib genutzt (falls du Boost verwenden willst)


chaostheorie314 
Beitragsersteller
 15.12.2013, 13:13

Also das Ergebnis von 66 über 33 passt definitiv in den Wertebereich des long long. Daher muss das auch gehen. Der Überlauf passiert irgendwann bei der Schleife bei b = (...). Also muss ich den Wert von b während der Rechnung irgendwie kleiner halten, damit kein Überlauf passiert.

0
DoTheBounce  15.12.2013, 13:22
@chaostheorie314

66 über 33 ist ~7 * 10^18 und die Grenze für long long ist ~9 * 10^18. Das heißt ganz offensichtlich, dass das Zwischenergebnis, bevor durch i geteilt wird, nichtmehr in den Wertebereich passt.

Du hast also 2 Möglichkeiten:

  • Du nimmst eine Library
  • Du passt deinen Algorithmus an, der die Zwischenergebnisse kleiner hält. Der offensichtlichste Schritt wäre die Berechnung anders zu klammern:

    b =  b *  (( n - k + i ) / i) ;
    

Damit sind die Zwischenwerte kleiner. Solange du diesen Algorithmus verwendest, glaube ich geht auch nicht viel mehr (auf den ersten Blick).

Du könntest in deinem speziellen Fall auch unsigned long long nehmen, das erhöht den Wertebereich nochmal, aber natürlich auch nicht unbegrenzt

0
chaostheorie314 
Beitragsersteller
 15.12.2013, 14:06
@DoTheBounce

Funktioniert leider auch nicht. Es ist eben (n - k + i) nicht restlos durch i teilbar, sondern nur (b * ( n - k + i )). Das führt bei deiner Klammermethode zu erheblichen Ungenauigkeiten durch die Rundungen.

Also mathematisch berechnet der Algorithmus ja

(n-k+1)/1 * (n-k+2)/2 * (n-k+3)/3 * ...

Dabei steht im Zähler ja eine Fakultät. Wenn n = 66, k = 23 ist das z.B.

(66-23+1)/1 * (66-23+2)/2 * (66-23+3)/3 * ...

= 43/1 * 44/2 * 45/3 * ...

Es ist 43 * 44 durch 2 teilbar, ( (43 * 44)/2 ) * 45 restlos durch 3 teilbar bzw. 434445 durch 6 teilbar usw., weswegen der Algorithmus auch nirgends runden muss und dadurch keine Ungenauigkeiten auftreten.

Also n * ( n + 1 ) mit n gerade ist 2a * (2a + 1) = 4a^2 + 2a = 2(2a^2 + a) durch 2 teilbar. Mit n ungerade: (2a+1) * (2a + 2) = 4a^2 + 4a + 2a + 2 = 2(2a^2 + 3a + 1) durch 2 teilbar.

Dass n * (n+1) * (n+2) durch 6 teilbar ist, lässt sich genauso zeigen usw.

0

Die Zahlen der Fakultät explodieren dir ziemlich schnell im Programm. Ein anderer Ansatz wäre es den Bruch zuerst zu kürzen um so die riesigen Zahlen zu vermeiden.

Beispiel binom(7,3)

    7!
-----------
3! * (7-3)!

=

 1*2*3*4*5*6*7
---------------
1*2*3 * 1*2*3*4

=

5*7
---
 1

Und damit haben wir nurnoch die Zahlen 5*7 übrig anstelle von 5040 / 144.

Ist zwar ein wenig aufwendiger zu programmieren aber dadurch bist du nurnoch durch den Wert des Ergebnis nach oben hin beschränkt.

Das Ergebnis zu binom(66,33) wäre hierbei

37*41*43*47*17*53*7*19*59*61*7*2*65*6 = 7219428434016265740

Anstelle von

             5,443449390......e+92
---------------------------------------------
8,683317618......e+36 * 8,683317618......e+36 

chaostheorie314 
Beitragsersteller
 15.12.2013, 19:26

Okay, das hat nicht geholfen, weil wir diesen bestimmten Algorithmus verwenden mussten. Ich habe es allerdings gelöst! (yeah! Saß lange dran :P) Und zwar so :

        if ( b % i == 0 )
            b = (b/i) * (n-k) + b ;
        else if ( (n-k) % i == 0 )
            b = b * ((n-k)/i) + b ;
        else
            b = (b * (n - k))/i + b ;

Das hält die Zwischenergebnisse kleiner, so dass 66 über 33 noch korrekt berechnet werden kann.

0

Wahrscheinlich wird der Fehler bei:

( b * ( n - k + i ) )

auftreten.

Gibt es ein bestimmten Grund warum dies so gerechnet werden sollte?

b = (n - k + i)  * (b / i);

Sollte das selbe Ergebnis geben wie:

b = ( b * ( n - k + i ) ) / i ;

Und du verhinderst das große Zwischenergebnis aus obersten Beispiel.

Woher ich das weiß:Berufserfahrung – Softwareentwickler/Projektleiter seit 2012

chaostheorie314 
Beitragsersteller
 15.12.2013, 13:54

Das funktioniert leider nicht. Deine Methode b = (n-k+i) * (b/i) schränkt die Genauigkeit ein, da b nicht immer durch i teilbar ist und (b/i) gerundet wird. b * (n-k+i) ist aber immer durch i restlos teilbar.

0