Zweierkomplement Long

Hallo,
ich bekomme von einer etwas umständlichen Dekodierung einen binären String zurrück, der 28 Bits enthält. Er repräsentiert eine zweierkomplement Zahl, die also auch negativ werden kann. Ich verzweifel nun etwas an der konvertierung dieses Strings, da ich einfach keine negativen Werte erhalte.
Ich hab dazu mal die Funktion strtol() getestet. Mit einem String, der einem Integer-Wert entspricht, klappt das. Bei der Ausgeabe in eine Long-Variable aber nicht. Was mache ich falsch?
Hier mal der Test-Code. Das Ganze findet auf einem Mega mit ATmel1280 statt, daher ist der Int 16Bit lang.
Den String hab ich mal auf 32 Bits verlängert (statt der 28Bits von der Dekodierung), damit das überhaupt klappt.

String testString = "11111001111001011111100111100101";

int testInt;

char inCharInt[17];
char outCharInt[17];

long testLong = -1L;
char inCharLong[33];
char outCharLong[33];

testString.toCharArray(inCharInt, 17);
strcpy(outCharInt, inCharInt);
testInt = strtol(inCharInt, (char **) NULL, 2);
Serial.print("Binary: ");
Serial.println(outCharInt);
Serial.print("testInt: ");
Serial.println(testInt);

testString.toCharArray(inCharLong, 33);
strcpy(outCharLong, inCharLong);
testLong = strtol(inCharLong, (char **) NULL, 2);
Serial.print("Binary: ");
Serial.println(outCharLong);
Serial.print("testLong: ");
Serial.println(testLong);

Als Ausgabe erhalte ich folgendes:
Binary: 1111100111100101
testInt: -1563
Binary: 1111100111100101111110011110010
testLong: 2096299250

Das höchste Bit wird als Vorzeichen interpretiert. Bei long wäre das das 31. Bit, bei 28 aber das 27. Bit. Man kann dann entweder das bekannte Vorzeichen-Bit in die unbelegten Bits bis 31 kopieren, oder die Zahl vor der Konvertierung um die fehlenden 4 Bits nach links schieben.

(deleted)

Statt

1111100111100101111110011110010

solltest du mal 32 Bit probieren:

11111100111100101111110011110010

Nach meiner Zählung fehlt ein Bit ?

@Peter: in strtol( ...  ,2);

ich bekomme von einer etwas umständlichen Dekodierung einen binären String zurrück, der 28 Bits enthält

Kann man da evtl ansetzen?

@michael: Der String hatte 32 Bit (siehe Deklaration). Warum der in der Ausgabe nun auf 31 gekürzt wird, ist ein weiteres Rätsel für mich. In der Konvertierung von Srting zu char Array hänge ich immer ein char extra dran für den Termination Character, daher müsste eigentlich in dem Code 32 Bits als Ausgabe rauskommen...
Ich weiß leider nicht, wie ich die Ausgabe mit dem Bit-String ändern soll. Das übersteigt meine Fähigkeiten wohl...

(deleted)

Du hast recht, es sind 32 Zeichen.

Allerdings liegt es wohl nicht daran, denn auch die Hex-Darstellung wird nach 2147483647 (0x7FFFFFFF) gewandelt

String testString = "11111001111001011111100111100101";
char test2[] = "F9E5F9E5";
void setup() {
  Serial.begin(9600);
  const char* cp = testString.c_str();
  Serial.println(strlen(cp)); // 32
  long l = strtol(cp, NULL, 2);
  Serial.println(l);
  l = strtol(test2, NULL, 16); 
  Serial.println(l);
}
void loop() { }

Nach Lesen der Doku ist das mit dem Zweierkomplement und strtol allerdings "so eine Sache".

char test2[] = "-101111001011111100111100101"; // das liefert negative Werte !

@michael: Das gibt mir den Wert aber auch nicht als Zweierkomplement raus.
Merkwürdig, dass die Funktion, die extra für Long Zahlen ist, genau hier nicht funktioniert. Bei Integern klappts damit ja.
Man müsste ja eigentlich auch einfach den Wert regulär umwandeln können, also ohne Vorzeichen, ihn dann invertieren und +1. Klappt aber auch nicht. Wenn ich den Wert invertiere, wird einfach nur das Vorzeichen geändert.

Binary Int: 1111100111100101
testInt: -1563
Binary Long: 11111001111001011111100111100101
testLong: -2147483647

String testString = "11111001111001011111100111100101";

int testInt;

char inCharInt[17];
char outCharInt[17];

long testLong = -1L;
char inCharLong[33];
char outCharLong[33];

