erf-Funktion approximieren



  • Danke für die Antworten. Es ist jetzt klarer.

    Ich habe eine andere Lösung gefunden, womit ich nicht zweimal dieselbe Code Line habe, aber bin mir nicht sicher, ob das doch nicht irgendwelche Fehler verbergen kann:

    double ErrorFunction(double x)
    {  
    
      static const double a_1=0.3480242,
            a_2=-0.0958798,
            a_3=0.7478556,
            p=0.47047;
      double t=0;
    
      if (x < 0)
      {
        return -ErrorFunction(-x);
      }
    
      t = 1 / (1 + p*x);
    
      return 1 - (t*(a_1 + t*(a_2 + t*a_3)))*exp(-(x*x));
    }
    

    Ist es okay, wenn man im Unterprogramm selber nochmal das Unterprogramm selbst aufruft?

    Die t's habe ich auch rausgehoben, um das ganze nur auf 3 Multiplikationen zu beschränken.



  • C_Boy schrieb:

    Ist es okay, wenn man im Unterprogramm selber nochmal das Unterprogramm selbst aufruft?

    Das ist eine Rekursion.
    Viele finden das sogar sehr elegant!

    Mir gefällt Deine Lösung auch sehr gut. Jetzt lass aber noch die Makros verschwinden.

    Das mit dem t rausziehen ist übrigens super. Schau mal, wie der vom Compiler generierte Code zusammenschrumpft! Offensichtlich hilft das ungemein.

    Viel Erfolg!



  • @Wutz
    Folgendes extrem hässliches Makro habe ich neulich in der SDK von Nordic gefunden:

    #define APP_UART_FIFO_INIT(P_COMM_PARAMS, RX_BUF_SIZE, TX_BUF_SIZE, EVT_HANDLER, IRQ_PRIO, ERR_CODE) \
        do                                                                                             \
        {                                                                                              \
            uint16_t           APP_UART_UID = 0;                                                       \
            app_uart_buffers_t buffers;                                                                \
            static uint8_t     rx_buf[RX_BUF_SIZE];                                                    \
            static uint8_t     tx_buf[TX_BUF_SIZE];                                                    \
                                                                                                       \
            buffers.rx_buf      = rx_buf;                                                              \
            buffers.rx_buf_size = sizeof (rx_buf);                                                      \
            buffers.tx_buf      = tx_buf;                                                              \
            buffers.tx_buf_size = sizeof (tx_buf);                                                      \
            ERR_CODE = app_uart_init(P_COMM_PARAMS, &buffers, EVT_HANDLER, IRQ_PRIO, &APP_UART_UID);   \
    } while (0)
    

    https://github.com/Seeed-Studio/mbed_ble/blob/master/nRF51822/nordic/nrf-sdk/app_common/app_uart.h

    PS:
    Ich wäre froh wenn viele Leute deinen Rat beherzigen würden: Makros sind Scheiße.



  • Bitte ein Bit schrieb:

    PS:
    Ich wäre froh wenn viele Leute deinen Rat beherzigen würden: Makros sind Scheiße.

    Ach, das kann man so pauschal nicht sagen.

    Für einen Datumsschreiber und -Interpreter definiere ich mir ein paar Makros, die sich darum kümmern, einen bestimmten String in einen Buffer zu schreiben/ihn von einem Buffer zu lesen.

    Die Alternative mit Funktionen?
    Benötigt zunächst mal einen Haufen Parameter, die deklariert werden müssen.
    Die Fehlerbehandlung wird sehr schnell kompliziert, da nicht genug Speicher für die gegenwärtige Aktion nicht in einem Fehler, aber je nach Feld das Schreiben/Lesen von 0 zu einem Fehler führen soll (Tage und Monate sind nicht 0, aber Stunden, Minuten, Sekunden).
    Viel Code wird zwischen den verschiedenen Makros geteilt. In der derzeitigen Fassung kann ich diese Teile noch mal in einem separaten Makro abstrahieren. Mit einer Funktion habe ich noch mal zusätzlichen Overheadcode für praktisch keinen Nutzen. Gleiches gilt auch für Parametersetzung innerhalb von Makros - wenn ich einen zweistelligen Wert schreiben will, kann die Quelle eine Stunde, eine Minute oder eine Sekunde sein, sprich, nur das Feld ändert sich. Mit einem Makro definiere ich die Quellsetzung in zwei Zeilen; selbst mit geinlineten Funktionen komme ich auf mindestens +5 Zeilen pro Ersetzung.

    Wenn ich auf den modularen, semi-automatischen Ansatz verzichte, kann ich Schreiber/Leser nicht mehr in weniger als 15 Zeilen definieren.

    Irgendwer voreiliges schrieb:

    Nimm doch strftime

    Die Funktion hat kaputte Fehlererkennung, ist lokalenabhängig und damit auf globale RW-Ressourcen angewiesen (sprich, entweder sind diese gelockt, was scheiße ist, oder sie sind nicht gelockt, was noch sehr viel mehr scheiße ist), und benötigt ein zusätzliches Byte für die NUL-terminierung. Ich kann mit Abstand keine Funktion nennen, die ich für kaputter erachte. Und das schließt mktime mit ein.

    Ja, Makros werden oft für Scheißpraktiken missbraucht. Makros sind oft scheiße. Aber nicht immer.



  • Ohje, Makros und static, das kann nicht gut gehen.

    Ist absoluter Schwachsinn, denn ruft man das Makro z.B. in einer Schleife auf,
    wird beim jeweils folgenden Aufruf z.B. für eine 2.UART die Initialisierung mit den Hinterlassenschaften der 1.UART durchgeführt, was sicher nicht gewünscht ist:

    int x;
    P_COMM_PARAMS uart[3];
    for(int i=0;i<3;i++)
    {
    APP_UART_FIFO_INIT(uart[i], 3, 3, NULL, 0, x); <- hier knallts dann bei i>0
    }
    

    Man muss also wissen, dass man dieses Makro nicht in Schleifen verwenden darf - purer Nonsens.
    Ebenso muss man "wissen", dass bei Verwendung des Makros

    APP_UART_FIFO_INIT(p1,p2,p3,p4,p5,p6);
    

    p2 und p3 Compilezeitkonstanten sein müssen (wegen static); ignoriert man die Compilerwarnungen - was insbesondere Ahnungslose gern tun um das Programm "erstmal" lauffähig zu machen - gibts dann Überraschungen zur Laufzeit - bestenfalls Abstürze.
    Weiterhin funktioniert die o.g. Variante der for-Schleife bei einem Durchlauf, bei mehr aber nicht mehr -> Auswirkungen von static-Verwendung.

    Ebenso jammern diese Hardwarefrickler immer rum, dass sie zu wenig Speicher hätten - und hier verwenden sie kostbaren static Speicher aus Bequemlichkeit und Unwissen - Deppen halt, denn bei jeder Referenz dieses Makros werden RX_BUF_SIZE+TX_BUF_SIZE Bytes static-Speicher unwiederbringlich verbraten - wobei zu befürchten ist, dass der ganze Code von solchem Unsinn durchsetzt ist und nicht immer ist solch Unsinn sofort zu erkennen wie hier.

    Professionell würde man sowas C99 konform mit compound literals machen, z.B.

    int x  = app_uart_init(P_COMM_PARAMS1, &(app_uart_buffers_t){0}, EVT_HANDLER, IRQ_PRIO, &(int8_t){0});
    int x1 = app_uart_init(P_COMM_PARAMS2, &(app_uart_buffers_t){0}, EVT_HANDLER, IRQ_PRIO, &(int8_t){0});
    

    So hat jede UART ihren eigenen - immer 0 initialisierten - exklusiven Speicher,
    ohne sich den static Speicher zuzumüllen, und um das Wegräumen dieses Speichers braucht man sich auch nicht zu kümmern, da free() hier ja entfällt.

    Fehlt bloß noch zu wissen, welche Hardware ich zukünftig meiden muss, um nicht in Abhängigkeit von solchem Schrottcode zu kommen.



  • Ich hätte da noch eine Frage bitte: Ich nehme an, dass es immer optimal ist bereits in eine Bibliothek implementierte Funktionen wie z.B. scanf() auf Rückgabewerte zu prüfen.
    Das Programm soll sich schließen, wenn ich keine Zahlen eingebe. Ich habe das mal ein zwei verschiedenen Versionen gemacht.
    (Ich benutze den VS 2017 Compiler, darum scanf_s)

    In der 1. Version meines main-Programms habe ich exit() benutzt. Aber man sollte goto, labels, exit, break und diese Sachen ja möglichst meiden, da diese die Struktur im Prinzip kaputt machen. Ist das so richtig gedacht?

    Darum habe ich dann eine 2. Version angefertigt, um das Program auf normale Art und Weise beenden zu lassen. Also das Programm läuft bis zum ender des main-Blocks.

    Ich vermute mal, dass die 2. Version die bessere Lösung ist. Aber gibts noch Verbesserungsmöglichkeiten bzw. etwas auszusetzen?

    main_v1:

    int main(void)
    {
      double x = 0;
    
      do
      {
        printf("Enter any value x to show erf(x): ");
    
        if (scanf_s("%lf", &x) != 1)
        {
          printf("error: invalid input\n");
          exit(EXIT_FAILURE);
        }
        else
        {
          printf("\n erf(x)=%f\n\n", ErrorFunction(x));
        }
    
      } while (x != 0);
    
      return 0;
    }
    

    main_v2:

    int main(void)
    {
      double x = 0;
      int i = 0; 
    
      do
      {
        printf("Enter any value x to show erf(x): ");
        i = scanf_s("%lf", &x);
    
        if (i == 1)
        {
          printf("\n erf(x)=%f\n\n", ErrorFunction(x));
        }
    
      } while (x != 0 && i == 1);
    
      if (i != 1)
      {
        printf("error: invalid input\n");
      }
    
      return 0;
    }
    


  • Aus der main-Funktion heraus kannst du auch einfach

    return EXIT_FAILURE;
    

    schreiben.



  • Ich finde deine v1 schöner. Nach dem potentiell fehlerauslösenden Call gleich der Check und bei Fehler sofort raus aus der Funktion (also z.B. break, return, exit).

    Was aber unschön ist, ist dein "else"-Zweig. Das else ist hier überflüssig. Wenn dein scanf nicht erfolgreich ist, springst du ja schon raus. Daher lass das else weg. Dadurch verringert sich auch der Einzug für das printf der ErrorFunction.



  • Ahh, stimmt. Danke euch!

    v3:

    int main(void)
    {
      double x = 0;
    
      do
      {
        printf("Enter any value x to show erf(x): ");
    
        if (scanf_s("%lf", &x) != 1)
        {
          printf("error: invalid input\n");
          return EXIT_FAILURE;
        }
    
        printf("\n erf(x)=%f\n\n", ErrorFunction(x));
      } while (x != 0);
    
      return 0;
    }
    

    D.h. v3 ist am "besten"? Ich dachte eben, dass man exit, goto, break etc. eher meiden sollte, da sie die Struktur des Programms kaputt machen oder halt "zerreißen" so wie die Macros.


  • Mod

    C_Boy schrieb:

    Aber ich dachte halt, dass man exit, goto, break etc. eher meiden sollte, da sie die Struktur des Programms kaputt machen oder halt "zerreißen" so wie die Macros.

    Was sagt ihr dazu?

    Das ist durchaus richtig.

    So ein Abbruch mitten im Programm ist oft auch eine maßlose Überschreitung von Zuständigkeiten, der verhindert, dass Code wiederverwendet werden kann. Beispielsweise ist eine Eingabeprüfung, die im Fehlerfall das ganze Programm unterbricht ziemlich unflexibel. Da bei dir derzeit sowieso keine Trennung von Aufgaben stattfindet, sondern das gesamte Programm aus einem einzigen Stück besteht, macht es aber bei dir derzeit noch keinen Unterschied.


Anmelden zum Antworten