So verstecken Sie die führende Null in printf

Lesezeit: 8 Minuten

Benutzer-Avatar
Christian Mann

Die folgenden Ausgaben 0.23. Wie bekomme ich es einfach ausgegeben .23?

printf( "%8.2f" , .23 );

  • Was erhalten Sie, wenn Sie “%.2f” verwenden? Ich habe seit vielen Jahren nicht mehr in C programmiert.

    – MJB

    7. April 2010 um 20:40 Uhr


  • Subtrahiere 0 davon 😉

    – Wirbelwind

    7. April 2010 um 20:43 Uhr

Die C-Norm sagt das für die f und F Fließkommaformatbezeichner:

Wenn ein Dezimalpunktzeichen erscheint, steht davor mindestens eine Ziffer.

Ich denke, wenn Sie nicht möchten, dass eine Null vor dem Dezimalkomma erscheint, müssen Sie wahrscheinlich so etwas wie use tun snprintf() um die Zahl in eine Zeichenfolge zu formatieren, und entfernen Sie die 0 wenn die formatierte Zeichenfolge mit “0” beginnt. (und ähnlich für “-0”). Übergeben Sie dann diese formatierte Zeichenfolge an unsere echte Ausgabe. Oder sowas ähnliches.

Benutzer-Avatar
Jérôme WAGNER

Es ist nicht möglich, dies nur mit zu tun printf. Die Dokumentation für printf sagt:

f  - "double" argument is output in conventional form, i.e.
     [-]mmmm.nnnnnn
     The default number of digits after the decimal point is six,
     but this can be changed with a precision field. If a decimal point
     appears, at least one digit appears before it. The "double" value is
     rounded to the correct number of decimal places.

Beachten Sie das Wenn ein Dezimalpunkt erscheint, steht davor mindestens eine Ziffer.

Daher scheint es, dass Sie Ihren eigenen Formatierer von Hand codieren müssen.

Benutzer-Avatar
jfs

double f = 0.23;

assert(f < 0.995 && f >= 0);  
printf(".%02u\n" , (unsigned)((f + 0.005) * 100));

  • Das ist die Antwort. Einfach, effizient. “%2d” funktioniert auch, wenn es in ein (int) umgewandelt wird. Nette Geste beim Hinzufügen von .005 zum Runden, wie es %.2f tut.

    – Brent Faust

    20. Februar 2014 um 8:06 Uhr


  • @Brent Foust scheitert für 0.995 < f < 1.0 und für 0.0 < f < 0.095

    – chux – Wiedereinsetzung von Monica

    5. Oktober 2015 um 14:21 Uhr


  • @chux: .%02u sollte beheben 0 < f < 0.095 und wenn 1 > f > 0.995 dann gibt es keine führende Null. Genau genommen gibt es viele Werte wo +0.005 Code unterscheidet sich von %.2f (neben führenden Nullen) zB, 0.625 -> .63 vs. 0.62

    – jfs

    5. Oktober 2015 um 16:43 Uhr


  • Stimmen Sie zu, dass die Änderungen mit den Bedenken der Kommentare fertig werden. Mit (f + 0.005) * 100)würde nicht (f *100 + 0.5) Minimieren Sie die halben Fallunterschiede?

    – chux – Wiedereinsetzung von Monica

    5. Oktober 2015 um 16:51 Uhr

  • @chux: Ich sehe keine signifikante Verbesserung, z. B. beim Aufzählen von 1/1000-Erträgen 41 abweichende Fälle für +0.005 vs. 40 Fälle für +0.5.

    – jfs

    5. Oktober 2015 um 17:01 Uhr

Benutzer-Avatar
Ivan Schwarz

Konvertieren Sie es einfach in eine ganze Zahl mit der erforderlichen Genauigkeit

double value = .12345678901; // input
int accuracy = 1000; // 3 digit after dot
printf(".%03d\n", (int)(value * accuracy) );

Ausgabe:

.123

Beispielquelle auf Pastebin

Benutzer-Avatar
Benutzer311396

#include <stdio.h>

static void printNoLeadingZeros(double theValue)
{
   char buffer[255] = { '\0' };

   sprintf(buffer, "%.2f", theValue);

   printf("%s\n", buffer + (buffer[0] == '0'));
}

