Vermeiden Sie nachgestellte Nullen in printf ()

107

Ich stolpere immer wieder über die Formatspezifizierer für die printf () - Funktionsfamilie. Ich möchte in der Lage sein, ein Double (oder Float) mit einer maximal angegebenen Anzahl von Stellen nach dem Dezimalpunkt zu drucken. Wenn ich benutze:

printf("%1.3f", 359.01335);
printf("%1.3f", 359.00999);

Ich bekomme

359.013
359.010

Anstelle des gewünschten

359.013
359.01

Kann mir jemand helfen?

Gorpik
quelle
1
Gleitkomma-Ungenauigkeit bedeutet wirklich, dass Sie die Rundung selbst durchführen sollten. Nehmen Sie eine Variante der Antwort von R und Juha (die mit den nachfolgenden Nullen nicht ganz zurechtkommt) und korrigieren Sie sie.
wnoise

Antworten:

88

Dies ist mit den normalen printfFormatspezifizierern nicht möglich. Das nächste, was Sie bekommen könnten, wäre:

printf("%.6g", 359.013); // 359.013
printf("%.6g", 359.01);  // 359.01

aber die ".6" ist also die gesamte numerische Breite

printf("%.6g", 3.01357); // 3.01357

bricht es.

Was Sie können tun , ist auf sprintf("%.20g")die Zahl in einen String - Puffer dann die Zeichenfolge manipulieren , um nur haben N Zeichen hinter dem Komma.

Angenommen, Ihre Zahl befindet sich in der Variablen num, entfernt die folgende Funktion alle bis auf die ersten NDezimalstellen und entfernt dann die nachfolgenden Nullen (und den Dezimalpunkt, wenn sie alle Nullen waren).

char str[50];
sprintf (str,"%.20g",num);  // Make the number.
morphNumericString (str, 3);
:    :
void morphNumericString (char *s, int n) {
    char *p;
    int count;

    p = strchr (s,'.');         // Find decimal point, if any.
    if (p != NULL) {
        count = n;              // Adjust for more or less decimals.
        while (count >= 0) {    // Maximum decimals allowed.
             count--;
             if (*p == '\0')    // If there's less than desired.
                 break;
             p++;               // Next character.
        }

        *p-- = '\0';            // Truncate string.
        while (*p == '0')       // Remove trailing zeros.
            *p-- = '\0';

        if (*p == '.') {        // If all decimals were zeros, remove ".".
            *p = '\0';
        }
    }
}

Wenn Sie nicht zufrieden mit dem Abschneiden Aspekte sind (was machen würde 0.12399in 0.123sie eher als Rundung 0.124), können Sie tatsächlich die Rundung Einrichtungen bereits zur Verfügung gestellt nutzen printf. Sie müssen nur die Zahl vorher analysieren, um die Breiten dynamisch zu erstellen, und diese dann verwenden, um die Zahl in eine Zeichenfolge umzuwandeln:

#include <stdio.h>

void nDecimals (char *s, double d, int n) {
    int sz; double d2;

    // Allow for negative.

    d2 = (d >= 0) ? d : -d;
    sz = (d >= 0) ? 0 : 1;

    // Add one for each whole digit (0.xx special case).

    if (d2 < 1) sz++;
    while (d2 >= 1) { d2 /= 10.0; sz++; }

    // Adjust for decimal point and fractionals.

    sz += 1 + n;

    // Create format string then use it.

    sprintf (s, "%*.*f", sz, n, d);
}

int main (void) {
    char str[50];
    double num[] = { 40, 359.01335, -359.00999,
        359.01, 3.01357, 0.111111111, 1.1223344 };
    for (int i = 0; i < sizeof(num)/sizeof(*num); i++) {
        nDecimals (str, num[i], 3);
        printf ("%30.20f -> %s\n", num[i], str);
    }
    return 0;
}

In nDecimals()diesem Fall geht es darum, die Feldbreiten korrekt zu berechnen und die Zahl dann mit einer darauf basierenden Formatzeichenfolge zu formatieren. Das Testkabel main()zeigt dies in Aktion:

  40.00000000000000000000 -> 40.000
 359.01335000000000263753 -> 359.013
-359.00999000000001615263 -> -359.010
 359.00999999999999090505 -> 359.010
   3.01357000000000008200 -> 3.014
   0.11111111099999999852 -> 0.111
   1.12233439999999995429 -> 1.122

Sobald Sie den korrekt gerundeten Wert haben, können Sie diesen erneut übergeben, morphNumericString()um nachfolgende Nullen zu entfernen, indem Sie einfach Folgendes ändern:

nDecimals (str, num[i], 3);

in:

nDecimals (str, num[i], 3);
morphNumericString (str, 3);

(oder morphNumericStringam Ende aufrufen, nDecimalsaber in diesem Fall würde ich die beiden wahrscheinlich nur zu einer Funktion kombinieren), und am Ende erhalten Sie:

  40.00000000000000000000 -> 40
 359.01335000000000263753 -> 359.013
-359.00999000000001615263 -> -359.01
 359.00999999999999090505 -> 359.01
   3.01357000000000008200 -> 3.014
   0.11111111099999999852 -> 0.111
   1.12233439999999995429 -> 1.122
paxdiablo
quelle
8
Beachten Sie, dass bei dieser Antwort davon .ausgegangen wird, dass die Dezimalstelle lautet. In einigen Regionen ist die Dezimalstelle tatsächlich ein ,Komma.
1
Hier gibt es tatsächlich einen kleinen Tippfehler - p = strchr (str, '.'); sollte eigentlich p = strchr (s, '.') sein; Verwendung des Funktionsparameters anstelle des globalen var.
n3wtz
Die Verwendung zu vieler Ziffern kann zu unerwarteten Ergebnissen führen. Beispiel: "% .20g" mit 0.1 zeigt Müll an. Was Sie tun müssen, wenn Sie die Benutzer nicht überraschen möchten, ist, mit etwa 15 Ziffern zu beginnen und weiter zu erhöhen, bis atofder gleiche Wert zurückgegeben wird.
6502
@ 6502, das ist , weil es gibt nicht so etwas wie 0,1 in IEEE754 :-) Allerdings wird es nicht Ursache eines Problems (zumindest für diesen Wert) , da die resultierenden 0.10000000000000000555sein wird , 0.1wenn abgestreift zurück. Das Problem kann auftreten, wenn Sie einen Wert haben, der leicht darunter liegt, z. B. wenn die nächstgelegene Darstellung von 42.1war 42.099999999314159. Wenn Sie damit wirklich umgehen wollten, müssten Sie wahrscheinlich auf der Grundlage der zuletzt entfernten Ziffer runden, anstatt abzuschneiden.
Paxdiablo
1
@paxdiablo: Es ist nicht erforderlich, dynamisch eine Formatzeichenfolge für printfähnliche Funktionen zu erstellen, da diese bereits dynamische Parameter ( *Sonderzeichen) unterstützen: Beispielsweise wird printf("%*.*f", total, decimals, x);eine Zahl mit der dynamisch angegebenen Gesamtfeldlänge und den Dezimalstellen ausgegeben.
6502
54

Um die nachgestellten Nullen zu entfernen, sollten Sie das Format "% g" verwenden:

float num = 1.33;
printf("%g", num); //output: 1.33

Nachdem die Frage ein wenig geklärt war, wurde nicht nur die Unterdrückung von Nullen gestellt, sondern auch die Ausgabe auf drei Dezimalstellen begrenzt. Ich denke, das geht nicht nur mit Sprintf-Format-Strings. Wie Pax Diablo betonte, wäre eine Manipulation der Saiten erforderlich.

Tomalak
quelle
Siehe die MSDN- Hilfeseite
xtofl
@Tomalak: Das macht nicht was ich will. Ich möchte in der Lage sein, eine maximale Anzahl von Stellen nach dem Dezimalpunkt anzugeben. Dies ist: Ich möchte, dass 1.3347 "1.335" und 1.3397 "1.34" gedruckt werden @xtofl: Ich hatte das bereits überprüft, aber ich kann die Antwort auf mein Problem immer noch nicht sehen
Gorpik
3
Autor will Stil 'f' nicht Stil 'e'. 'g' kann den Stil 'e' verwenden: Aus der Dokumentation: Der Stil e wird verwendet, wenn der Exponent aus seiner Konvertierung kleiner als -4 oder größer oder gleich der Genauigkeit ist.
Julien Palard
20

Ich mag die Antwort von R. leicht optimiert:

float f = 1234.56789;
printf("%d.%.0f", f, 1000*(f-(int)f));

'1000' bestimmt die Genauigkeit.

Leistung auf die Rundung von 0,5.

BEARBEITEN

Ok, diese Antwort wurde einige Male bearbeitet und ich verlor den Überblick, was ich vor ein paar Jahren dachte (und ursprünglich erfüllte sie nicht alle Kriterien). Hier ist also eine neue Version (die alle Kriterien erfüllt und negative Zahlen korrekt behandelt):

double f = 1234.05678900;
char s[100]; 
int decimals = 10;

sprintf(s,"%.*g", decimals, ((int)(pow(10, decimals)*(fabs(f) - abs((int)f)) +0.5))/pow(10,decimals));
printf("10 decimals: %d%s\n", (int)f, s+1);

Und die Testfälle:

#import <stdio.h>
#import <stdlib.h>
#import <math.h>

int main(void){

    double f = 1234.05678900;
    char s[100];
    int decimals;

    decimals = 10;
    sprintf(s,"%.*g", decimals, ((int)(pow(10, decimals)*(fabs(f) - abs((int)f)) +0.5))/pow(10,decimals));
    printf("10 decimals: %d%s\n", (int)f, s+1);

    decimals = 3;
    sprintf(s,"%.*g", decimals, ((int)(pow(10, decimals)*(fabs(f) - abs((int)f)) +0.5))/pow(10,decimals));
    printf(" 3 decimals: %d%s\n", (int)f, s+1);

    f = -f;
    decimals = 10;
    sprintf(s,"%.*g", decimals, ((int)(pow(10, decimals)*(fabs(f) - abs((int)f)) +0.5))/pow(10,decimals));
    printf(" negative 10: %d%s\n", (int)f, s+1);

    decimals = 3;
    sprintf(s,"%.*g", decimals, ((int)(pow(10, decimals)*(fabs(f) - abs((int)f)) +0.5))/pow(10,decimals));
    printf(" negative  3: %d%s\n", (int)f, s+1);

    decimals = 2;
    f = 1.012;
    sprintf(s,"%.*g", decimals, ((int)(pow(10, decimals)*(fabs(f) - abs((int)f)) +0.5))/pow(10,decimals));
    printf(" additional : %d%s\n", (int)f, s+1);

    return 0;
}

Und die Ausgabe der Tests:

 10 decimals: 1234.056789
  3 decimals: 1234.057
 negative 10: -1234.056789
 negative  3: -1234.057
 additional : 1.01

Jetzt sind alle Kriterien erfüllt:

  • Die maximale Anzahl von Dezimalstellen hinter der Null ist festgelegt
  • Nachgestellte Nullen werden entfernt
  • es macht es mathematisch richtig (richtig?)
  • funktioniert (jetzt) ​​auch, wenn die erste Dezimalstelle Null ist

Leider ist diese Antwort zweizeilig, da sprintfdie Zeichenfolge nicht zurückgegeben wird.

Juha
quelle
1
Dies scheint jedoch keine nachgestellten Nullen zu trimmen? Es kann gerne so etwas wie 1.1000 ausspucken.
Dean J
Die Frage und meine Antwort sind nicht mehr die Originale ... Ich werde es später überprüfen.
Juha
1
Fehlgeschlagene Testfälle: 1.012 mit dem Formatierer "% .2g" führt zu 1.012 anstelle von 1.01.
WTL
@wtl Schöner Fang. Jetzt behoben.
Juha
@Jeroen: Ich denke, die Floats versagen auch irgendwann, wenn die Zahlen größer werden ... aber im Grunde sollte Typecasting zu long, long long oder int64 funktionieren.
Juha
2

Ich suche die Zeichenfolge (beginnend ganz rechts) für das erste Zeichen im Bereich 1bis 9(ASCII - Wert 49- 57) dann null(auf 0) jedes Zeichen rechts davon - siehe unten:

void stripTrailingZeros(void) { 
    //This finds the index of the rightmost ASCII char[1-9] in array
    //All elements to the left of this are nulled (=0)
    int i = 20;
    unsigned char char1 = 0; //initialised to ensure entry to condition below

    while ((char1 > 57) || (char1 < 49)) {
        i--;
        char1 = sprintfBuffer[i];
    }

    //null chars left of i
    for (int j = i; j < 20; j++) {
        sprintfBuffer[i] = 0;
    }
}
DaveR
quelle
2

Was ist mit so etwas (kann Rundungsfehler und Probleme mit negativen Werten haben, die debuggt werden müssen und als Übung für den Leser verbleiben):

printf("%.0d%.4g\n", (int)f/10, f-((int)f-(int)f%10));

Es ist leicht programmatisch, aber es bringt Sie zumindest nicht dazu, Saiten zu manipulieren.

R .. GitHub HÖREN SIE AUF, EIS ZU HELFEN
quelle
1

Eine einfache Lösung, die jedoch die Arbeit erledigt, eine bekannte Länge und Präzision zuweist und die Möglichkeit vermeidet, ein exponentielles Format zu verwenden (was ein Risiko darstellt, wenn Sie% g verwenden):

// Since we are only interested in 3 decimal places, this function
// can avoid any potential miniscule floating point differences
// which can return false when using "=="
int DoubleEquals(double i, double j)
{
    return (fabs(i - j) < 0.000001);
}

void PrintMaxThreeDecimal(double d)
{
    if (DoubleEquals(d, floor(d)))
        printf("%.0f", d);
    else if (DoubleEquals(d * 10, floor(d * 10)))
        printf("%.1f", d);
    else if (DoubleEquals(d * 100, floor(d* 100)))
        printf("%.2f", d);
    else
        printf("%.3f", d);
}

Fügen Sie "elses" hinzu oder entfernen Sie sie, wenn Sie maximal 2 Dezimalstellen möchten. 4 Dezimalstellen; etc.

Zum Beispiel, wenn Sie 2 Dezimalstellen wollten:

void PrintMaxTwoDecimal(double d)
{
    if (DoubleEquals(d, floor(d)))
        printf("%.0f", d);
    else if (DoubleEquals(d * 10, floor(d * 10)))
        printf("%.1f", d);
    else
        printf("%.2f", d);
}

Wenn Sie die Mindestbreite angeben möchten, um die Felder ausgerichtet zu halten, erhöhen Sie sie nach Bedarf, z. B.:

void PrintAlignedMaxThreeDecimal(double d)
{
    if (DoubleEquals(d, floor(d)))
        printf("%7.0f", d);
    else if (DoubleEquals(d * 10, floor(d * 10)))
        printf("%9.1f", d);
    else if (DoubleEquals(d * 100, floor(d* 100)))
        printf("%10.2f", d);
    else
        printf("%11.3f", d);
}

Sie können dies auch in eine Funktion konvertieren, bei der Sie die gewünschte Feldbreite übergeben:

void PrintAlignedWidthMaxThreeDecimal(int w, double d)
{
    if (DoubleEquals(d, floor(d)))
        printf("%*.0f", w-4, d);
    else if (DoubleEquals(d * 10, floor(d * 10)))
        printf("%*.1f", w-2, d);
    else if (DoubleEquals(d * 100, floor(d* 100)))
        printf("%*.2f", w-1, d);
    else
        printf("%*.3f", w, d);
}
Iaijutsu
quelle
Nehmen Sie d = 0.0001: dann floor(d)ist 0, also ist der Unterschied größer als 0,000001, DoubleEqualsist also falsch, so dass der Bezeichner nicht verwendet wird "%.0f": Sie sehen nachgestellte Nullen von "%*.2f"oder "%*.3f". Es beantwortet also nicht die Frage.
Cœur
1

Ich habe Probleme in einigen der veröffentlichten Lösungen gefunden. Ich habe dies basierend auf den obigen Antworten zusammengestellt. Es scheint für mich zu funktionieren.

int doubleEquals(double i, double j) {
    return (fabs(i - j) < 0.000001);
}

void printTruncatedDouble(double dd, int max_len) {
    char str[50];
    int match = 0;
    for ( int ii = 0; ii < max_len; ii++ ) {
        if (doubleEquals(dd * pow(10,ii), floor(dd * pow(10,ii)))) {
            sprintf (str,"%f", round(dd*pow(10,ii))/pow(10,ii));
            match = 1;
            break;
        }
    }
    if ( match != 1 ) {
        sprintf (str,"%f", round(dd*pow(10,max_len))/pow(10,max_len));
    }
    char *pp;
    int count;
    pp = strchr (str,'.');
    if (pp != NULL) {
        count = max_len;
        while (count >= 0) {
             count--;
             if (*pp == '\0')
                 break;
             pp++;
        }
        *pp-- = '\0';
        while (*pp == '0')
            *pp-- = '\0';
        if (*pp == '.') {
            *pp = '\0';
        }
    }
    printf ("%s\n", str);
}

int main(int argc, char **argv)
{
    printTruncatedDouble( -1.999, 2 ); // prints -2
    printTruncatedDouble( -1.006, 2 ); // prints -1.01
    printTruncatedDouble( -1.005, 2 ); // prints -1
    printf("\n");
    printTruncatedDouble( 1.005, 2 ); // prints 1 (should be 1.01?)
    printTruncatedDouble( 1.006, 2 ); // prints 1.01
    printTruncatedDouble( 1.999, 2 ); // prints 2
    printf("\n");
    printTruncatedDouble( -1.999, 3 ); // prints -1.999
    printTruncatedDouble( -1.001, 3 ); // prints -1.001
    printTruncatedDouble( -1.0005, 3 ); // prints -1.001 (shound be -1?)
    printTruncatedDouble( -1.0004, 3 ); // prints -1
    printf("\n");
    printTruncatedDouble( 1.0004, 3 ); // prints 1
    printTruncatedDouble( 1.0005, 3 ); // prints 1.001
    printTruncatedDouble( 1.001, 3 ); // prints 1.001
    printTruncatedDouble( 1.999, 3 ); // prints 1.999
    printf("\n");
    exit(0);
}
Magnusviri
quelle
1

Einige der hoch bewerteten Lösungen schlagen den %gKonvertierungsspezifizierer von vor printf. Dies ist falsch, weil es Fälle gibt, in denen %gwissenschaftliche Notation erzeugt wird. Andere Lösungen verwenden Mathematik, um die gewünschte Anzahl von Dezimalstellen zu drucken.

Ich denke, die einfachste Lösung besteht darin, sprintfmit dem %fKonvertierungsspezifizierer zu arbeiten und nachgestellte Nullen und möglicherweise einen Dezimalpunkt manuell aus dem Ergebnis zu entfernen. Hier ist eine C99-Lösung:

#include <stdio.h>
#include <stdlib.h>

char*
format_double(double d) {
    int size = snprintf(NULL, 0, "%.3f", d);
    char *str = malloc(size + 1);
    snprintf(str, size + 1, "%.3f", d);

    for (int i = size - 1, end = size; i >= 0; i--) {
        if (str[i] == '0') {
            if (end == i + 1) {
                end = i;
            }
        }
        else if (str[i] == '.') {
            if (end == i + 1) {
                end = i;
            }
            str[end] = '\0';
            break;
        }
    }

    return str;
}