testString.toCharArray(inCharInt, 17);
strcpy(outCharInt, inCharInt);
testInt = strtol(inCharInt, (char **) NULL, 2);
Serial.print("Binary Int: ");
Serial.println(outCharInt);
Serial.print("testInt: ");
Serial.println(testInt);

testString.toCharArray(inCharLong, 33);

strcpy(outCharLong, inCharLong);
testLong = strtol(inCharLong, (char **) NULL, 2);
testLong = ~testLong;
testLong = testLong +1;
Serial.print("Binary Long: ");
Serial.println(outCharLong);
Serial.print("testLong: ");
Serial.println(testLong);

Das ständige Kopieren ist übrigens überflüssig. Du machst mit toCharArray() schon eine Kopie. Und auch das kann man sich sparen. Siehe c_str()

@Serenifly: Stimmt, das war aus vorigen Versuchen übrig geblieben. Ändert aber nix...

Habt Ihr noch eine andere Idee, wie das klappen könnte? Den binären String zerlegen oder so?
Mir fällt auch gerade auf, dass die von strtol ausgegebene Zahl dem maximalen Wert eines Long Wertes entspricht.

leclerke:
Habt Ihr noch eine andere Idee, wie das klappen könnte? Den binären String zerlegen oder so?
Mir fällt auch gerade auf, dass die von strtol ausgegebene Zahl dem maximalen Wert eines Long Wertes entspricht.

#include <Streaming.h>


void setup() 
{
  Serial.begin(9600);
  Serial << "Start: "<< __FILE__ << endl;
  
  char text[] ="1111111111111111111111111110"; // 28 Bit signed -2
  
  Serial << "strlen: "<< strlen(text) << endl;
   
  long a = strtol(text,nullptr,2);// wandlung zu long
  
  a|=1ul<<27&a?0b1111ul<<28:0; // vorzeichen auf 32Bit erweitern

  Serial << "a:  "<< a << endl; // zeigt -2

}

void loop() 
{

}

EDIT:
Code geändert.
Jetzt mit Vorzeichen Erweiterung für die 28Bit zu 32Bit Konvertierung.

Ähm, wie kann dass denn klappen? Also es klappt, aber warum ausgerechnet mit der string to UNSIGNED long?

Habe den Code im Beitrag geändert, damit er eher deinen Anforderungen entspricht.
Auch der Umweg über unsigned entfällt so.

leclerke:
Ähm, wie kann dass denn klappen? Also es klappt, aber warum ausgerechnet mit der string to UNSIGNED long?

Weil beide Funktionen dummerweise kein Zweierkomplement lesen können!
strtol() erwartet bei negativen Zahlen ein Vorzeichen!
Serial << "v: "<< strtol("-10",nullptr,2) << endl; // zeigt -2

Mit strtoul() bekommt man die 32 Bit überhaupt erst unverfälscht in den Speicher.
Der cast nach long übernimmt dann das umtaufen auf Vorzeichen behaftet


Aber wie schon gesagt, mit der "neuen" Variante ist alles besser.
Die 28 Bit machen auch bei strtol() keine Probleme,
Nur das Vorzeichen muss angepasst/repariert werden.

Oh man, das muss ich mir morgen noch mal ansehen. Ich hab schon einen Knoten im Kopf. Aber vielen Dank schon mal! Es geht ja jetzt!

#include <Streaming.h>

/**
 * long strXBitToLong<bit>(const char* str, char** endptr)
 * 
 * Wandelt Zweierkomplement Binärstrings zu long
 * Der Parameter bit übergibt die Anzahl relevanter
 * Binärstellen in der Zeichenkette
 * 
 * 
 */

template<byte bit> // allgemeiner Fall
long strXBitToLong(const char* str, char** endptr)
{
  static_assert(bit >=  2, "min  2 Bit ");
  static_assert(bit <= 32, "max 32 Bit ");
  constexpr long maske = UINT32_MAX << (bit - 1); // Vorzeichen Maske
  long value = strtol(str,endptr,2); // Wandlung zu long 
  if(value&maske) // Vorzeichenerweiterung notwendig??
    value|=maske; // dann Vorzeichenerweiterung auf 32Bit
  return value;
}

template<> // Spezialisierung
long strXBitToLong<32>(const char* str, char** endptr)
{
  return strtoul(str,endptr,2); // Wandlung zu long 
}


char text[] ="1111111111111111111111111110"; // 28 Bit signed -2

void setup() 
{
  Serial.begin(9600);
  Serial << "Start: "<< __FILE__ << endl;
    
  Serial << "strlen: "<< strlen(text) << endl;
 
  Serial << "Gewandelt:  "<< strXBitToLong<28>(text,nullptr) << endl; // zeigt -2
}

void loop() 
{

}