int main()
{
   double values[] = { 0.23, .23, 1.23, 01.23, 001.23, 101.23 };
   int n           = sizeof(values) / sizeof(values[0]);
   int i           = 0;

   while(i < n)
      printNoLeadingZeros(values[i++]);

   return(0);
}

  • Wird das nicht sizeof(values) die Größe des Zeigers zurückgeben, nicht das tatsächliche Array?

    – Christian Mann

    8. April 2010 um 3:16 Uhr

  • @ChristianMann: Hängt davon ab, ob Sie sich im C- oder C++-Modus Ihres Compilers befinden, denke ich.

    – Ben Voigt

    14. April 2010 um 5:10 Uhr

  • Auf jeden Fall die Größe des Arrays, unabhängig von der Sprache. Arrays sind etwas widerwillig, zu Zeigern zu werden, und tun dies erst, wenn Sie sie dazu ‘zwingen’, indem Sie sie beispielsweise als Parameter an eine Funktion übergeben, die einen Zeiger akzeptiert.

    – Aaron McDaid

    24. Juli 2015 um 13:49 Uhr

  • Gute Antwort, solange theValue ist positiv.

    – chux – Wiedereinsetzung von Monica

    5. Oktober 2015 um 14:46 Uhr

Benutzer-Avatar
Schweizer Franken

Die Standard-C-Bibliothek bietet dies nicht, also müssen Sie es selbst schreiben. Dies ist keine seltene, einmalige Anforderung. Früher oder später müssen Sie ähnliche Funktionen schreiben, um nachgestellte Nullen zu kürzen und Tausendertrennzeichen hinzuzufügen. Es lohnt sich also, nicht nur die gesuchten Bytes der Ausgabe zu erhalten, sondern allgemeiner zu veranschaulichen, wie man eine starke Bibliothek schreibt. Beachten Sie dabei Folgendes:

  1. finde heraus, wie du es nennen willst. So etwas schreibt man einmal, ruft aber millionenfach an, also macht das Telefonieren so einfach wie möglich.

  2. Machen Sie dann die Testsuite, indem Sie alle Alternativen ausprobieren, die Ihnen einfallen

  3. Wenn Sie schon dabei sind, lösen Sie das Problem einfach für immer, damit Sie nie wieder darauf zurückkommen müssen (z. B. Breite und Genauigkeit nicht fest codieren, weitermachen und Versionen für Leading-Plus, E-Format usw. erstellen )

  4. Machen Sie es Thread-sicher, auch wenn Sie keine Threads verwenden (eigentlich ein spezieller Fall von Punkt 3)

Also rückwärts arbeiten: Die Thread-Sicherheit erfordert die Zuweisung von Speicher auf dem Stack, was vom Aufrufer aus erfolgen muss. Das ist nicht schön oder lustig, aber gewöhnen Sie sich einfach daran. Es ist der C-Weg. Formate können Breite, Genauigkeit, einige Flags und einen Konvertierungstyp (f, e, g) haben. Machen wir also Breiten- und Präzisionsparameter. Anstatt die öffentliche API vollständig zu parametrisieren, habe ich einfach mehrere Einstiegspunkte, die im Funktionsnamen angeben, welche Flags und welcher Konvertierungstyp sie verwenden.

Ein kleines Ärgernis ist, dass beim Übergeben von Puffern an Funktionen die Funktion die Größe kennen muss. Aber wenn Sie das zu einem separaten Parameter machen, ist das ein Problem, da 1) der Anrufer es schreiben muss und 2) der Anrufer es falsch verstehen kann. Mein persönlicher Stil besteht also darin, ein Maskierungsmakro zu erstellen, das davon ausgeht, dass der Puffer ein Zeichenarray und kein Zeiger ist, und das sizeof() verwendet, um die Größe an eine ausführlichere Version der Funktion zu übergeben, die die Größe übernimmt.

Hier ist das Mock-up der einfachsten Art, wie ich es nennen kann, mit Testfällen.

(Hinweis COUNT() ist ein Makro, das ich seit Jahrzehnten wöchentlich verwende, um die Anzahl der Elemente in einem Array zu ermitteln. Standard-C hätte so etwas haben sollen.)

