Datentype (Design?) Problem / Brauche Rat



  • Hallo Forum,

    in meinen Projekt benutze ich eine Korrdinateneinheit bzw. zwei wenn man den Vorzeichenlosen Type mit einbezieht.
    Angefangen habe ich mit den Signed Type. Hatte meine eigene Klasse dazu erstellt mit den Passenden Operatoren.
    In laufe der Zeit kam dann der Unsigned Type und hier habe ich dann Angefangen zu schludern.
    "Ach ist nur an dieser Stelle, schnell ein typedef und gut ist.." Kennt wohl jeder. 🙂

    Das Projekt wuchs und wuchs und wie es kommen muss benutzte man den Unsigned Type bald genauso häufig wie den Signed.
    Ich werde wohl in den Sauren Apfel Beißen und das Überarbeiten sonnst fliegt mir das irgendwann um die Ohren.
    Naja lieber jetzt bei 50K Zeilen als Später bei >100K Zeilen ändern. 🙂

    Also folgende Eigenschaften sind an diese Einheit gestellt.
    1. Wertebereich signed -200000000 - 2000000000
    unsigned 0 - 4000000000
    Dieses muss immer Sichergestellt sein. Ob 16, 32, 64 oder 72fach-Veschränktes-Quanten Bit System

    2. Beim Streamen eine Kennung mitgegeben.
    Beispiel:

    <Rectangle  x="s10000" y="s-1736" w="u300" h="100"/>
    

    3. Gute Kommunikation mit Schnittstellen die int benutzen

    4. Wünschenswert währe das man direkt damit Rechnen kann.
    So etwa: SignedCord + SignedCord / 2 * UnsignedCord

    Jetzt die Frage.

    Wie würdet Ihr das Lösen.
    - Eigen Klasse mit allen Passenden Operator? Was geht was geht dabei nicht.
    - int oder long benutzen und beim Streamen Sonderlösung fahren?
    - oder ganz andere Ideen?

    Lichtlein


  • Mod

    Lichtlein schrieb:

    - Eigen Klasse mit allen Passenden Operator?

    Aber klar.

    Was geht was geht dabei nicht.

    Alles geht.

    Was du mit der Kennzeichnung beim Streamen meinst, versteh ich nicht. Werd mal konkreter.

    P.S.: Ich verstehe doch richtig, dass der Wertebereich genau 0 bis 4000000000 sein soll und nicht mindestens?



  • 1. Beim Stream Lesen geht es um eine Art eingebaute Sicherung.
    Wenn das Programm an der Stelle ein Signed Datentype erwartet was
    dann mit "s1000" s anfängt. Stellt das Programm fest das da kein 's'
    steht dann gibt es gleich ein Fehler.
    Beispiel:
    "u102" = unsigned
    "s102" = signed
    "d102" = degree

    Daten könnten/werden auch von anderen Programmen verwendet und erzeugt.
    Wahrscheinlich Passiert es auch mal das mit Hand drin Editiert wird.
    Und hier wollte ich möglichens Fehlverhalten schon im Vorfeld erkennen.
    ( Bin auch gerade am vorbereiten das mir ein Hilfstream Automatisch eine
    Dateiformat Dokumentation erstellt, dazu brauche ich dann auch die
    Unterschiedlichen Datentypen )

    2. Der Zahlenbereich ist eigentlich Willkürlich gesetzt. Wollte nicht 32 Bit
    Ausnutzen um nicht Gefahr zu laufen von Überläufen. Grade Zahlen sehen in
    einen Metrischen System auch besser aus. 🙂
    (Benutze eigentlich zur Zeit auch nur -150.000.000 - 150.000.000)

    Hoffe Deine Fragen Beantwortet zu haben.
    Lichtlein


  • Mod

    Lichtlein schrieb:

    2. Der Zahlenbereich ist eigentlich Willkürlich gesetzt. Wollte nicht 32 Bit
    Ausnutzen um nicht Gefahr zu laufen von Überläufen. Grade Zahlen sehen in
    einen Metrischen System auch besser aus. 🙂
    (Benutze eigentlich zur Zeit auch nur -150.000.000 - 150.000.000)

    Und was soll passieren, wenn ein Wert über 400000000 vorkommt?

    Ansonsten: Antwort bleibt die gleiche: Klasse mit überladenen Operatoren und passenden Konstruktoren.



  • Wenn die Klassen für verschiedene Typen weitgehend gleich aussehen, kannst du auch Templates einsetzen, um Codeduplizierung vorzubeugen.



  • Und was soll passieren, wenn ein Wert über 400000000 vorkommt?

    Es gibt zwei Wege wie Zahlen in das System kommen.
    1. Durch den Stream, das kann man Abfangen. Zb. Eine 64 Bit Variable zum
    einlesen Benutzen,dann Testen ob im Bereich liegt und wenn nicht: Exception.

    Für viel viel Später ist es mal Angedacht eine Höhere Auflösung im Programm zu benutzen. Um alte Daten zu benutzen wird jetzt schon am Anfang des Streams die Information hinterlegt wie die Koordinaten zu Interpretieren sind. Das wandelt dann der Parser Selbständig um.

    2. Durch User Aktionen, Werte die außerhalb des Bereiches sind, werden nicht angenommen.

    Ok, es gibt Programmfehler wo das Passieren kann. Aber da hätte ich ja meine Koordinaten Klasse in der das Überprüft werden kann ob immer ein Gültiger Wert eingetragen wird. Und dann die laufende Aktion mit einer Exception beenden.

    Übrigens, bei den Externen Operatoren gibt es ein Problem, das mir aufgefallen ist. Vielleicht kann das jemand nachvollziehen.

    Beispiel 1 ohne Fehler:

    struct Cord
    {
      const Cord& operator + (const Cord &Val)
      {
        Cord Tmp(*this);
        Tmp.mVar += Val.mVar;
        return *this;
      }
    };
    

    Beispiel 2 wie man es machen sollte. (Aber Fehler macht)

    struct Cord
    {
      const Cord& operator += (const Cord &Val)
      {
        mVar += Val.mVar;
        return *this;
      }
    };
    
    const Cord& operator + (const Cord &Val1, const Cord &Val2)
    {
      Cord Tmp(Val1);
      Tmp += Val2;
      return Tmp;
    }
    

    Was ist Passiert, Compelieren kann man das nur der Linken bringt Fehler.
    Nach einigen Überlegen bin ich dann drauf gekommen. Im Debug Mode Copeliere ich immer mit no_inline Flag. Jetzt wird der Externe Operator als Normale Funktion gesehen und da er im Header steht mehrfach erzeugt. Dieses reklammiert der Linker. Mache ich dann ein 'inline' vor dem operator dann Funktioniert alles.

    Ist das bei anderen auch so?

    Lichtlein



  • Wirf die Unterscheidung signes/unsigned raus und verwende int32_t oder int64_t .
    Gerade bei Koordinaten ist es immer schrecklich ekelhaft wenn man signed und unsigned mischt.

    Ja, es gibt Stellen wo negative Werte nicht entstehen können, und es gibt Stellen wo sie nicht reingetan (z.B. als Parameter übergeben) werden dürfen. Deswegen aber einen unsigned Typen zu verwenden führt zu einem von zwei möglichen Ausgängen:

    1: man hat überall tonnenweise Casts, verbunden mit Range-Checks und was-nicht-noch alles. Das Programmieren gestaltet sich äusserst lästig, da man immer nachdenken muss welche Checks/Casts man jetzt schreiben muss, damit auch genau das rauskommt was man haben möchte.

    2: man hat Code der an vielen Stellen nicht das tut was man eigentlich möchte.

    Was deine Anforderungen angeht:

    1. Wertebereich signed -200000000 - 2000000000
    unsigned 0 - 4000000000

    Nicht erfüllt bei int32_t , sollte aber nach dem was du später geschrieben hast egal sein.
    Erfüllt bei int64_t .

    3. Gute Kommunikation mit Schnittstellen die int benutzen

    Erfüllt (perfekt)

    4. Wünschenswert währe das man direkt damit Rechnen kann.

    Erfüllt (perfekt) (und wäre schreibt man ohne h ;))

    Beim Streamen eine Kennung mitgegeben.

    Ganz klar nicht erfüllt.
    Ich würde das aber in Kauf nehmen.

    Was das Schreiben der Streams angeht sehe ich zwei Möglichkeiten:

    1: Du schreibst einfach keine Kennung mehr. Wenn die anderen Programme die solche Datenfiles lesen/schreiben können damit klarkommen oder entsprechend angepasst werden können würde ich das ganz klar vorziehen.

    2: Du passt die Stream-Insert Operatoren/Funktionen aller Klassen an die nen intXX_t als Member enthalten. Möglichkeiten wären
    2.1: Wrapper-Klassen für die Stream-Insertion verwenden ala stream << StreamAsUnsigned(value) . Abgesehen von keine Kennung mehr Schreiben ist das mein Favorite, da du in der Wrapper Klasse für unsigned checken kannst ob der Wert auch wirklich nicht negativ ist.
    2.2: Überall wo "u" stehen soll vorher auf uintXX_t casten
    2.3: Das "s" bzw. "u" von Hand vorher reinshiften ala stream << "u" << value

    Beim Lesen der Streams gibt es im Prinzip die selben zwei Möglichkeiten:

    1: Du ignorierst die "s" und "u" einfach.

    2: Du modifizierst die Stream-Extraction Operatoren/Funktionen der Klassen die intXX_t enthalten. Auch hier gibt es im Prinzip wieder die 3 selben Varianten wie beim Schreiben.

    ----

    Wenn du trotzdem einen Typ suchst der quasi ja nachdem wie er "befüllt" bzw. initialisiert wurde sich als signed oder unsigned verhält folgende Frage:
    Was soll bei signed + unsigned rauskommen?
    Was bei signed - unsigned?
    Bei unsigned - signed?
    Bei unsigned - unsigned?
    *?
    /?
    %?

    Ich glaube nämlich nicht, dass du ein Set von Regeln findest, die dazu führen dass das Ergebnis auch immer den "Typ" hat den du brauchst.
    Und hat es den falschen "Typ", dann wird der falsche Prefix in deine Files geschrieben, und es knallt beim Einlesen.



  • hustbaer

    Wirf die Unterscheidung signes/unsigned raus und verwende int32_t oder int64_t.

    Ich gebe Dir in Deinen Punkten Vollkommen Recht. Genauso habe ich angefangen. Schnell hatte ich mir dann aber eine Koordinaten-klasse geschrieben. Nur die Signed ... Unsigned braucht man ja nicht....

    Was dann Passiert ist steht ja in meinem ersten Post.

    Begründung:

    Nimm ein Rechteckiges Papier, die Linke Seite ist -105cm , die Rechte Seite ist +105cm . Oben und Unten das gleiche.

    Das ist jetzt der Wertebereich -105 - +105.

    Der Anwender darf jetzt Beliebige Kreise drauf Zeichen.
    Wenn man den Radius als Wert benutzt kann man alles mit Integer Speichern.
    Radius darf aber nicht < 0 sein.

    Beim Durchmesser sieht das anders aus. Dort geht der Wert von 0 - 210cm.

    Gut, nimmt man Radius, alles schnell Definiert.
    X-Achse = Integer
    Y-Achse = Integer
    Radius = Integer

    Tage vergehen, Wochen vergehen ....

    Hee ich habe eine Gute Idee, lass den Anwender nicht nur mit der Maus ein
    Kreis malen sondern, weil Genauer, über eine Eingabemaske die Koordinaten selber eingeben und weil wir Heute den Anwender-Gedenk Tag haben, darf der Anwender die Koordinate als Radius oder als Durchmesser angeben können.

    Ups...

    ---------------------

    Ich hoffe ich konnte Dir zeigen worauf ich Hinaus will. Mir währe es auch lieber wenn ich nur mit int Arbeiten müsste. Es gibt aber einfach zu viel Stellen wo Unsigned benötigt wird.

    2.1: Wrapper-Klassen für die Stream-Insertion verwenden ala stream << StreamAsUnsigned(value)

    Hier muss ich sagen das es noch viel Einfacher währe weil das so aussieht:

    template< typename archiv >
    archiv& serialize( archiv &Archiv )
    {
      return Archiv.attribut( "X", mXAxis )
                   .attribut( "Y", mYAxis )
                   .attribut( "Fill", mFill, true);  // boolean mit default wert
    }
    

    Hier könnte man hergehen und schreiben.

    template< typename archiv >
    archiv& serialize( archiv &Archiv )
    {
      return Archiv.attributCordinate( "X", mXAxis )
                   .attributCordinate( "Y", mYAxis )
                   .attribut( "Fill", mFill, true);  // boolean mit default wert
    }
    

    Der Parser Steckt hier im Archiv drin.

    Lichtlein



  • Kreis malen sondern, weil Genauer, über eine Eingabemaske die Koordinaten selber eingeben und weil wir Heute den Anwender-Gedenk Tag haben, darf der Anwender die Koordinate als Radius oder als Durchmesser angeben können.

    Ups...

    ---------------------

    Ich hoffe ich konnte Dir zeigen worauf ich Hinaus will. Mir währe es auch lieber wenn ich nur mit int Arbeiten müsste. Es gibt aber einfach zu viel Stellen wo Unsigned benötigt wird.

    Nein, konntest du mir nicht zeigen.

    Ich verstehe nicht mal ansatzweise wieso man dazu einen unsigned Typen im Code bräuchte, bzw. wieso man dazu die s/u/d Prefixe in den Datenfiles (!) brauchen würde.
    Der User soll eingeben was er mag, speichern kannst du es z.B. immer als Durchmesser ohne Prefix. Bzw. wenn du keine ungeraden Durchmesser erlaubst immer als Radius ohne Prefix.

    Beim Einlesen kannst du ja nach wie vor die s/u/d Prefixe parsen, aber im restlichen Code sowie beim Schreiben neuer Datenfiles braucht es keine Unterscheidung zu geben.

    BTW: das mit dem Durchmesser verstehe ich (glaube ich) ja noch, "d10" bedeutet Durchmesser 10, "u10" würde bedeuten Durchmesser 20 (weil Radius 10). Richtig sowei? Nur was soll die Unterscheidung zwischen s10 und u10? Ich meine 10 ist 10... egal obs jetzt in einem signed oder unsigned Typen drinnen steht.

    EDIT: ne, nix, "d" bedeutet ja Grad (degree)... also hab' ich überhaupt keine Ahnung was das soll. *verwirr* 🙂 /EDIT

    Also entweder verstehe ich dein Problem nicht ganz, oder du machst dir das Leben viel zu schwer.



  • @hustbaer

    Ok ich Versuche es noch mal mit char (8 Bit) zu erklären. -128 - +128 ist besser vorstellbar als 32/64 Bit Ausgeschrieben. 😉

    Der User bekommt eine Arbeitsfläche mit dem Wertebereich -100 - +100.
    Ein Eingabefenster wird erstellt in den der User für den Kreis den Radius eingeben kann. Dieses Eingabefenster liefert einen Wert zurück, der -100 bis + 100 annehmen kann. Das ganze Passt dann in ein Char.

    Jetzt soll ein Eingabefenster erzeugt werden in den der User den Durchmesser eingeben kann. Zurück liefern muss das Fenster einen Wert, der 0 - 200 annehmen kann. Worin Speichert man das nun? In einen Char geht nicht. Der kann die Zahl 200 nicht Aufnehmen. (Der Trick mit dem Überlauf funktioniert nicht weil der Arbeitsbereich nicht -128 - +128 ist, sondern nur -100 - +100.

    Ich hoffe so wird es Deutlicher.

    Zu den Präfix:
    Für mein Programm ist das Tatsächlich nicht nötig. Es weiß ja was für Daten kommen sollten.
    Aber vielleicht nicht Jemand der nur die Datendatei vor sich liegen hat.

    Beispiel:

    <Enviroment  CordMin="-100" CordMax="100">
    <GaphicSet
      <Line X="100" Y="299" Durchmesser="100"/>
    </GraphicSet>
    [code]
    
    So die Datendatei liefert Dir mit, das es Koordinatenwerte gibt die einen Bereich abdecken.
    Das X und Y Koordinaten sind kann man sich auch noch vorstellen. 
    Was ist mit dem Durchmesser? Ist das auch ein Koordinaten Type? Was Passiert wenn ich da -10 Reinschreibe? Dürfte ich ja, da am Anfang der Datei Steht das Koordinaten -100 - + 100 Beinhalten dürfen?
    
    Also Bau ich da ein Prefixe ein.
    [code]
    <Enviroment>
     <Types>
      <CordSigned   Prefix="s" Min=(-100) Max(+100)/>
      <CordUnsigned Prefix="u" Min=(0) Max(100)/>
     </Types>
    <GaphicSet
      <Line X="s100" Y="s299" Durchmesser="u100"/>
    </GraphicSet>
    

    Nun ist die Datei Eindeutig. Der in die Datei anschaut weiß was in x und y drin stehen darf und er weiß was in Durchmesser stehen darf. Dafür ist das gedacht.

    Lichtlein



  • Ich verstehe dein Problem immer noch nicht.
    Du schreibst du brauchst den Wertebereich gar nicht.
    Also hast du auch keinen "200 geht nimmer in einen signed char" Fall.
    Also hast du auch kein Problem, ausser einem, das du dir selbst machst.
    Nimm int32_t oder int64_t und die Sache ist gegessen.

    Was die Prefixes angeht: die sind mMn. für genau nix.
    Dein 2. Datenfile ist nicht aussagekräftiger als dein 1. (EDIT: schlimmer noch: das 2. ist verwirrender als das 1., da Dinge vorkommen, die für nix gut sind, was für den User aber nicht sofort ersichtlich ist /EDIT)
    Das was daran nicht klar ist, ist ... was soll eine Line sein, die nur ein Koordinatenpaar (X, Y) hat? Geht die vom Ursprung aus? Oder ... ?
    Dass ein Wert der "Durchmesser" heisst wohl kaum negativ werden darf, sollte klar sein.
    Wenn nicht: dokumentier' es einfach. Ohne Doku wird keines der beiden Formate 100% klar sein.

    Und/oder schreib ein Schema für deine XML Files. Das bringt sicher mehr als komische Prefixe zu verwenden.



  • @hustbaer

    Schönen Dank erst mal für Deine Hartnäckigkeit. 🙂

    Nach langen Überlegen, und 6 Stunden Herumprobieren und Deinen Erklärungen, den ich ehrlich gesagt nicht wirklich was entgegen zusetzen habe, werde ich mir Überlegen wie ich Dein Vorschlag in meinen Programm einsetzen könnte.

    a.
    Alles mit int32 ersetzen.

    b.
    Eine Koordinatenklasse behalten die nur einen int32 operator hat oder

    c.
    ein typedef int32 tUnit definieren, so das man im Source gleich sieht das es
    sich um einen Koordinaten Type handelt.

    Was meinst Du was besser aussieht?

    Auf jeden Fall gibt das einiges an Arbeit. 😞

    Lichtlein



  • Lichtlein schrieb:

    (...) werde ich mir Überlegen wie ich Dein Vorschlag in meinen Programm einsetzen könnte.

    a.
    Alles mit int32 ersetzen.

    Würde ich nicht unbedingt machen. Vielleicht brauchst du irgendwann mal doch 64 Bit, dann ärgerst du dich grün und blau dass du nicht wenigstens nen Typedef gemacht hast.

    b.
    Eine Koordinatenklasse behalten die nur einen int32 operator hat oder

    c.
    ein typedef int32 tUnit definieren, so das man im Source gleich sieht das es
    sich um einen Koordinaten Type handelt.

    Was meinst Du was besser aussieht?

    Was da für dein Programm die bessere Variante ist weiss ich nicht, da ich dein Programm nicht kenne.

    Eine eigene Klasse kann Sinn machen, da man damit z.B. Operatoren überladen kann. Mit nem Typedef geht das nicht, da der Typ dann immer noch "int" oder "long" oder sowas ist (was halt am Ende der Typedef-Kette als Typ steht). Genauso kannst du bei überladenen Funktionen nicht zwischen z.B. "int" und deinem Koordinaten-Typ unterscheiden.

    Ich würde auch nicht danach gehen was besser aussieht, sondern danach, was mehr Sinn macht 😉

    Ohne weiteres zu wissen würde ich eher zu nem Typedef tendieren, da die oben erwähnten Dinge meist nicht nötig sind.
    Addition ist immer Addition, da muss ich nicht wissen ob das Koordinaten oder "normale" Integers sind.
    Beim (De-)Serialisieren könnte es sinnvoll sein die Unterscheidung zu treffen, allerdings wirst du dann ja wieder zwischen "darf negativ sein" und "darf nicht negativ sein" unterscheiden wollen. Das aber - so wie ich deine ursprüngliche Frage verstanden habe - ohne zwei verschiedene Typen zu verwenden.
    Dort würde dir also die eigene Koordinaten-Klasse erst nichts bringen, und die einfachere Variante wäre eigene (De-)Serialisierungs-Funktionen zu verwenden.



  • ps.

    Lichtlein schrieb:

    @hustbaer

    Schönen Dank erst mal für Deine Hartnäckigkeit. 🙂

    Das ist SELTEN
    Die meisten reagieren eher genervt 🙂
    (Was ich sogar irgendwo verstehe)



  • Also, ich habe mich entschlossen es mit Vorschlag B. zu machen.
    Es wird eine ganz einfache Signed Klasse geben. Die sieht so aus.

    struct unit
    {
        typedef int32_t     value_type;
    
        unit() : mValue(0) {}
        unit(value_type Value) : mValue(Value) {}
    
        value_type get() const { return mValue; }
        void set(value_type Value) { mValue = Value; }
    
        operator value_type () const { return mValue; }
    
        const unit& operator += (const unit &Unit);
        const unit& operator -= (const unit &Unit);
        const unit& operator *= (const unit &Unit);
        const unit& operator /= (const unit &Unit);
        const unit& operator %= (const unit &Unit);
    
    private:
        value_type  mValue;
    };
    

    Getestet habe ich das mal in einen kleinen C++ File.
    Es lässt alle Rechenoperationen, Vergleiche usw. zu.
    Es lässt mir aber die Möglichkeit:
    1. Operatoren zu Definieren
    2. template Spezialisierung zu machen <- für mich wichtiger

    Die IN/Archiv Klasse bekommt eine Funktion für diesen Type. Dieser Funktion gibt man noch einen Bereich an, in dem, der Wert beim einlesen liegen muss.
    Etwa so:

    cArchivOut& attribute(const unit &Unit, const cUnitRange &Range = RangeDefault)
    {
      int64_t Tmp;
      cin >> Tmp;
      if( ! Rang.isInside(Tmp))
        throw (-1);
    
      Unit = Tmp;    
      return Archiv;
    }
    

    Dazu kommen noch die beiden Stream operatoren << und >>.
    [cpp]
    ::std::ostream& operator << (::std::ostream Out, const unit &Value);
    ::std::ostream& operator >> (::std::ostream Out, unit &Value);
    [cpp]

    Weiter werde ich meine Datenbasis durchsuchen und "unsigned" Typen gegen "signed" Typen Tauschen.
    z.b.:
    Durchmesser -> Radius
    Breite -> Links / Rechts Wert
    Höhe -> Oben / Unten Wert

    Ich hoffe das wird auch was, da steckt viel viel Arbeit hinter.

    Lichtlein



  • Willst du implizite Konvertierung wirklich erlauben? Die ist zwar praktisch, aber führt teilweise zu Überraschungen. Ausserdem verlieren dann set() und get() einen grossen Teil ihrer Existenzberechtigung.

    Die Alternative wäre, den Konstruktor explicit zu machen und den Konvertierungsoperator zu entfernen. Aber dann hast du halt teilweise umständliche Umwandlungen im Code.



  • Ihr glaubt gar nicht wie Deprimierend das ist wenn man sich nicht 100% Sicher ist das richtig zu machen. Besonders wenn man an die Umstellung von einen 50.000 Zeile Code denkt. 😞

    @Nexus
    Ja an die "implizite Konvertierung" hatte ich auch schon gedacht. Aber bei meinen Versuchen habe ich Herausbekommen das alles Konvertiert wird was er bei Benutzung von int/uint auch machen würde. (Ich lasse mich da aber eines Besseren belehren)

    Da ich die Werte ab und an in double und zurück Wandeln muss habe ich die get/set eingebaut. Dort macht er keine implizite Konvertierung.

    Irgendwie weiß ich immer weniger. 😕

    Andere Frage:
    Weiß jemand wie viel langsamer ist gleich mit Double oder int64 (auf ein 32 Bit System) zu Arbeiten?
    Oder macht es noch Sinn sich auf 32 Bit System festzulegen?

    Lichtlein


Anmelden zum Antworten