Zahl für LCD/Serial/SD/... 'zurechtschneiden`

Hallo,

suche bzw. bastle an einer Funktion zum "zurecht schneiden" von Zahlen (keine floats) fürs LCD oder Serial oder... Habe bisher noch nicht ganz das rechte gefunden und es mal selbst probiert, aber zum einen tuts noch nicht ganz richtig (mit der Nachkommazahl) und ist sicher nicht optimal vom Code/Ram.

So habe ich mir die Funktion vorgestellt:

Input:
z... signed long Zahl
t... Teiler, gibt Anzahl der Nachkommastellen an
vk... Vorkommastellen, wird ggf. mit '0' oder 'LEERZEICHEN' aufgefüllt
nk... Nachkommastellen die angezeigt werden sollen

Output:
String... der fertige string zum senden an Serial / LCD / SD / ...

BSP1: z = 1001; t = 3; vk = 4; nk = 3; String: 0001,001
BSP2: z = -12345; t = 2; vk = 3; nk = 3; String: -123,450
BSP3: z = 12345; t = 4; vk = 3; nk = 1; String: 012,3

Würde mich über Anregungen und Tipps freuen. Vielen Dank!

Hier mal mein bisheriger (noch fehlerhafter) Code:

//### Long2String L2S() #######################################################
// Zahl, Teiler<0..6>, Vor-,NachkommaStellen <0..10>, Füllzeichen, Trennzeichen
//### Long2String L2S() #######################################################
String L2S(long z, byte t, byte vk,byte nk, char c, char k){ // Zahl, Teiler, Vor-,Nachkommastellen, Füll-, Trennzeichen
  String out; u_long teiler = 1; long z1; bool neg = 0; byte t2 = t; int st = 1;
  
  SPrL(); SPr("Z: "); SPrL(z);    //DEBUG
  while(t--){                           //Potenz von Teiler bilden
    teiler *= 10;
  }
  
  if(z<0){      // Test ob negative Zahl
    z *= -1;    // pos. Zahl
    vk--;       // Eine Vorkommastelle weniger wegen "-"
    neg = 1;    // negativ Flag setzen
  }

  //Gesamtstellenanzhal ermitteln
       if(z>999999999) st = 10;
  else if(z>99999999) st = 9;
  else if(z>9999999) st = 8;
  else if(z>999999) st = 7;
  else if(z>99999) st = 6;
  else if(z>9999) st = 5;
  else if(z>999) st = 4;
  else if(z>99) st = 3;
  else if(z>9) st = 2;
  //else        st = 1;

  if(t2) {
    z1 = z / teiler;                      // Vorkommazahl (VKZ)
    z %= teiler;                          // Nachkommazahl (NKZ), nur wenn t>0
  SPr("NKZ: "); SPrL(z);    //DEBUG
  }
  else{
    z1 = z;
  }
   
  switch (vk)
    { case 10:  if(z1<1000000000) out+=c;  //Serial.print(" 10,"); 
      case 9:   if(z1<100000000)  out+=c;  //Serial.print(" 9,"); 
      case 8:   if(z1<10000000)   out+=c;  //Serial.print(" 8,"); 
      case 7:   if(z1<1000000)    out+=c;  //Serial.print(" 7,");  
      case 6:   if(z1<100000)     out+=c;  //Serial.print(" 6,");  
      case 5:   if(z1<10000)      out+=c;  //Serial.print(" 5,");    // KEIN break; !
      case 4:   if(z1<1000)       out+=c;  //Serial.print(" 4,");  
      case 3:   if(z1<100)        out+=c;  //Serial.print(" 3,");  
      case 2:   if(z1<10)         out+=c;  //Serial.print(" 2,");  
      default: break;
    }
  
  if(neg) out += String('-');
  out += String(z1);                      // "leer-zeichen" + VKZ
  
  if(nk && t2){                        // gibt es eine NKZ?
    
    out += String(k);                     // Kommazeichen

    // Potenz der Nachkommazahl
    byte t = nk-1;                              // mit 100 statt 1000 testen!
    teiler = 1;
    while(t--){                           
    teiler *= 10;
    }
    SPr("Teiler: "); SPrL(teiler);    //DEBUG
    
    //Test ob NKZ kleiner als Potenz, und wievielkleiner
    byte cnt_0 = 0;
    while(t>9){                                 // >=10, also mehrstellig

      // wenn NKZ < Potenz t, dann add '0'
      if(z < t){
        out += String('0');
        cnt_0++;
      }
      else{break;}
      t /= 10;                                  // Potenz verringern um 10
    }
    SPr("cnt_0: "); SPrL(cnt_0);    //DEBUG

    // Add NKZ
    //out += String(z);
    
    String str1;
    str1 = String(z);                           // NKZ
    str1 = str1.substring(0,(nk-cnt_0));         // NKZ ggf. abschneiden wenn zu lang
    SPr("str1 cuted: "); SPrL(str1);    //DEBUG

    out += str1;


    // //Verhältnis gesamt Stellen und Teiler
    // SPr("t2: "); SPrL(t2);    //DEBUG
    // SPr("st: "); SPrL(st);    //DEBUG
    // int delta = t2 - st;

    // SPr("delta: "); SPrL(delta);    //DEBUG
    // byte nullen = 0;
    
    // if(delta > 0) nullen = delta;
    // int i = constrain(nullen, 0, nk); //i 0...Nachkommastellen
    // int i2 = i;
    // SPr("i: "); SPrL(i);    //DEBUG
    // while(i--){
    //   out += String('0');
    // }

    // if(i2 < nk){
    //   int frei = nk-i2;       // noch freie NackkommaStellen
    //   if(frei < 0) frei = 0;
    //   if(frei){               // Nur Stubstring zurechtschneiden, wenn noch was frei
    //     String str1;
    //     str1 = String(z);
    //     out += str1.substring(0,frei);     // Substrings
    //   }
    //}//if(i2 < nk){
  }// if(nk && t2){
  return out;
}// L2S

