floating point error



  • Welchen meinst du? Ich habe es mit WinDbg versucht, bin da aber nicht so ganz klar gekommen. Wie kann ich denn mit dem Debuggen. Eine einfache Erklärung hierzu konnte ich über Google einfach nicht finden.
    Tschö, nullplan002





  • Dank dir, aber...
    also ich habe mich so ziemlich durch alles durchgebissen, aber irgendwas funzt nicht. Durchgecheckt... aha, der bekommt beim Binomialkoeffizien (Funktion ovr) keinen int-Wert raus. Ich musste erst alles, was rekursiv war, iterativ machen (also alles mit for-Schliefen) und die Ergebnis-Variable vom Typ "unsigned long long int" (kein Tippfehler) machen. Außerdem entlastete ich den Stack, indem ich einige Fakultätsberechnungen, also alle, in die Funktionen, wo sie benötigt wurden, einbaute. Sah dann so aus:

    #include <stdio.h>
    #include <math.h>
    
    float b(unsigned int,float,unsigned int);
    float sumb(unsigned int,float,unsigned int);
    
    int main()
    {
      unsigned int n;
      float p;
      unsigned int k;
      int choice;
      int z = 0;
      printf ("Dieses Programm berechnet die (aufsummierte) binomiale Wahrscheinlichkeit für eine\n");
      printf ("Bernoulli-Kette der Länge n und der Trefferwahrscheinlichkeit p mit der Trefferzahl k\n\n");
      printf ("n = ");
      scanf("%d",&n);
      printf ("\np = ");
      scanf("%f",&p);
      printf("\nk = ");
      scanf("%d",&k);
      do
      {
        printf("\n1. einfache oder\n2. aufsummierte Wahrscheinlichkeit? ");
        scanf("%d",&choice);
        switch (choice)
        {
          case 1: 
          {	
            printf ("b(%d,%f,%d) = %f",
    				n,p,k,
    				b(n,p,k));
            z = 1;
    	  break;
    	}
    	case 2: 
    	{
    	  printf ("B(%d,%f,%d) = %f",
    			n,p,k,
    			sumb(n,p,k));
    	  z = 1;
    	  break;
    	}
    	default: printf("falsche Eingabe!!!");
        }
      } while (z != 1);
      return 0;
    }
    
    float b(unsigned int n,float p,unsigned int k)
    {
    	if ((k > n) || (p > 1) || (p < 0)) return -1;
    	float erg = 1;
    	int i = 1;
    	unsigned long long int bla1=1 , bla2=1 , bla3=1;
    	for(; i <= n; i++)
    	{
    		bla1 = bla1 * i;
    	}
    	for (i = 1; i <= k; i++)
    	{
    		bla2 = bla2 * i;
    	}
    	for (i = 1; i <= (n-k); i++)
    	{
    		bla3 = bla3 * i;
    	}
    	erg = bla1 / bla2 / bla3;
    	erg *= exp(k * log(p));
    	erg *= exp((n-k) * log(1-p));
    	return erg;
    }
    
    float sumb(unsigned int n,float p, unsigned int k)
    {
    	float erg;
    	int i = 0;
    	for (; i<=k; i++)
    	{
    		erg = b(n,p,i);
    	}
    	return erg;
    }
    

    Ich lies das Programm dann unter GDB durchlaufen. Breakpoint vor die erste Schleife in b, normaler Durchlauf bis dahin, dann immer durchsteppen und zwischen drin immer mal wieder den Wert von bla1, 2 und 3 abprüfen.
    Ergebnis nach erg = bla1 / bla2 / bla3; :

    • n = 5
    • k = 3
    • erg = 10

    Ok, das mit 5 über 3 = 10 sieht ja noch ganz gut aus. Nächster Test:

    • n = 50
    • k = 47
    • erg = 0

    Öhm, also 50 über 47 ist ja eigentlich nicht 0, sondern 19600. Hm, was haben wir da falsch gemacht? Idee: Zu kleine Speichergröße für erg. Nur, wie erhöht man die Speichergröße? Nächster Test: Alles mit double.
    Öh, was ist denn jetzt passiert? 😕 😮 Nach Zeile 69 (erg = bla1 / bla2 / bla3) folgender Status:

    • n = 50
    • k = 47

    Soweit stimmt's, aber

    • p = 9.01e-314
    • erg = 0

    ist nicht das erwünschte Egebnis. Wo hab ich denn je bei der Wahrscheinlichkeit rumgefummelt? Eigentlich nie. Warum wurde die Variable dann verändert? Noch mal von vorne und diesmal durchsteppen.
    Breakpoint auf Zeile 24 gesetzt, Programm anrollen lassen. Im Prinzip direkt nach dem Einlesen: p = 9.01e-314. 😡 What the ***** is going on here??? 😕 Letzter Versuch: Gleich nach dem Einlesen, ohne irgendeine andere Anweisung zuzulassen das Ergebnis abprüfen:
    Breakpoint auf Zeile 20, los geht's.AHA: p hat gleich nach dem Einlesen diesen verschrobenen Wert. Wie kommt der da hin? Oder viel wichtiger: Wie krieg ich den da wieder weg?
    Tschö, nullplan002



  • Ähm, falls ihr euch wundert: p sollte eigentlich den Wert 0.95 haben. Oder etwas in der Nähe.



  • (double)bla1 / bla2 / bla3;

    3/4 ist nicht 0.75, sondern 0, da bei int/int integerarithmetik zieht.
    bei double/int jedoch werden beide zu double konvertiert.



  • c.rackwitz schrieb:

    (double)bla1 / bla2 / bla3;

    3/4 ist nicht 0.75, sondern 0, da bei int/int integerarithmetik zieht.
    bei double/int jedoch werden beide zu double konvertiert.

    double wird zu double konvertiert? macht sinn

    SCNR 😃



  • 🙄 kritik zur kenntnis genommen und verstanden.
    bedenke: ob eine konvertierung von double nach double irgendwas bewirkt oder nicht, haengt nicht von der logik ab, sondern von der software. sinnvollerweise passiert da nichts, aber in manchen sprachen kann man den cast-operator ueberladen (nur in c eben nicht *g*) und ganz kranke sachen veranstalten, selbst bei NO-OP-casts.

    @OP: ich weiss ja nicht wie du debuggst, aber integer division bugs findet man normalerweise beim debuggen, weil man sich *schrittweise* durchs programm bewegt und dabei alle angesprochenen werte beobachtet. naja, mit der praxis kommt die erfahrung.



  • Es ging um die bla-Variablen, die alle Integer sind. Danke für den Tipp, ich teste mal... äa, tja, erg ist an der kritischen Stelle immer noch 0. Und weil ich danach nur noch multipliziere bleibt auch das endergebnis 0. Aber danke für den Anstoß, ich teste mal was anderes... Hmm, also ich hab mal alle bla's auf double gecastet, während der Division. jetzt kommt da irgendwas von wegen 0.14 raus. Halt mal, wieso zum Henker ist 47! eigentlich größer als 50!. Geht ja gar nicht!!!! Mal gucken... Hä? i ist zum besagten Zeitpunkt 23. Das legt die Vermutung nahe, das irgendwas mit der Berechnung von 50! nicht stimmt. Nach MS-CALC ist 50! ≈ 3,041e+64. Wat macht der da jetzt falsch? Och nö... ihr wollt doch nicht von mir, dass ich 50 Schleifendurchläufe überwache, oder? Na gut... Hm bei 16 ist noch alles OK, bei 22 stimmt es nicht mehr ganz. Danach gerät es völlig aus dem Ruder und bla1 bekommt total interseeante Werte, nur nicht die Fakultät von i. 😕 ⚠ ⚠ Kopf überhitzt. Ich glaub, ich mach erstmal Pause bis morgen, sonst kann man meine Gehirnsreste vom Monitor kratzen, weil mein Kopf explodiert ist.
    Tschö, nullplan



  • mit long long int (64 bit) hast du etwa 19 dezimalstellen, was nur bis 21! ueberhaupt sinn macht.
    bei allen groesseren fakultaeten bekommst du einen integer overflow. praktisch heisst das, dass auf auf die groesste darstellbare zahl die kleinste darstellbare zahl folgt.

    mit fliesskommazahlen solltest du besser rechnen koennen, aber auch hier hast du irgendwann keine ausreichende genauigkeit mehr fuer sehr kleine aenderungen am wert.



  • Die Frage ist: Passen die Fakultäten von 50 oder gar 100 in einen double? Wenn ich ein long double versuche, schmiert mir der Compiler mit der Meldung ab: long double? Kenn ich nicht. Außer double gehen mir aber die Fließkommatypen aus. Gibt es sowas wie "unsigned long double"? Das wäre enorm hilfreich.
    P.S.: Ja, manchmal muss man auch Aussagen für n = 100 treffen können. Wie der Taschenrechner 100 über 95 berechnet, weis ich nicht, aber im Normalfall kommt der grad mal bis 69! (für Nichtwisser: 100 über 95 = 100! / 95! / 5!). Hm, soweit komm ich aber nicht mal. Moment, Bruder sprach mal von einem Problem mit Fakultäten, weil bei 21! Ende ist mit 32-Bit-Registern. Ich werd ihn mal fragen. Er ist das Problem schließlich in Assembler umgangen, da werd ich das doch in C hinkriegen.
    Till Monday (da dürfte er zurück sein),
    nullplan



  • Hat sich erledigt. Seit neuestem nimmt der Compiler "long double" doch an. Damit hat sich das Speicherproblem erledigt.


Anmelden zum Antworten