Beachten Sie, dass die für Ziffern und das Dezimaltrennzeichen verwendeten Zeichen vom aktuellen Gebietsschema abhängen. Der obige Code setzt ein Gebietsschema in C oder US-Englisch voraus.

nwellnhof
quelle
−0.00005 und 3 Dezimalstellen ergeben "−0", was erwünscht sein kann oder nicht. Vielleicht machen Sie "3" auch zu einem Parameter für die Bequemlichkeit anderer?
Delphin
0

Hier ist mein erster Versuch einer Antwort:

Leere
xprintfloat (char * -Format, float f)
{
  char s [50];
  char * p;

  Sprintf (s, Format, f);
  für (p = s; * p; ++ p)
    if ('.' == * p) {
      while (* ++ p);
      while ('0' == * - p) * p = '\ 0';
    }}
  printf ("% s", s);
}}

Bekannte Fehler: Möglicher Pufferüberlauf je nach Format. Wenn "." ist aus einem anderen Grund vorhanden als% f, kann ein falsches Ergebnis auftreten.


quelle
Ihre bekannten Fehler sind die gleichen wie printf () selbst, daher gibt es dort keine Probleme. Aber ich suchte nach einer Formatzeichenfolge, mit der ich tun konnte, was ich wollte, und nicht nach einer programmatischen Lösung, die ich bereits habe.
Gorpik
0

Warum nicht einfach so machen?

double f = 359.01335;
printf("%g", round(f * 1000.0) / 1000.0);
Jim Hunziker
quelle
0

Leichte Abweichung von oben:

  1. Beseitigt den Zeitraum für den Fall (10000.0).
  2. Pausen nach der ersten Periode werden verarbeitet.

Code hier:

void EliminateTrailingFloatZeros(char *iValue)
{
  char *p = 0;
  for(p=iValue; *p; ++p) {
    if('.' == *p) {
      while(*++p);
      while('0'==*--p) *p = '\0';
      if(*p == '.') *p = '\0';
      break;
    }
  }
}

Es besteht immer noch die Möglichkeit eines Überlaufs. Seien Sie also vorsichtig

David Thornley
quelle
0

Ich würde sagen, Sie sollten verwenden printf("%.8g",value);

Wenn Sie verwenden, erhalten "%.6g"Sie für einige Zahlen wie 32.230210 nicht die gewünschte Ausgabe. Es sollte gedruckt werden, 32.23021aber es wird gedruckt32.2302

Ankit Mishra
quelle
-2

Ihr Code rundet aufgrund der ".3" vor dem f auf drei Dezimalstellen

printf("%1.3f", 359.01335);
printf("%1.3f", 359.00999);

Wenn Sie also die zweite Zeile auf zwei Dezimalstellen gerundet haben, sollten Sie sie folgendermaßen ändern:

printf("%1.3f", 359.01335);
printf("%1.2f", 359.00999);

Dieser Code gibt Ihre gewünschten Ergebnisse aus:

359.013
359.01

* Beachten Sie, dass dies davon ausgeht, dass Sie es bereits in separaten Zeilen drucken lassen. Wenn dies nicht der Fall ist, wird es durch Folgendes verhindert, dass es in derselben Zeile gedruckt wird:

printf("%1.3f\n", 359.01335);
printf("%1.2f\n", 359.00999);

Der folgende Programmquellcode war mein Test für diese Antwort

#include <cstdio>

int main()
{

    printf("%1.3f\n", 359.01335);
    printf("%1.2f\n", 359.00999);

    while (true){}

    return 0;

}
TeamXlink
quelle
4
Die Frage war, eine Formatzeichenfolge zu erstellen, bei der nachgestellte Nullen weggelassen werden, um nicht für jeden Fall unterschiedliche Formatzeichenfolgen zu haben.
Christoph Walesch