jim_beam:
BSP3: z = 12345; t = 4; vk = 3; nk = 1; String: 012,3

Also entweder habe ich ein Verständnisproblem mit dem Teiler t und was dieser bedeuten soll. Nach Deinen Erklärungen würde der Teiler 4 die Zahl auf 1,2345 herunterteilen und die Anzeige mit drei Vorkomma und einer Nachkommastelle wäre dann 001,2 (und nicht 012,3).

Oder Dein Beispiel ist falsch.

Für den Fall, dass Du Dir keine drei fehlerfreien Beispiele Deiner Funktion ausdenken kannst, möchte ich Dich fragen: Findest Du nicht, dass Dir diese Funktion etwas zu komplex geraten ist, und dass man vielleicht Dinge wie "runden auf eine bestimmte" Stellenzahl auf eine zweite Funktion auslagern könnte?

Und für den Fall, dass Beispiel 3 korrekt ist, rechne mir mal vor, wie Du mit den gegebenen Zahlenwerten auf den Anzeige-String 012,3 kommst! Ich persönlich kann eine Funktion nur dann erstellen, wenn die gewünschte Ausgabe nachvollziehbar etwas mit den Eingabeparametern zu tun hat, und diese Nachvollziehbarkeit fehlt mir bei BSP3 momentan etwas.

Hallo,

guck dir mal die Funktion/Befehl dtostrf an.

grob erklärt:
dtostrf(Input, n, n, Output);
// float Input Variable, Gesamtstellen inkl. Komma, Nachkommastellen, Output char string

Edit:
nur wenn es keine floats sein sollen wie du schreibst, wofür sind dann die Kommastellen? Bin irritiert.

Ansonsten guckste Dir mal die Funktion/Befehl snprintf an. Damit kann man Ganzzahlen mit führenden Nullen erzeugen. Am Ende ist das wieder ein string.

Doc_Arduino:
Edit:
nur wenn es keine floats sein sollen wie du schreibst, wofür sind dann die Kommastellen? Bin irritiert.

Hast Du mit Gleitkommazahlen schon mal versucht, irgendwelche Zahlen mit mehr als 6-7 Stellen genau darzustellen?

Mal ausgehend von der Zahl 20000,000 die Aufgabe:
Zähle die letzte angezeigte Stelle um 1 hoch (also die Zahl selbst um 0,001) und zeige nacheinander an:
20000,000
20000,001
20000,002
20000,003
20000,004
Wie geht das mit Gleitkommazahlen und dtostrf()?

Hallo,

komm mal wieder runter.

