Variable Binär ausgeben

Hi!

Ich lerne gerade die Grundlagen in C und hab mal ne Frage:

Wie gebe ich den Wert einer Variablen A als Kommazahl mit 4 Nachkommastellen in Binär an?

z.B.:


int A = 9; float B = 2.0;

Y = A/B;

1) Serial.print(Y, BIN, 4) // Meine Vermutung...

2) Serial.print(A/B, BIN, 4); // Das würde mich auch sehr interessieren, wie das geht!!


Ich hoffte, dass 100.1000 rauskämme.

Ich möchte nur verstehen, wie das richtig geht. Fragts mich bitte nicht, für was ich es brauche.

Binär gar nicht.

Kommazahlen können nur als Typ Fließkommazahl verarbeitet werden und diese nur als Dezimal.zahl mit komma ausgegeben werden.

Alles ander mußt Du selbst programmieren.

Grüße Uwe

v3loxx:
Ich lerne gerade die Grundlagen in C und hab mal ne Frage:

Für jemanden, der Grundlagen lernt, ein “einfaches” Programm zur Anzeige von float in Hex und Binär (getestet mit UNO):

bool flag = true;
int A = 9;
float B = 2.0;
union Data
{
  float Y;
  byte werte[sizeof(float)];
};

Data data;

void setup() {
  Serial.begin(9600);
  Serial.println("Anfang");
}

void loop() {
  if (flag) {
    flag = false;
    data.Y = A / B;

    Serial.print("Y: ");
    Serial.print(data.Y);
    Serial.print("\tBytes: ");
    Serial.print(sizeof(data.werte));
    Serial.print("\tHex: ");
    for (byte j = 0; j < sizeof(data.werte); j++)
    {
      if (data.werte[j] < 16) Serial.print('0');
      Serial.print(data.werte[j], HEX);
      Serial.print(' ');
    }
    Serial.print("\tBin: ");
    for (byte j = 0; j < sizeof(data.werte); j++)
    {
      for (int k = 7; k >= 0; k--)
      {
        Serial.print((data.werte[j] & 1 << k) ? '1' : '0');
      }
      Serial.print(' ');
    }
    Serial.println();
  }
}

In welcher Reihenfolge die Bytes zu interpretieren sind, kann ich derzeit nicht sagen. Da vertraue ich dann mal auf Dich, würde auch gerne was lernen :slight_smile:

Serial.print(Y, BIN, 4) // Meine Vermutung...

Worauf gründest du deine Vermutung? Hast du dir schonmal die Referenz zu Serial.print angeguckt? Gibt es da eine Version mit 3 Parametern, von denen der erste eine float-Zahl ist?

Serial.println(1000.1, 4); //1000.09997... sollte auf 4 Nachkommastellen gerundet, zufällig *) dein Wunschergebnis anzeigen...

@agmue: Guckst du de.wikipedia.org/wiki/IEEE_754 ( single) und spielst ein bisschen mit diesem Tool


) Nachtrag: *zufällig ist es nicht wirklich, "glücklicherweise bei deinem Beispiel" wäre passender ;)

Dazu sollte man sich erst mal ansehen was Nachkommastellen in Binär überhaupt bedeuten. Naiv geht das einfach http://www.binaryessence.de/mth/de000155.htm

Oder z.B. 0.101(b) = (1 * 2^-1) + (0 * 2^-2) + (1 * 2^-3) = 1/2 + (0 * 1/4) + 1/8 = 0.5 + 0.125 = 0.625

Die Binärdarstellung der einzelnen Bytes eines Floats ist aber was ganz anderes!

michael_x: Worauf gründest du deine Vermutung? Hast du dir schonmal die Referenz zu Serial.print angeguckt? Gibt es da eine Version mit 3 Parametern, von denen der erste eine float-Zahl ist?

Serial.println(1000.1, 4); //1000.09997... sollte auf 4 Nachkommastellen gerundet, zufällig dein Wunschergebnis anzeigen...

@agmue: Guckst du de.wikipedia.org/wiki/IEEE_754 ( single) und spielst ein bisschen mit diesem Tool

Arduino rechnet mit einer 4Byte Fließkommazahl. Da sind nur 6 bis 7 Stellen richtig, alles andere sind Vermutungen.

Grüße Uwe

Da sind nur 6 bis 7 Stellen richtig, alles andere sind Vermutungen

Richtig. Allerdings keine Zufallszahlen.

1000.1 sind nur 5 Stellen, aber keine "glatte" Zahl, da wird das interne Bitmuster 0x447a0666 gerundet, je nach der gewünschten Anzahl Nachkommastellen...

Serial.println(1000.1, 2) ; // 1000.10
Serial.println(1000.1, 4) ; // 1000.1000
Serial.println(1000.1, 5) ; // 1000.09998
Serial.println(1000.1, 6) ; // 1000.099976

