C - Verwendung von Union und binäre Darstellung?
Hallo Leute,
meine Freundin hat ein Problem mit einer C-Aufgabe. Ich bin leider mit meinen Basics am Ende und wollte daher mal euch um Rat fragen.
Zur Aufgabe: Man soll sich die interne Repräsentation einer double Zahl binär ausgeben. Man soll dazu die double Variabel mit 2 ints überlagern (Union) und diese dann hintereinander binär ausgeben (mit größtem Index zuerst).
Laut Aufgabe und aktuellem Stoff soll die Aufgabe also mit Union gelöst werden.
Hier der Code:
#include <stdio.h>
union zahl { int x[2]; double y; } n;
void toBinary(int zahl){
int rest, erg = 0;
int fak = 1;
while(zahl){
rest = zahl%2;
zahl = zahl/2;
erg = erg + rest*fak;
fak = fak10;
}
printf("%d", erg);
}
int main(void) {
int erg =0;
sscanf("3.5", "%lf", &n.y);
toBinary(n.x[1]);
puts(" ");
printf("%d\n", n.x[1]);
printf("%f\n", n.y)
}
Sie hat sich nun dran gemacht eine andere Lösung zu finden (anscheinend mit bitshift), aber es wurmt sie sehr wieso die obige Lösung nicht funktioniert.
Ich sehe in der Programmierung erstmal keine groben Fehler, aber es kommt das falsche Ergebnis raus, vermutlich weil sich da irgendetwas mit union nicht so verträgt, aber da bin ich nicht so fit drin.
Könnte mir jemand erklären was hier nicht richtig ist? Ohne erstmal einen alternativen Vorschlag zu geben?
Danke und viele Gürße
2 Antworten
Die Funktion toBinary( ) tut an sich, hat aber arge Probleme mit Überläufen.
Bis zu einer Länge von 8 Bit (0xff) kommt noch das richtige raus, mit 12 Bit (0xfff) schon nicht mehr. Das liegt ganz einfach daran, dass die ints bei der Berechnung überlaufen. 10^10 > 2^32, d.h. bei 10 Bit knallt's schon, wenn man 10x fak mit 10 multipliziert.
Wenn man aus den ints longs macht geht's ein wenig länger gut, reicht aber noch lange nicht für 32 Bit.
Sinniger wäre, das Ergebnis textuell in einem char[ ] (Zeichenkette) zu halten. Oder überhaupt nicht zu halten, sondern direkt rauszuschreiben (tut das printf am Ende ja ohnehin, wieso also nicht gleich ziffernweise??).
Ob man dann mittels Modulo und Division oder mit Rightshifts und Bit-Ands arbeitet ist einerlei. Letzteres ist aber sauberer und ggf. (bei schlechten Compilern) auch performanter.
Und statt
rest = zahl % 2;
zahl = zahl / 2;
würde ich
rest = zahl & 1;
zahl = zahl >> 1;
schreiben.
Ist mathematisch in diesem Fall natürlich das selbe: Rechtsshift = geteilt durch 2 bzw. unterstes Bit = gerade/ungerade = Modulo 2.
Kann man natürlich noch weiter vereinfachen/aufräumen, ggf. um den Preis der Lesbarkeit. Wir vergeben hier ja keine Preise für das kürzeste C-Programm. ;-))
Vielen Dank. Die Ausgabe sieht nun so aus:
0000000000000000001100000000001
1074528256
3.5
Die erste Zeile sollte nun die binäre Darstellung sein. Warum sind die beiden Anteile? Also 00000000000000000011 für die 3
und 00000000001 für die 0.5 unterschiedlich lang?
und 1074528256 ist dann die interne Darstellung von 3.5?
und eine weitere Frage zum ursprünglichen Programm:
Was soll dieser ganze Teil mit "fak" eigentlich? Also
erg = erg + rest*fak;
fak = fak10;
Vielen Dank :)
Das "fak" war der "Zeiger" auf die Dezimalstelle. x 10 -> eine Dezimalstelle weiter. Also im Grunde das dezimale Äquivalent zu Bitshift.
Die Aushabe haut nicht hin, weil die führenden Nullen fehlen. Die "zahl" wird ja immer halbiert, bis nur noch Nullen kommen. Das klappt so natürlich nicht, weil führende (und nachfolgende) Null-Bits keine Ausgabe erzeugen.
Deshalb sollte man besser ein einzelnes 1-Bit über alle Stellen (Bit 32 bis Bit 1) rennen lassen, Bit-And machen, und dann 0 oder nicht-0 in ein 32 chars langes Array schreiben (als '0' oder '1').
Okay den Teil nach fak hab ich nicht verstanden. Ist die Ausgabe nun falsch, die ich geschrieben habe?
Naja, was heißt "falsch"... Sagen wir mal so: Sie gibt nicht das aus, was du denkst bzw. gerne hättest, dass ausgegeben wird. :D
Als Denkansatz:
char bits[33];
bits[32] = 0; // nullterminierter String der Länge 32
for (int i = 0; i < 32; ++i) {
int digit = (zahl >> i) & 1; // Bit an Position i
bits[31-i] = digit ? '1' : '0';
}
printf("%s\n", bits);
Oder so ähnlich.
Also als toBinary().
Ausgabe dann für
toBinary(0xff);
toBinary(0xfff);
toBinary(0xffff);
toBinary(0xfffff);
toBinary(0xffffff);
Tut m.E. was es soll:
00000000000000000000000011111111
00000000000000000000111111111111
00000000000000001111111111111111
00000000000011111111111111111111
00000000111111111111111111111111
Ich habe den Code mal abgeändert, aber die Ausgabe ist immer noch "fast" die gleiche. Jetzt bekomme ich:
00000000000000000011000000000010
1074528256
3.500000
anstelle
0000000000000000001100000000001
1074528256
3.5
union zahl { int x[2]; double y; }n;
void toBinary(int zahl){
char bits[33];
bits[32] = 0;
for (int i = 0; i < 32; ++i) {
int digit = (zahl >> i) & 1;
bits[31-i] = digit ? '1' : '0';
printf("%d", digit);
}
printf("\n");
}
int main(void) {
int erg =0;
sscanf("3.5", "%lf", &n.y);
toBinary(n.x[1]);
printf("%d\n", n.x[1]);
printf("%f\n", n.y);
}
dann ist das hier meine erste Ausgabe:
``````````````````````````````````````````````````````````00000000011000000000000000000`000000000011000000000000000000`100000000001100000000000000000001000000000011000000000000000000
wie kann ich das interpretieren? :D
#include <stdio.h>
union zahl { int x[2]; double y; }n;
void toBinary(int zahl){
char bits[33];
bits[32] = 0; // nullterminierter String der Länge 32
for (int i = 0; i < 32; ++i) {
int digit = (zahl >> i) & 1; // Bit an Position i
bits[31-i] = digit ? '1' : '0';
// printf("%s", bits);
}
printf("%s", bits);
printf("\n");
}
int main(void) {
int erg =0;
sscanf("3.5", "%lf", &n.y);
toBinary(n.x[1]);
printf("%d\n", n.x[1]);
printf("%f\n", n.y);
}
Das ist die korrekte Ausgabe!
01000000000011000000000000000000 und dann nochmal 32 Nullen (fehlt ja noch der zweite int, dobule hat 64 Bit).
http://www.binaryconvert.com/result_double.html?decimal=051046053
Super! Vielen Dank, ich suche auch schon die ganze Zeit nach einer bit Darstellung.
Aber nochmal 32 Nullen weil du (also der Link) ja eine 64bit Version hast oder? :)
Warum hat denn das Programm davor das Ergebnis umgedreht gezeigt?
Weil du vmtl. den Code halb geändert hattest, so dass er vom niedrigen zum höherwertigen Bit durchgelaufen ist und das in genau dieser Reihenfolge auch rausgeschrieben hat.
Deswegen ja bits[31-i], damit der String in Big Endian Reihenfolge rauskommt (d.h. höherwertigste Ziffer links, wie man es von Dezimalzahlen gewohnt ist).
Nicht nur ich/der Link hat 64 bit. Du hast ja auch ein int-Array der Länge 2. Double ist jedenfalls doppelt so lang wie int (auch wenn erstmal unklar ist wie lang der int ist: früher 32 bit, heute vmtl. eher 64 bit).
Ja es kam falsch herum raus, weil ich quasi jedes Bit rausschreibe.
dann liegt es ggf an der Darstellung meines compilers das er mir nur 32bit im Gesamtergebnis ausspuckt, bzw die restlichen Nullen abschneidet, wobei die Schleife ja auch nur 32bit zählt.
Dennoch: Die Zahl 1074528256
Ist das eine interne Darstellung? oder einfach nur der Wert der Adresse? Sprich was in x[2] steht.
Sorry für die restlichen "Kleinviehfragen".
Dickes Dankeschön nochmal! :)
Der Länge des Exponenten nach (augenscheinlich ja 11 Bit)
01000000000011000000000000000000...
handelt es sich um IEEE Doubles (64 Bit).
Wieviel Bits dein "int" hat ist compilerabhängig, kannst du mittels
sizeof(int)
abfragen. Daran solltest du die Schleifenlänge festmachen, wenn es denn portabel sein soll...
Ersetze mal die Zeilen
toBinary(n.x[1]);
puts(" ");
printf("%d\n", n.x[1]);
printf("%f\n", n.y)
durch
printf( "%lf = 0x%08x%08x\n", n.y, n.x[0], n.x[1] );
Die Methode toBinary() kann dann raus. Ich gebe die Zahlen hexadezimal aus. Aus jeder Ziffer lassen sich mit etwas Übung oder einen kleinen Umsetzungstabelle die Bits ablesen:
0 = 0000
1 = 0001
...
9 = 1001
A = 1010
...
F = 1111
Okay deine Argument hab ich soweit verstanden. Danke! Die Ausgabe allerdings noch nicht. Wie würde ich das also umschreiben?