Er möchte Zahlen formatieren für eine Anzeige. Soweit klar. Was ich noch nicht verstehe ist, wofür er Kommastellen benötigten wenn es keine floats sein sollen die er formatieren möchte.

Wegen Deiner Frage.

float _Input = 200.0;
char _Output[9];            

void setup()  {
  
 Serial.begin(38400);    
}
  
void loop()  {  
 
   dtostrf(_Input, 8, 3, _Output); 
   _Input = _Input+0.001;
   Serial.println(_Output); 
}

jurs:
Also entweder habe ich ein Verständnisproblem mit dem Teiler t und was dieser bedeuten soll. Nach Deinen Erklärungen würde der Teiler 4 die Zahl auf 1,2345 herunterteilen und die Anzeige mit drei Vorkomma und einer Nachkommastelle wäre dann 001,2 (und nicht 012,3).

Jap vollkommen richtig, mein Beispiel3 ist falsch, und ja ich bin zu blöd 3 korrekte Bespiele ins Forum zu schreiben :fearful:

aber habe es dann doch noch so hinbekommen wie ichs gerne hätte :smiley: .

Doc_Arduino:
Was ich noch nicht verstehe ist, wofür er Kommastellen benötigten wenn es keine floats sein sollen die er formatieren möchte.

Warum das ganze mit dem Komma, wenns Ganzzahlen sind: glaube man nennt es Festkommaarithmetik. Um die "Probleme" von Floats zu umgehen, rechne ich z.B. eine Spannung von 1,23V als int 123 oder 33,6Ah als long
336000 [10*mAh]. Nur beim Anzeigen/Speichern ist das unpraktisch.

Jetzt bin ich erst mal froh, das es so funktioniert wie es soll, der nächste Schritt ist es den Code zu optimieren. Vielen Dank für die Vorschläge, werde ich mir die Tage mal genauer ansehen.

Doc_Arduino:
Wegen Deiner Frage.

Du schummelst in Deinem Code, weil die formatierten Zahlen nicht mehr als 7 signifikante Stellen haben.

Aber teste Deinen geposteten Code mal mit diesem Anfang für Zahlen ab 20000 aufwärts mit 0,001 Auflösung:

float _Input = 20000.0;
char _Output[10];

Ansonsten alles unverändert lassen.
Fällt Dir dann bei den Berechnungen und der Ergebnisformatierung etwas auf?

Hallo,

jurs:
schummeln war nicht beabsichtigt, war nur so das ich das array auf 9 hatte. Ja okay, mit mehr Gesamtstellen wird es ungenau. Es ging aber um seine Formatierung und wir wußten bis jetzt beide nicht genau was er formatieren möchte. Ich habe nur Vorschläge gemacht.

Jetzt wo wir wissen das Du mit Ganzzahlen arbeiten möchtest, wird es einfacher. Du kannst das alles mit snprintf erschlagen. Du mußt nur Deine Zahl in den Teil vorm Komma und in den Teil nachdem Komma zerlegen. Beides Ganzzahlen.

snprintf_P(_Output, 9, PSTR("%04d.%03d"),_Vorkomma,_Nachkomma);
// 4 stellig vorm Komma, 3 stellig nach Komma inkl. führenden Nullen.

Die 9 muß genauso so groß sein wie der _Output string.
Oder Du ermittelst die array Länge automatisch oder gibts für beides eine Größenvariable vor die nur an einer Stelle im Code ändern müßtest.

zum Bsp ergibt sowas ...

int _Vorkomma = 200;
int _Nachkomma;
# define AL 9        // Array Länge, maximale Zeichenanzahl + 1
char _Output[AL];           

void setup()  {
 
 Serial.begin(38400);   
}
 
void loop()  { 
 
   snprintf_P(_Output, AL, PSTR("%04d,%03d"),_Vorkomma,_Nachkomma);
   _Nachkomma++;
   Serial.println(_Output);
}

... diese Anzeige/Ausgabe, statt dem Punkt kannste auch das Komma nehmen, vollkommen egal

0200.001
0200.002
0200.003
0200.004
0200.005

Da mir persönlich die führende Null nicht gefällt, außer bei Zeitangaben, würde ich das so schreiben. Formatiert bleibt das dennoch nur mit unsichtbarer Null.
snprintf_P(_Output, AL, PSTR("%4d,%03d"),_Vorkomma,_Nachkomma);
Ich denke Du siehst den kleinen aber feinen Unterschied.

