Datentype (Design?) Problem / Brauche Rat



  • 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