Wenn das niederwertigste Bit der Mantisse um 1 größer wäre (0x447a0667), wäre sowas zu erwarten:

union {float x; byte b[4];} test;
test.x=1000.1;
test.b[0] |=1; // ein Tick größer als 1000.1000 ( im niederwertigsten Byte der Mantisse  )

Serial.println(test.x, 4) ; // 1000.1000
Serial.println(test.x, 5) ; // 1000.10004
Serial.println(test.x, 6) ; // 1000.100037

Der Wunsch-Wert 1000.10000000 ist eben dazwischen, aber in einer float-Zahl nicht exakt darstellbar.


Danke agmue: union ist hardware-abhängig, die Reihenfolge der Bytes auf LittleEndian-Maschinen ( atmega, attiny, intel, amd usw.) ist wie in deinem Test. (Hatte ich anfangs falsch rum)

Zufallszahlen meinte ich, daß die Zahlen sich nicht aus der Rechnung ergeben, sondern durch die endliche Genauigkeit der Zahl in Schritten zwar wiederholbar aber falsch darstellen.

Mach mal (1000.0 * 1000.0) gibst das aus und dann addiere 1 dazu und gebe es wieder aus.

Grüße Uwe

Danke für die tollen Links, damit konnte ich feststellen, daß, wie von mir vermutet, die Bytes andersherum angezeigt werden sollten. Das Programm aus #2 habe ich entsprechend verändert und Leerzeichen hinter dem Vorzeichen und dem Exponenten eingefügt:

bool flag = true;
int A = 9;
float B = 2.0;

void setup() {
  Serial.begin(9600);
  Serial.println("Anfang");
}

void loop() {
  if (flag) {
    flag = false;
    float_als_bin(A/B);
  }
}
void float_als_bin(float f)
{
  union Data
  {
    float y;
    byte werte[sizeof(float)];
  };

  Data data;
  data.y = f;
  /* Anzeige dezimal und hexadezimal
  Serial.print("Wert: ");
  Serial.print(data.y);
  Serial.print("\tBytes: ");
  Serial.print(sizeof(data.werte));
  Serial.print("\tHex: ");
  for (int8_t j = sizeof(data.werte) - 1; j >= 0; j--)
  {
    if (data.werte[j] < 16) Serial.print('0');
    Serial.print(data.werte[j], HEX);
    Serial.print(' ');
  }
  Serial.print("\tBin: ");
  */
  /* Anzeige pro Byte
  for (int8_t j = sizeof(data.werte) - 1; j >= 0; j--)
  {
    for (int8_t k = 7; k >= 0; k--)
    {
      Serial.print((data.werte[j] & 1 << k) ? '1' : '0');
    }
    Serial.print(' ');
  }
  Serial.println();
  */
  // Anzeige Vorzeichen-Exponent_Mantisse (siehe https://www.h-schmidt.net/FloatConverter/IEEE754de.html)
  for (int8_t j = 31; j >= 0; j--)
  {
    Serial.print((data.werte[j / 8] & 1 << (j % 8)) ? '1' : '0');
    if (j == 31 || j == 23 || j == 16 || j == 8) Serial.print(' ');
  }
  Serial.println();
}

Mal zur Erklärung weil IEE-754 nicht intuitiv ist. Man kann binäre Nachkommastellen wie erhofft ganz naiv behandeln. Also 100.1 = (1 * 2^2) + (1 * 2^-1) = 4.5

Das ist nicht falsch! Aber das ist nicht was Computer abspeichern. Damit wäre der Zahlenbereich sehr stark eingeschränkt.

In IEE-754 wird 4.5 in eine Mantisse M und einen Exponenten E aufgeteilt mit der Formel 2^E * M so dass die Mantisse immer zwischen 1 und 2 liegt. Was hier kodiert wird ist 2^2 * 1.125.
Der tatsächlich gespeicherte Exponent ist 2 + 127 = 129. Dadurch werden negative Exponenten vorzeichenlos.
Die 1 der Mantisse wird nicht gespeichert, also hat man 0.125 hat was Binär dann 001 ist (2 ^ -3).
So ergibt sich:
0 | 10000001 | 001…
wobei das erste Bit das Vorzeichen ist. Dann 8 Bit für den Exponenten und der Rest die Mantisse

Oder z.B. 100.25(d) wäre naiv 1100100.01(b)
IEE-754 macht daraus 2^6 * 1.56640625. Der Exponent ist 6 + 127 = 133. Die Mantisse ist 0.56640625 = 10010001 = 0.5 + 0.0625 + 0.00390625

So kann man auch extrem große und kleine Zahlen in nur 4 Byte speichern.