Doc_Arduino:
snprintf_P(_Output, 9, PSTR("%04d.%03d"),_Vorkomma,_Nachkomma);
// 4 stellig vorm Komma, 3 stellig nach Komma inkl. führenden Nullen.

Wie kann man die Stellenanzahl vor und nach dem Komma varibale gestelten, wenn man eine Funktion folgender Art haben möchte:

String Zahl2String(int Zahl, byte Teiler, byte VK_Stellen, , byte NK_Stellen){

int _Vorkomma   = Zahl / Teiler,
int _Nachkomma  = Zahl % Teiler;
String _Output   = NULL;
String helper_str = "%0" + String(VK_Stellen) + "d.%0" + String(NK_Stellen) +"d";

snprintf_P(_Output, 9, PSTR(helper_str),_Vorkomma,_Nachkomma);

return _Output;
}

Kann den Code leider nicht Test, würde es funktionieren?
Nutzt die String()-FCN dynamischen Speicher und sollte besser vermieden werden? Wie würde man es dann "besser" mach mit char-Arrays?

char* Zahl2char(int Zahl, byte Teiler, byte VK_Stellen, , byte NK_Stellen){

int _Vorkomma   = Zahl / Teiler,
int _Nachkomma  = Zahl % Teiler;
char[12] _Output   = {0};

// so geht es dann ja nicht mehr, benötigt eine strcopy()-Funktion um den string entsprechend zusammen zu kopieren
char[18] helper         = "%0" + String(VK_Stellen) + "d.%0" + String(NK_Stellen) +"d";

snprintf_P(_Output, 9, PSTR(helper_str),_Vorkomma,_Nachkomma);

return _Output;
}

So viele Fehler....

Kann den Code leider nicht Test, würde es funktionieren?

Nein. Du kannst kein String Objekt an printf() übergeben! Du kannst vielleicht c_str() verwenden um an das interne char Arrays des Objekts zu kommen

Aber wie willst du eine Variable die zur Laufzeit erzeugt wird per PSTR() ins Flash schreiben?

Nutzt die String()-FCN dynamischen Speicher

Ja. Und das ist keine einfache Funktion, sondern ein Konstruktor um ein Objekt zu erzeugen

Wenn du schon snprintf() verwendest, wieso baust du dann nicht den Format String auch damit zusammen? String Objekte sind hier absolut unnötig

Dann machst du noch einen katastrophalen Fehler:
Du kannst keine Zeiger oder Referenzen auf lokale Variablen zurückgeben!!

Wenn man ein Array zurückgeben will, muss man dieses als Parameter übergeben:

char* func(char* buffer, int length)
{
    snprintf(buffer, length....);

    return buffer;
}

So wie das auch die C String Funktionen machen

jim_beam:
Wie kann man die Stellenanzahl vor und nach dem Komma varibale gestelten, wenn man eine Funktion folgender Art haben möchte:

Vom Prinzip her funktioniert diese Idee: Man erzeugt zuerst einen variablen Formatierungsstring, den man danach dann für die gewünschte Formatierung verwendet.

Im Detail funktioniert das natürlich nicht mit "String"-Objekten und Deiner Pluszeichen-Bastelei, sondern es funktioniert mit einer zweimaligen Anwendung der sprintf()/snprintf() Funktion:

  • erste Anwendung von snprintf() ==> Formatierungsstring erzeugen
  • zweiten Anwendung von snprintf() ==> Mit dem Formatierungsstring die gewünschte Formatierung erzeugen

Wenn Du oben nicht "aber habe es dann doch noch so hinbekommen wie ichs gerne hätte" gepostet hättest, hätte ich dafür am 27. Juli abends fertigen Code gepostet, den ich hier in einem IDE-Fenster nebenbei entwickelt hatte und den ich hier "fast" schon fertig hatte.