(Beachten Sie, dass ich hier einen Dialekt der “ungarischen Notation” verwende. “d” ist ein Double. “a” ist “array of”. “sz” ist ein NUL-terminierter Zeichenfolgenpuffer, während “psz” ein Zeiger auf einen ist. Der Unterschied zwischen diesen beiden besteht darin, dass „sz“ mit COUNT() oder sizeof() verwendet werden kann, um die Array-Größe zu erhalten, während „psz“ dies nicht kann. „i“ ist eine Ganzzahl und die spezifische Variable „i“ wird zum Schleifen verwendet .

double ad[] = { 0.0, 1.0, 2.2, 0.3, 0.45, 0.666, 888.99,
                -1.0, -2.2, -0.3, -0.45, -0.666, -888.99 };
char   szBuf[20];

for ( int i = 0; i < COUNT( ad ); i++ )
    printf( "%s\n", NoLeadingZeroF( 4, 2, ad[i], szBuf ) );

for ( int i = 0; i < COUNT( ad ); i++ )
    printf( "%s\n", NoLeadingZeroPlusF( 4, 2, ad[i], szBuf ) );

Nun, die Versionen “f” und “+f” scheinen sehr ähnlich zu sein, also lassen wir sie beide eine interne Funktion aufrufen. Hier sind die Funktionen, die die Puffergröße übernehmen, und Makros, die es selbst herausfinden. (Parallele Funktionen werden auch für die Formate e und g geschrieben.)

char* NoLeadingZeroFN( int iWidth, int iPrecision, double d, char* szBuf, int iBufLen ) {
  return NoLeadingZeroFmtN( "%*.*f", iWidth, iPrecision, d, szBuf, iBufLen );
}

char* NoLeadingZeroPlusFN( int iWidth, int iPrecision, double d, char* szBuf, int iBufLen ) {
  return NoLeadingZeroFmtN( "%+*.*f", iWidth, iPrecision, d, szBuf, iBufLen );
}


#define NoLeadingZeroF( width, precision, number, buf ) \
        NoLeadingZeroFN( ( width ), (precision ), ( number ), ( buf ), sizeof( buf ) ) 

#define NoLeadingZeroPlusF( width, precision, number, buf ) \
        NoLeadingZeroPlusFN( ( width ), (precision ), ( number ), ( buf ), sizeof( buf ) ) 

Schließlich die (interne) Funktion, die die Arbeit erledigt. Beachten Sie, dass snprintf() unter Windows einen vorangestellten Unterstrich benötigt, aber nicht unter Unix.

char* NoLeadingZeroFmtN( char* szFmt, int iWidth, int iPrecision, double d, char* szBuf, int iBufLen ) {

#ifdef WIN32
  _snprintf( szBuf, iBufLen - 1, szFmt, iWidth, iPrecision, d );
#else
  snprintf( szBuf, iBufLen - 1, szFmt, iWidth, iPrecision, d );
#endif

  // Some snprintf()'s do not promise to NUL-terminate the string, so do it ourselves.
  szBuf[ iBufLen - 1 ] = '\0';

  // _snprintf() returns the length actually produced, IF the buffer is big enough.
  // But we don't know it was, so measure what we actually got.
  char* pcTerminator = strchr( szBuf, '\0' );

  for ( char* pcBuf = szBuf; *pcBuf && *pcBuf != '.'; pcBuf++ )
      if ( *pcBuf == '0' ) {
          memmove( pcBuf, pcBuf + 1, pcTerminator - pcBuf );
          break;
      }

  return szBuf;
}

Die Ausgabe ist:

.00
1.00
2.20
.30
.45
.67
888.99
-1.00
-2.20
-.30
-.45
-.67
-888.99
+.00
+1.00
+2.20
+.30
+.45
+.67
+888.99
-1.00
-2.20
-.30
-.45
-.67
-888.99

Zusätzliche Tests sollten sicherstellen, dass die Funktionen mit zu kleinen Puffern funktionieren.

  • Wird das nicht sizeof(values) die Größe des Zeigers zurückgeben, nicht das tatsächliche Array?

    – Christian Mann

    8. April 2010 um 3:16 Uhr

  • @ChristianMann: Hängt davon ab, ob Sie sich im C- oder C++-Modus Ihres Compilers befinden, denke ich.

    – Ben Voigt

    14. April 2010 um 5:10 Uhr

  • Auf jeden Fall die Größe des Arrays, unabhängig von der Sprache. Arrays sind etwas widerwillig, zu Zeigern zu werden, und tun dies erst, wenn Sie sie dazu ‘zwingen’, indem Sie sie beispielsweise als Parameter an eine Funktion übergeben, die einen Zeiger akzeptiert.

    – Aaron McDaid

    24. Juli 2015 um 13:49 Uhr

  • Gute Antwort, solange theValue ist positiv.

    – chux – Wiedereinsetzung von Monica

    5. Oktober 2015 um 14:46 Uhr

Benutzer-Avatar
Michas

Es sieht so aus, als gäbe es keine einfache Lösung. Ich würde wahrscheinlich so etwas wie den folgenden Code verwenden. Es ist nicht die schnellste Methode, aber es sollte mit vielen verschiedenen Formaten funktionieren. Es behält auch die Anzahl der Zeichen und die Position des Punktes bei.

#include <stdio.h>

void fixprint(char *s)
{
        size_t i;
        i = 1;
        while (s[i]=='0' || s[i]==' ' || s[i]=='+' || s[i]=='-') {
                if (s[i]=='0') s[i]=' ';
                i++;
        }
}

int main()
{
        float x = .23;
        char s[14];
        sprintf(s,"% 8.2f",x);
        fixprint(s);
        printf("%s\n",s);
}

  • Dies funktioniert nicht mit dem, was der einfachste Fall sein sollte sprintf(s,"%.5f",x) — es scheint irgendwo ein Leerzeichen zu erwarten?

    – Jongware

    13. April 2015 um 10:37 Uhr


1374000cookie-checkSo verstecken Sie die führende Null in printf

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy