Verständnisfragen zu unsigned, signed, Wertebereich short, int, long, Suffix etc.



  • Hallo alle zusammen,

    ich habe folgenden Quellcode:

    #include <iostream>
    
    using namespace std;
    
    int main() {
    	unsigned int ui = 0u;																				
    	unsigned long int uli = 0ul;																		
    	unsigned long long int ulli = 0ull;																
    	
    	cout << "max. unsigned int	     = " << ~ui << '\n';
    	cout << "max. unsigned long int      = " << ~uli << '\n';
    	cout << "max. unsigned long long int = " << ~ulli << '\n';
    
    
    	
    	cout << "max. int                    = " << static_cast<int>(~ui >> 1) << '\n';								
    	cout << "max. long int               = " << static_cast<long int>(~uli >> 1) << '\n';				
    	cout << "max. long long int          = " << static_cast<long long int>(~ulli >> 1) << '\n';			
    
    	cout << "Annahme: char = 8 Bits; short = 16 Bits\n";
    	cout << "max. unsigned char          = " << 0b11111111 << '\n';									
    	cout << "max. signed char            = " << 0b01111111 << '\n';
    	cout << "max. unsigned short         = " << 0b1111'1111'1111'1111 << '\n';			
    	cout << "max. signed short           = " << 0b0111'1111'1111'1111 << '\n';
    
    
    	cout << "Je nach System koennen manche Werte gleich sein, also "
    		" dieselbe"
    		" Anzahl Bits verwenden.\n";
    

    Jetzt habe ich folgende Verständnisfragen:

    Frage 1, Zeile 6 - 8:

    Die Suffix u, ul und ull werden benutzt, damit der Rechner nur im Wertebereich z.B. int = 0 - 2³²-1 "arbeitet" bzw. um den Rechner in diesen Wertebereich "festzunageln" oder?

    Frage 2, Zeile 10 - 12:

    Der ~-Operator dreht alle Bits, d.h. im Fall von

    cout << "max. unsigned int			 = " << ~ui << '\n';
    

    "entsteht" erst eine

    0000'0000'0000'0000'0000'0000'0000'0000

    und dann eine

    1111'1111'1111'1111'1111'1111'1111'1111

    = 4.294.967.295

    Hab ich mir das so richtig überlegt?

    Frage 3, Zeile 16 - 18:

    Hier wird bei

    (~ui >> 1)
    

    die

    1111'1111'1111'1111'1111'1111'1111'1111

    "genommen" und um eine Stelle nach rechts verschoben. Somit
    entsteht die

    0111'1111'1111'1111'1111'1111'1111'1111

    = 2.147.483.647

    die dann als signed int dargestellt wird.
    Ist dieser Gedankengang korrekt?

    Zusatzfrage:

    Muss

    static_cast
    

    benutzt werden, weil sich der Wertebereich ändert?

    Frage 5, Zeile 21 - 24

    In z.B. Zeile 21 wird mit

    0b11111111
    

    eine Binärzahl dargestellt. Die Ausgabe in der Konsole zeigt 255 an.
    Ist das eine implizite Umwandlung oder schmeiss ich da was gewaltig durcheinander?

    Ich bedanke mich schon mal im voraus für eure Hilfe und entschuldige mich für die teils
    "einfache" Wortwahl. Ich habe keinen IT - technischen Hintergrund und mir sind keine Fach-
    begriffe geläufig bzw. kann mir die noch nicht alle merken.

    Nichtsdestotrotz möchte ich die Hintergründe bei z.B. oben gezeigten Programm verstehen...:)...

    Wünsche euch allen einen schönen Sonntag und ein stressfreien Start in die neue Woche.

    Grüsse....der Benny...☺

    P.S.:
    Sollte dieser Beitrag zu unübersichtlich sein etc. dann entschuldige ich mich auch dafür und gelobe Besserung.



  • @J0k3r sagte in Verständnisfragen zu unsigned, signed, Wertebereich short, int, long, Suffix etc.:

    Frage 5, Zeile 21 - 24
    In z.B. Zeile 21 wird mit
    0b11111111

    eine Binärzahl dargestellt. Die Ausgabe in der Konsole zeigt 255 an.

    Das 0b11111111 ist ein Zahlenliteral. Das ist, sofern du kein Suffix angibst, ein int
    cout nimmt, sofern kein Modifier genommen wird, bei einem int die Dezimaldarstellung.



  • @J0k3r sagte in Verständnisfragen zu unsigned, signed, Wertebereich short, int, long, Suffix etc.:

    Die Suffix u, ul und ull werden benutzt, damit der Rechner nur im Wertebereich z.B. int = 0 - 2³²-1 "arbeitet" bzw. um den Rechner in diesen Wertebereich "festzunageln" oder?

    ja im prinzip ja, also der compiler wird dann festgenagelt, nicht der rechner. wenn du also z.b. unsigned long long int x = 0xffffffffu + 0xffffffffu schreibst, hast du evtl. ein falsches ergebnis.

    Frage 2, Zeile 10 - 12:

    Der ~-Operator dreht alle Bits, d.h. im Fall von

    cout << "max. unsigned int			 = " << ~ui << '\n';
    

    "entsteht" erst eine

    0000'0000'0000'0000'0000'0000'0000'0000

    und dann eine

    1111'1111'1111'1111'1111'1111'1111'1111

    = 4.294.967.295

    Hab ich mir das so richtig überlegt?

    ja genau. aber wenn man mit binär- (und oktal- und hex-) zahlen arbeitet, ist man am dezimalzahlenwert eigentlich nicht interessiert.

    Frage 3, Zeile 16 - 18:

    Hier wird bei

    (~ui >> 1)
    

    die

    1111'1111'1111'1111'1111'1111'1111'1111

    "genommen" und um eine Stelle nach rechts verschoben. Somit
    entsteht die

    0111'1111'1111'1111'1111'1111'1111'1111

    = 2.147.483.647

    die dann als signed int dargestellt wird.
    Ist dieser Gedankengang korrekt?

    ja.

    Zusatzfrage:

    Muss

    static_cast
    

    benutzt werden, weil sich der Wertebereich ändert?

    ich weiß gar nicht, ob man einem signed int den wert eines unsigned int zuweisen kann. also allgemein bedeutet static_cast ja soviel wie "bitte interpretiere den wert in diesem typ" und der compiler geht dann auch davon aus, dass du 100%ig genau weißt, was du da eigentlich machst.



  • Vielen Dank @DirkB und @Wade1234 für die schnellen Antworten..:)

    @Wade1234 sagte in Verständnisfragen zu unsigned, signed, Wertebereich short, int, long, Suffix etc.:

    Zusatzfrage:

    Muss

    static_cast
    

    benutzt werden, weil sich der Wertebereich ändert?

    ich weiß gar nicht, ob man einem signed int den wert eines unsigned int zuweisen kann. also allgemein bedeutet static_cast ja soviel wie "bitte interpretiere den wert in diesem typ" und der compiler geht dann auch davon aus, dass du 100%ig genau weißt, was du da eigentlich machst.

    Das weiß ich auch nicht....:)..diese Zeilen sind auch die einzigen die ich..ähmm...nur so halb verstehe...:/..

    Ich habe herausgefunden dass static_cast wohl so funktioniert:

    static_cast<Zieldatentyp>(Quellobjekt)
    

    Eine der Zeilen in der Übungsaufgabe lautet:

    cout << "max. int                    = " << static_cast<int>(~ui >> 1) << '\n';
    

    In diesem Fall ist das Quellobjekt eine unsigned int und wird in eine signed int umgewandelt richtig?
    Dadurch ändert sich der Wertebereich...und so kam ich zu meiner Frage...:)..

    Eine weitere Frage:

    Sind die Zeilen mit static_cast ein Beispiel für eine explizite Typumwandlung?

    Grüsse


  • Mod

    Frage 1: Die Suffixe in Zeilen 6-8 haben überhaupt keinen Einfluss auf irgendetwas. Können weggelassen werden.

    Frage 2: Wurde beantwortet.

    Frage 3: Wurde beantwortet.
    Zusatzfrage: Ich bin mir nicht sicher, ob ich dich richtig verstehe. Es geht darum, dass der Typ umgewandelt werden soll. Das Ergebnis der Berechnungen in den Klammern ist jeweils vom Typ der dort benutzten unsigned Variablen (Das ist übrigens gar nicht so trivial, dass das Ergebnis der Rechnung von diesem Typ ist. Siehe: usual arithmetic conversions). Aus welchem Grunde auch immer, soll das Ergebnis hier in einen entsprechenden signed-Typen umgewandelt werden. Das wird natürlich den Wertebereich (und möglicherweise - aber nicht hier - sogar den Wert) verändern, aber das static_cast ist auf jeden Fall nötig, wenn man innerhalb eines Ausdrucks den Typ eines Unterausdrucks ändern möchte.
    Es macht hier bloß nix. Ist genauso unnötig wie die Suffixe aus Frage 1.

    Frage 5: Wurde beantwortet.

    Insgesamt solltest du das Programm nicht unbedingt als Vorbild nehmen. Es macht viele implizite Annahmen, die zwar auf vielen Rechnern gelten, aber nicht unbedingt gelten müssen. Beispielsweise die im Text genannte Annahme über die Bitgröße gewisser Typen. Aber auch mehr. Wenn du den Wertebereich von Integern und anderen Typen wirklich zuverlässig wissen möchtest, dann schau dir an:
    https://en.cppreference.com/w/cpp/types/numeric_limits (C++-Stil, und kann allgemein mehr)
    oder im C-Stil
    https://en.cppreference.com/w/cpp/header/climits
    https://en.cppreference.com/w/cpp/header/cfloat

    PS:
    @J0k3r sagte in Verständnisfragen zu unsigned, signed, Wertebereich short, int, long, Suffix etc.:

    Eine weitere Frage:

    Sind die Zeilen mit static_cast ein Beispiel für eine explizite Typumwandlung?

    Ja.



  • also ich bin mir jetzt nicht ganz sicher, aber ich meine, dass es mathematisch egal ist, ob signed oder unsigned. aber wenn du die werte mit cout oder printf ausgeben willst, macht das natürlich schon einen unterschied. also negative zahlen werden mittels einerkomplement und zweierkomplement dargestellt, falls dich das interessiert.


  • Mod

    @Wade1234 sagte in Verständnisfragen zu unsigned, signed, Wertebereich short, int, long, Suffix etc.:

    also ich bin mir jetzt nicht ganz sicher, aber ich meine, dass es mathematisch egal ist, ob signed oder unsigned. aber wenn du die werte mit cout oder printf ausgeben willst, macht das natürlich schon einen unterschied. also negative zahlen werden mittels einerkomplement und zweierkomplement dargestellt.

    Das widerspricht sich doch. Wenn die Werte entweder mittels Einerkomplement oder Zweierkomplement dargestellt werden können, dann macht das ja wohl einen gewaltigen Unterschied, wenn man ein Bitmuster reinterpretiert. Der Standard ist so nett, zu verlangen, dass die Konvertierung von unsigned nach signed der Modulo-Arithmetik folgen muss (d.h. bei Einerkomplement ist es dann doch keine reine Reinterpretation), aber umgekehrt ist es implementation defined (sofern der Wertebereich nicht mehr passt), was salopp gesagt so viel heißt wie "aus Effizienzgründen wird das Bitmuster uminterpretiert". Dann ist das mathematisch schon sehr wichtig, ob bei irgendwelchen Zwischenergebnissen der Wertebereich verlassen wurde oder nicht.



  • @SeppJ also ich meine damit, dass es egal ist, ob ich jetzt - sag ich mal - "-25 + 3" oder "231 + 3" (bei 8 bit) rechne, weil ja immer der gleiche binärwert dabei rauskommt.

    dass man in einer datenbank, die die telefonnummern der weltbevölkerung aufnehmen soll, für die id-nummer, oder in einem programm, das das vermögen von bill gates berechnet, vielleicht nicht unbedingt unsigned int verwenden sollte, ist da ja eine ganz andere geschichte.



  • Siehe auch
    https://youtu.be/PPAlh_FmO4M
    Gibt in der Reihe auch eine Erklärung, warum unsigned u = -1 dar Maximalwert ist. Finde ich auf dem Handy auf die Schnelle aber nicht.



  • Vielleicht hilft das schon beim Verständnis, das habe ich in einem anderen Forum gefunden.

    #include <stdio.h>
    #include <inttypes.h>
    
    int main()
    {
    	uint8_t a = 0 - 1;
    	uint16_t b = 0 - 1;
    	uint32_t c = 0 - 1;
    	uint64_t d = 0 - 1;
    	printf("%u\n", a);
    	printf("%u\n", b);
    	printf("%u\n", c);
    	printf("%" PRIu64 "\n", d);
    	printf("%x\n", a);
    	printf("%x\n", b);
    	printf("%x\n", c);
    	printf("%" PRIx64 "\n", d);
    	return 0;
    }
    

    Wenn Du dich fragst, wieso die Ausgabe so ist, wie sie ist, dann einfach fragen...


  • Mod

    @Wade1234 sagte in Verständnisfragen zu unsigned, signed, Wertebereich short, int, long, Suffix etc.:

    @SeppJ also ich meine damit, dass es egal ist, ob ich jetzt - sag ich mal - "-25 + 3" oder "231 + 3" (bei 8 bit) rechne, weil ja immer der gleiche binärwert dabei rauskommt.

    Das hab ich schon verstanden, aber was du sagst ist falsch, und du hast selber erklärt, warum. Es ist nämlich nicht definiert, dass

    unsigned char c = -25;
    

    gleich 231 ist. Die interne Darstellung von signed Werten, hier die -25, ist implementation defined und wenn das beispielsweise ein Einerkomplement ist (oder wie wäre es mit etwas ganz exotischem? Ist ja nicht verboten), dann ist das ohne sein Vorzeichen wer weiß was, aber nicht 231 (bin gerade zu faul, das auszurechnen).

    Wohlgemerkt ist umgekehrt vorgeschrieben, dass

    signed char c = 231;
    

    gleich -25 ist. Was Zweierkomplement als interne Darstellungsweise zwar stark begünstigt, aber nicht zwingend vorschreibt.

    Das ganze ist natürlich ein bisschen akademisch, da 99.9% aller Rechner, mit denen man so in Berührung kommen wird, mit Zweierkomplement rechnen werden. Aber es kostet ja nichts, auf solche Feinheiten zu achten. Dann tut's nicht so weh, wenn man bei den 0.1% ansonsten auf die Schnauze fliegen würde.

    Das kritisiere ich auch an der Aufgabenstellung hier. 8 und 16 Bit mögen zwar gängige Größen sein, aber es gibt auf der Welt durchaus real existierende Rechner, wo das nicht so ist, und es hätte hier überhaupt nichts gekostet (es wäre sogar kürzer gewesen!), zum Finden der Extremwerte die dafür vorgesehenen vordefinierten Konstanten zu benutzen.



  • Vielen Dank an alle für die Antworten.

    @SeppJ

    Ich habe das Programm mal ohne die Suffix laufen lassen....ändert sich nichts....:/....könntest du vllt deinen letzten Post erklären? Ich habe den inhaltlich nicht verstanden...

    @Wade1234

    @Wade1234 sagte in Verständnisfragen zu unsigned, signed, Wertebereich short, int, long, Suffix etc.:

    @SeppJ also ich meine damit, dass es egal ist, ob ich jetzt - sag ich mal - "-25 + 3" oder "231 + 3" (bei 8 bit) rechne, weil ja immer der gleiche binärwert dabei rauskommt.

    Diese Aussage habe ich nicht verstanden. Was meinst du damit?

    @all

    Sry fürs dumme Nachfragen..


  • Mod

    Das ist nur gegenseitige Klugscheißerei, ohne direkten Bezug zu deinem Thema. Das grundlegend zu erklären wäre ziemlich umfangreich und würde nichts bringen, außer dass du dann selber auch Klugscheißen kannst 😃

    Als Einstieg: Es geht um die maschinelle Darstellung von Zahlen. Die Begriffe "Einerkomplement" und "Zweierkomplement" sollten sich ganz gut googeln lassen. Erwarte aber keine kurze, einfache Erklärung!



  • @SeppJ

    Das erwarte ich schon lange nicht mehr@kurze, einfache Erklärungen..zumindest wenn es um C++ und das Drum - Herum geht...;D

    Über "Einerkomplement" und "Zweierkomplement" habe ich schon ein paar Zeilen in meiner Lektüre gelesen, dort wurde das aber nur angerissen.

    Mal schauen was ich zu dem Thema noch so finde...:)..



  • einerkomplement ist ja nur die invertierte zahl und zweierkomplement ist einerkomplement plus 1.

    und um eine dezimalzahl in eine dualzahl umzurechnen, teilst du die diese durch 2, schreibst den rest auf und den quotienten teilst du dann wieder durch 2 und schreibst den rest links daneben, bis irgendwann nichts mehr zu rechnen ist.

    also beispiel:
    25 : 2 = 12 R 1 => 1
    12 : 2 = 6 R 0 => 01
    6 : 2 = 3 R 0 => 001
    3 : 2 = 1 R 1 => 1001
    1 : 2 = 0 R 1 => 11001

    wenn du dann das einerkomplement haben willst, invertierst du das (8 bit)
    25 <=> 00011001

    einerkomplement: 11100110
    zweierkomplement: 11100111

    probe mit 25 + (-25):

      00011001
    + 11100111
    ----------
     111111110 (übertrag)
    ----------
      00000000 (achtung
    

    => funktioniert



  • Hallo,

    jetzt muss ich den Fred hier doch noch mal öffnen/wiederbeleben, weil ich eine Verständnisfrage bzgl. Einerkomplement habe.

    @SeppJ

    Du hattest recht mit "Erwarte keine kurze, einfache Erklärung"...:)

    Zur Frage:

    Bei dem Einerkomplement habe ich einen Wertebereich von -127 bis +127, richtig?
    Wenn ich das richtig verstanden habe, dann kommt in diesem Wertebereich die Null
    2x vor...+0 und -0...

    Einerkomplement habe ich so verstanden:

    Fall 1:

    0000 0000

    Die dicke Null ist das Vorzeichen - Bit.
    In diesem Fall hätte ich doch quasi eine +0 oder?

    Fall 2:

    1000 0000

    In diesem Fall hätte ich doch eine -0...

    Also ich bin ein bissel verwirrt, weil im WWW wird zu diesem Thema geschrieben, dass
    1111 1111 eine -0 wäre.

    Aber rechnerisch ist das doch die -127, weil die erste 1 nur das Vorzeichen darstellt und wenn
    ich die anderen Einsen nach Dezimal umrechne kommt 127 raus..mit Vorzeichen -127...

    Ich bin mir ziemlich sicher, dass ich irgendwo nen Denk/Rechen/Logikfehler hab, aber ich
    find ihn nicht.

    Möglicherweise kann mir jemand einen kleinen Hinweis geben....

    Danke im voraus.



  • nein im einerkomplement wäre die -0 1111 1111 (0000 0000 invertiert bzw. +0 identisch -0). weil du aber mit dem zweierkomplement rechnest und dieses das einerkomplement + 1 = 1111 1111 + 1 = (1) 0000 0000 ist. die -127 ist übrigens ~(0111 1111) = 1000 0000 und 1111 1111 ist immer -1 im zweierkomplement wegen ~(0000 0001) = 1111 1110 im einerkomplement bzw. 1111 1110 + 1 = 1111 1111.


  • Mod

    Wenn das Vorzeichenbit gesetzt ist, dann kehrt sich die Interpretation der restlichen Zahl um.

    Daher ist 1000 0000 die -127, denn es hat
    a) Ein Vorzeichen
    b) Es sind alle anderen Bits "gesetzt", weil die Nullen wegen a) als Einsen gelten
    Und entsprechend ist 1111 1111 dann eine negative Null.

    Was du bei deiner Interpretation im Kopf hast, nennt man gemeinhin "Betrag und Vorzeichen" (Englisch: Signed magnitude representation). Das ist halt noch eine weitere mögliche Zahlendarstellung. Die ist aber nochmals unüblicher als die ohnehin schon seltene Einerkomplementdarstellung.



  • Ich habe das aus Digitaltechnik so in Erinnerung, dass beim Einerkomplement die Null irgendwie doppelt belegt ist, und beim Zweierkomplement wird das "Problem" gelöst, indem im negativen Zahlenbereich noch ein zusätzlicher Wert hinzukommt, dazu wird die Arithmetik ebenfalls einfacher (z.B. einfaches carry bit bei Addition).



  • also einer meiner professoren sagte mal, dass man diese rechenweise in beliebigen zahlensystemen (und damit auch im dezimalsystem) durchführen kann und wenn ich meine experimente richtig in erinnerung habe, ist die invertierte ziffer (vom einerkomplement) immer basis - ziffer - 1 und um solche spielereien mit negativen zahlenäquivalenten durchzuführen muss man dann wieder 1 zur gesamtzahl addieren.


Log in to reply