Fragen zu 128-Bit Integer



  • Schlitzauge schrieb:

    Ich möchte aber später die Wahl haben. Insb. wenn ich es mit OpenMP kombiniere, möchte ich eher zu printf() greifen, als zu std::cout.

    Warum? prtinf hat keinerlei Vorteile, sondern nur Nachteile. Wie du siehst ist ein typsicheres printf mit cout sehr schnell gebaut. Vergiss dabei nicht, dass du mit cout auch eigene Typen ausgeben kannst.



  • Naja, die iostream Bibliothek ist sicherlich Geschmackssache.
    Ich würde aber auch eher was komplett selbst bauen als auf printf setzen.



  • Wenn ich die Wahl hätte, nehme ich bevorzugt C++ und damit auch std::cout.
    Es scheint dennoch eine Grenze des Machbaren zu sein und es interessiert mich brennend das "Unmögliche" irgendwie möglich zu machen.

    Irgendwie gefällt mir die Möglichkeit mit .c_str() dann doch am ehesten.
    Lieber wäre mir eine direkte Nutzung der 128-Bit-Datentypen oder meinetwegen auch mit einfacher Funktions-Syntax (also Funktion mit lediglich der Variablen als Parameter, kein extra temporäres Array oder Bufferlength). Zu dumm, dass printf() nur Zeiger annimt. Einfach Call-by-Value scheint ja nicht möglich zu sein.



  • Was mir nur spanisch vorkommt.
    Wie Ihr auch sehen konntet.
    Rufe ich meine Funktionen in separaten printf() auf, funktionieren sie.
    Nur bei dem Punkt, wo ich bei printf() mehrfach die Funktionen aufrufe, gehts nicht mehr.



  • Meinst du mit deiner kaputten Versdion, die wir kritisiert haben?



  • Ja, meine ich.
    Folgendes:

    cout << "\n+Mit printf(,) UND prn_int128():\n----------------------------------------------------\n";
    printf("x: %s\n",prn_int128(1024));                          //geht
    printf("x: %s, y: %s\n",prn_int128(2048),prn_int128(4096));  //geht nicht
    

    Output:

    +Mit printf(,) UND prn_int128():
    ----------------------------------------------------
    x: 1024
    x: 2048, y: 2048
    

    Ebenso:

    cout << "\n+Mit printf(,) UND [U]INT128ToCSTR(): (EINZELN)\n----------------------------------------------------\n";
    printf("INT128-MIN: %s\n",INT128ToCSTR(INT128_MIN));      //geht
    printf("INT128-MAX:  %s\n",INT128ToCSTR(INT128_MAX));     //geht
    printf("UINT128-MIN: %s\n",UINT128ToCSTR(UINT128_MIN));   //geht
    printf("UINT128-MAX: %s\n",UINT128ToCSTR(UINT128_MAX));   //geht
    
    cout << "\n+Mit printf(,) UND [U]INT128ToCSTR(): (ZUSAMMEN)\n----------------------------------------------------\n";
    //geht nicht
    printf("INT128-MIN:%s\nINT128-MAX:%s\nUINT128-MIN:%s\nUINT128-MAX:%s\n",INT128ToCSTR(INT128_MIN),INT128ToCSTR(INT128_MAX),UINT128ToCSTR(UINT128_MIN),UINT128ToCSTR(UINT128_MAX));
    

    Output:

    +Mit printf(,) UND [U]INT128ToCSTR(): (EINZELN)
    ----------------------------------------------------
    INT128-MIN: -170141183460469231731687303715884105728
    INT128-MAX:  170141183460469231731687303715884105727
    UINT128-MIN: 0
    UINT128-MAX: 340282366920938463463374607431768211455
    
    +Mit printf(,) UND [U]INT128ToCSTR(): (ZUSAMMEN)
    ----------------------------------------------------
    INT128-MIN:-170141183460469231731687303715884105728
    INT128-MAX:-170141183460469231731687303715884105728
    UINT128-MIN:0
    UINT128-MAX:-170141183460469231731687303715884105728
    

    Da sieht man, dass es EINZELN jeweils funktioniert. Die richtigen Werte werden zurückgegeben, printf() übergeben und printf() gibts aus.
    Nur bei Mehrfachanwendung bei printf() nicht.



  • Das ist undefiniertes Verhalten. Vermutlich wird dir der Stack einfach überschrieben, deshalb kommst du an den alten Wert nicht mehr ran.



  • Na, ob das Verhalten den Optimizer überlebt? Es ist reine Glückssache, dass du damit überhaupt einen der beiden formatierten Werte bekommst; vom Standard gedeckt ist das auf keinen Fall. Dass es dieses Verhalten an den Tag legt, wird daran liegen, dass der Stack nach unten wächst und der Buffer in der Funktion länger als 5 + 3 * sizeof(char*) - 16 + was auch immer printf intern auf den Stack legt Byte ist. Häng mal noch ein Dutzend weitere Parameter an printf ran, mit einiger Wahrscheinlichkeit kriegst du dann auch diesen Wert nicht mehr korrekt wiedergegeben.

    Natürlich kann es auch an anderen Dingen scheitern - eine neue libc kann in printf mehr auf den Stack legen lassen, der Optimizer kann sich entscheiden, Parameter in Registern herumzureichen, das Betriebssystem kann dazwischen kommen und alles jenseits des Stackpointers wieder einsammeln, solche Dinge. Man kann an diesem Beispiel sehen, dass "es funktioniert bei mir" nicht auch "das ist so richtig" bedeutet.

    Wenn du den Kram unbedingt in printf stopfen willst, kann man das beispielsweise so machen:

    template<typename T>
    std::string to_string(T const &t) {
      std::ostringstream fmt;
      fmt << t;
      return fmt.str();
    }
    
    printf("%s\n", to_string(dein_int128).c_str());
    

    Und wenn dir die Syntax partout nicht gefällt, kannst du ein Makro drumherumschreiben, etwa

    #define prn_int128(x) to_string(x).c_str()
    

    ...wobei das eigentlich schon wieder ziemlich schlechter Stil ist. Allerdings ist das ganze Vorhaben ziemlich zweifelhaft, so dass es daran nun auch nicht mehr scheitern sollte.



  • Ja, meine ich.

    Genau das Problem, was wir kritisiert haben.
    Deine Funktion holt sich Speicher und schreibt den String hinein. Am Ende der Funktion wird der Speicher freigegeben, du gibst aber einen Zeiger auf den Speicher, der dir nicht mehr gehört, zurück.
    In deinen ersten Beispiel hatte durch Glück keine andere Funktion Interesse an diesem Speicherbereich, deswegen klappt es auch.
    In dem zweiten Beispiel wird die Funktion zuerst korrekt mir 4096 ausgeführt und "4096" wird in den Speicher geschrieben. Dann wird die Funktion mit 2048 aufgerufen, die logischerweise an dem selben Bereich Interesse zeigt, und der Wert wird überschrieben. Jetzt hast du 2 Pointer auf den exakt gleichen Speicherbereich, in dem natürlich der Wert des zweiten Aufrufs steht.

    Deswegen musst du a.) einen std::string nehmen, denn der verwaltet seinen eigenen Speicher und keiner kann ihm reinpfuschen ... oder b.) du übergibst per Parameter einen Buffer, in den die Funktion schreiben soll. Wenn du für jeden Aufruf pro Statement einen eigenen Buffer benutzt dann gibt es auch da keine Kollissionen.

    Jetzt verständlich?

    Na, ob das Verhalten den Optimizer überlebt? Es ist reine Glückssache, dass du damit überhaupt einen der beiden formatierten Werte bekommst; vom Standard gedeckt ist das auf keinen Fall. Dass es dieses Verhalten an den Tag legt, wird daran liegen, dass der Stack nach unten wächst und der Buffer in der Funktion länger als 5 + 3 * sizeof(char*) - 16 + was auch immer printf intern auf den Stack legt Byte ist. Häng mal noch ein Dutzend weitere Parameter an printf ran, mit einiger Wahrscheinlichkeit kriegst du dann auch diesen Wert nicht mehr korrekt wiedergegeben.

    std::stringstream bzw std::string alloziert auf dem Heap, deswegen zeigt sein Zeiger auch auf den Heap, nichts mit Stack. 😉



  • wie waere es mit sowas ?

    class myint
    {
       public:
          myint(int v) : m_value(v) { to_string(); }
    
          operator const char*(void) const { return buffer; }
       private:
          int m_value;
          char buffer[12]; // buffergroesse fuer __int128_t anpassen
          void to_string() { itoa(m_value, buffer, 10); }
    
    };
    
    #define i128(x) (const char*)myint(x) 
    #define PRIi128 "%s"
    
    int main()
    {
        int val = 12;
        printf("test: "PRIi128" -> "PRIi128"\n", i128(12), i128(24));
    }
    

    🙂

    habs der einfachhaltshalber mit nem int gemacht

    Meep Meep


  • Mod

    Meep Meep schrieb:

    habs der einfachhaltshalber mit nem int gemacht

    und vermutlich auch nicht getestet. Eine Ellipse führt sicher nicht dazu, dass ein Konvertierungsoperator aufgerufen wird.



  • camper schrieb:

    Meep Meep schrieb:

    habs der einfachhaltshalber mit nem int gemacht

    und vermutlich auch nicht getestet. Eine Ellipse führt sicher nicht dazu, dass ein Konvertierungsoperator aufgerufen wird.

    wie ? was ? wo ?
    welche ellipse ?


  • Mod

    printf("test: "PRIi128" -> "PRIi128"\n", i128(12), i128(24));
    
    int printf(const char* format, ...);
    


  • @camper:
    Die Ellipse nicht, aber dafür sollte doch das grauenhafte i128 Makro sorgen.
    Da steht dann im Endeffekt ja

    printf("test: %s -> %s\n", (const char*)myint(12), (const char*)myint(24));
    

    Abgesehen davon dass ich es grässlich finde... funktionieren müsste das IMO schon.



  • das PRIi128 hatte ich nur eingefuegt weil Schlitzauge sowas haben wollt 🙂



  • Sers,

    Jo, GROßEN DANK an alle. Die Lösungsvorschläge helfen mir sehr.
    Hab jetzt einige INT128ToCSTR-Funktionen erfolgreich implementiert.
    Bei drei Implementierungen komme ich allerdings nicht weiter.
    Ich möchte jetzt auch nicht diese drei parallel diskutieren, Zwecks Übersicht.
    Deshalb eine nach der anderen.

    Alle bisherigen Implementierungen, verwenden C++-spezifische Elemente.
    Lediglich der Output ist dann für printf() geeignet (was jetzt nicht weiter schlimm ist, insofern ich C++-fähige Compiler einsetze; => dann würde ich sowieso C++ bevorzugt coden).
    Es wurde aber auch eine reine C-Implementierung vorgeschlagen:

    const char* prn_int(int x)
    {
       static const int size = 20;
       static char buf[size];
       char *p = buf + size - 1;
       *p = '\0';
       --p;
       bool sign = false;
    
       if(x == 0)
       {
          *p = '0';
          return p;
       }
    
       if(x < 0)
       {
          sign = true;
          x *= -1;
       }
    
       while(x > 0)
       {
          if(x < 10)
          {
             *p = '0' + static_cast<char>(x);
             x = 0;
          }
          else
          {
             *p = '0' + static_cast<char>(x % 10);
             x /= 10;
          }
    
          --p;
       }
    
       if(sign == true)
          *p = '-';
       else
          ++p;
    
       return p;
    }
    

    Ich wollte jetzt gerne diese Funktion ebenso mit OUT-Parameter (also mittels übergebenen C-Array) realisieren, komme da aber irgendwie nicht weiter. Soll heißen, es funktioniert nicht.
    Wie würde denn eine richtige Implementierung aussehen? (wie gesagt, geht es hier um reines ANSI-C, kein C++)

    Grüße

    Schlitzauge 🙂



  • // size = buffergroesse
    const char* prn_int(int x, char *buf, int size)
    {
       char *p = buf + size - 1;
       *p = '\0';
       --p;
       bool sign = false;
    
       if(x == 0)
       {
          *p = '0';
          return p;
       }
    
       if(x < 0)
       {
          sign = true;
          x *= -1;
       }
    
       while(x > 0)
       {
          if(x < 10)
          {
             *p = '0' + static_cast<char>(x);
             x = 0;
          }
          else
          {
             *p = '0' + static_cast<char>(x % 10);
             x /= 10;
          }
    
          --p;
       }
    
       if(sign == true)
          *p = '-';
       else
          ++p;
    
       return p;
    }
    

    du musst natuerlich dafuer sorge tragen das dein buffer gross genug ist

    Meep Meep



  • Ich hab den Code mal korrigiert, denn bei INT128_MIN kommt sonst "-0" heraus.

    Das sind die genauen 128-Bit-Grenzwerte:

    INT128_MIN: -170141183460469231731687303715884105728
    INT128_MAX:  170141183460469231731687303715884105727
    

    Wenn jetzt nach obigen Code x*=(-1) gerechnet wird, würde die Grenze von INT128_MAX gesprengt werden, wenn man der Funktion INT128_MIN übergeben würde.

    Hier meine verbesserte Version:

    const char* prn_int128(__int128_t x, char *buf, int size)
    {
      char *p = buf + size - 1;
      *p = '\0';
      --p;
      bool sign = false;
      bool isINT128_MIN = false;
    
      if(x == 0)
      {
        *p = '0';
        return p;
      }
      if(x < 0)
      {
        sign = true;
        if(x == INT128_MIN)
        {
          ++x;
          isINT128_MIN = true;
        }
        x *= -1;
      }
      while(x > 0)
      {
        if(x < 10)
        {
          *p = '0' + /*static_cast<char>*/(char)(x);
          x = 0;
        }
        else
        {
          if(isINT128_MIN == true)
          {
            *p = '8';
            isINT128_MIN = false;
          }
          else
          {
            *p = '0' + /*static_cast<char>*/(char)(x % 10);
          }
          x /= 10;
        }
        --p;
      }
    
      if(sign == true)
      {
        *p = '-';
      }
      else
      {
        ++p;
      }
      return p;
    }
    

    Die Korrektur gewährleistet, dass ausschließlich im Falle von x==INT128_MIN, die erste Ziffer als 8 gesetzt wird und zwar nur im Output.

    Jetzt habe ich die alte und neue Version mal getestet und es funktioniert.
    Allerdings gibt es nach wie vor Probleme mit der Funktion:

    char testbuffer[41]={NULL};
      cout << "\n+Mit printf() UND prn_128():\n----------------------------------------------------\n";
      printf("INT128_MIN: %s\n",prn_int128(INT128_MIN,testbuffer,41));     //geht
      printf("INT128_MAX:  %s\n",prn_int128(INT128_MAX,testbuffer,41));    //geht
      printf("INT128:      %s\n",prn_int128(0,testbuffer,41));             //geht
      printf("INT128:      %s\n",prn_int128(1024,testbuffer,41));          //geht
      printf("INT128:     %s\n\n",prn_int128(-1024,testbuffer,41));        //geht
      //geht nicht:
      printf("INT128_MIN: %s\nINT128_MAX:  %s\nINT128:      %s\n",prn_int128(INT128_MIN,testbuffer,41),prn_int128(INT128_MAX,testbuffer,41),prn_int128(1024,testbuffer,41));
    

    Output:

    +Mit printf() UND prn_128():
    ----------------------------------------------------
    INT128_MIN: -170141183460469231731687303715884105728
    INT128_MAX:  170141183460469231731687303715884105727
    INT128:      0
    INT128:      1024
    INT128:     -1024
    
    INT128_MIN: -170141183460469231731687303715884105728
    INT128_MAX:  170141183460469231731687303715884105728
    INT128:      5728
    

    Wie Ihr sehen könnt, funktionierts einzeln hervorragend, sowohl mit den Grenzwerten von 128-Bit signed Integer als auch mit sonstigen Werten zwischendrinn (Output: Zeilen 3-7).

    Sobald man die Funktion aber mehrfach in printf() verwenden möchte (Output: Zeilen 9-11), stimmt lediglich die erste Ausgabe, die darauffolgenden nicht mehr.
    Ich verwende zwar bei jedem der drei Funktionsaufrufe das gleiche char-Array, lasse dafür aber auch unterschiedliche Werte übergeben.

    Ich hab mir jetzt die Funktion schon etliche Male durchgeschaut und auch den Debugger mehrfach bemüht, sehe aber nicht, wo der Fehler liegt und wie ich es besser machen müsste. Wäre sehr dankbar für jeden Tipp bzw. Lösungsvorschlag.

    Grüße

    Schlitzauge 🙂


  • Mod

    Schlitzauge schrieb:

    Ich verwende zwar bei jedem der drei Funktionsaufrufe das gleiche char-Array,

    Das.

    hustbaer schrieb:

    @camper:
    Die Ellipse nicht, aber dafür sollte doch das grauenhafte i128 Makro sorgen.
    Da steht dann im Endeffekt ja

    printf("test: %s -> %s\n", (const char*)myint(12), (const char*)myint(24));
    

    Abgesehen davon dass ich es grässlich finde... funktionieren müsste das IMO schon.

    Ok, hätte nicht bloß quer lesen sollen... da der Bezeichner klein geschrieben wird, habe ich nicht an ein Makro gedacht.



  • Was wie was DAS?
    Verstehe nicht so recht.

    Meine Vermutung liegt zwar auch bei der Verwendung von nur einem char-Array, aber ehrlich gesagt möchte ich nicht für jeden printf()-Wert ein extra Array anlegen müssen. Ich verstehe nicht, warum es mit einem Array nicht funktionieren sollte.
    Wie genau geht printf() bei der Interpretation denn vor?

    Hätte denn wer nen Lösungsvorschlag, wie ich es auch mit einem char-Array lösen könnte? Ich bin langsam mit meinem Latein am Ende.
    Wie gesagt, ausschließlich bzgl. dieser C-Funktion. Lösungen und Implementierungen mit C++-Elementen habe ich schon zu Genüge (herzlichen THX nochmal! 🙂 ).

    Grüße

    Schlitzauge 🙂


Anmelden zum Antworten