Aber nachdem ich dann bis auf kleine Restarbeiten fast fertig mit dem Code war, nachdem Du bestätigt hattest dass Dein Beispiel-3 falsch von Dir gepostet worden war, und nachdem ich sah, dass Du bereits gepostet hattest "habe es dann doch noch so hinbekommen wie ichs gerne hätte", habe ich es mir geschenkt, ein völlig überflüssiges Posting zu einem vom Fragesteller selbst gelösten Problem zu verfassen, habe meinen dafür entwickelten Code mit der Option "Schließen - nicht speichern" in den Orkus entsorgt und keine neue Antwort mehr zu dem bereits gelösten Problem verfasst. Aber da wäre genau das drin gewesen: Eine erste Formatierung des Formatierungsstrings, der dann die Platzhalterformatierung für variable Stellenzahlen enthält und dann im Nachgang die eigentliche Formatierung.

Wie gesagt, mußt Du dazu snprintf() doppelt anwenden.
Und bei der ersten Anwendung muß der gewünschte Formatierungsstring erzeugt werden, wie z.B. "%04d,%03d" oder "%05d,%02d" oder wie auch immer, der dann für die eigentliche Formatierung im zweiten Schritt verwendet wird.

Warum suchst Du denn jetzt schon wieder Code für die Lösung eines Problems, das Du selbst bereits genau so gelöst hast, wie Du es gerne hättest? Ist Dir Deine eigene Lösung nach 10 Tagen nicht mehr elegant genug und Du suchst deshalb nach einer eleganteren Alternative und möchtest das Problem noch einmal auf andere Art angehen?

Serenifly:
So viele Fehler....
Aber wie willst du eine Variable die zur Laufzeit erzeugt wird per PSTR() ins Flash schreiben?

:blush: In der Tat, ein sehr dummer Fehler von mir, habe nicht aufgepasst und die Flash Variante erwischt!

Serenifly:
Dann machst du noch einen katastrophalen Fehler:
Du kannst keine Zeiger oder Referenzen auf lokale Variablen zurückgeben!!

Und gleich noch den nächsten Fehler der zum Rest führt. :frowning:
Danke für die Hinweise!!

jurs:
Wenn Du oben nicht "aber habe es dann doch noch so hinbekommen wie ichs gerne hätte" gepostet hättest, hätte ich dafür am 27. Juli abends fertigen Code gepostet, den ich hier in einem IDE-Fenster nebenbei entwickelt hatte und den ich hier "fast" schon fertig hatte.

Aber nachdem ich dann bis auf kleine Restarbeiten fast fertig mit dem Code war, nachdem Du bestätigt hattest dass Dein Beispiel-3 falsch von Dir gepostet worden war, und nachdem ich sah, dass Du bereits gepostet hattest "habe es dann doch noch so hinbekommen wie ichs gerne hätte", habe ich es mir geschenkt, ein völlig überflüssiges Posting zu einem vom Fragesteller selbst gelösten Problem zu verfassen, habe meinen dafür entwickelten Code mit der Option "Schließen - nicht speichern" in den Orkus entsorgt und keine neue Antwort mehr zu dem bereits gelösten Problem verfasst. Aber da wäre genau das drin gewesen: Eine erste Formatierung des Formatierungsstrings, der dann die Platzhalterformatierung für variable Stellenzahlen enthält und dann im Nachgang die eigentliche Formatierung.

Vielen Danke, dass Du Dir so viel Arbeit machst um hier (mir) zu Helfen!! :slight_smile:

jurs:
Warum suchst Du denn jetzt schon wieder Code für die Lösung eines Problems, das Du selbst bereits genau so gelöst hast, wie Du es gerne hättest? Ist Dir Deine eigene Lösung nach 10 Tagen nicht mehr elegant genug und Du suchst deshalb nach einer eleganteren Alternative und möchtest das Problem noch einmal auf andere Art angehen?

Ja. Ich bin kein Programmierer und "Probieren geht über studieren" also einfach mal los legen. Geht beim Programmieren ja auch recht gut ohne die Gefahr einen großen Schaden anzurichten.
Und wo ich jetzt gesehen habe, wie elegant und vergleichsweise kurz (vom Code) es geht, möchte ich es gerne auch so schön machen. Denn man (ich) kann ja nur dazu lernen. Würde tatsächlich auch gerne mal einen systematischen Kurs für embeded C (++) besuchen, um nicht mit so viel Halbwissen umher zu irren.

PS: Vielen Dank für die konstruktiven Hinweise, ich denke ich weiß jetzt erst mal genug wie es vom Prinzip her geht und kann es selbst weiter zu versuchen.