it-swarm.com.de

Vermeiden Sie nachfolgende Nullen in printf ()

Ich stolpere immer wieder über die Formatbezeichner für die Funktionsfamilie printf (). Was ich möchte, ist in der Lage zu sein, ein Double (oder Float) mit einer maximalen Anzahl von Stellen nach dem Komma 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?

102
Gorpik

Dies ist mit den normalen printf -Formatspezifizierern nicht möglich. Der nächste, den Sie bekommen könnten, wäre:

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

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

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

bricht es.

Was Sie können tun, ist, sprintf("%.20g") die Zahl in einen Zeichenkettenpuffer zu schreiben und dann die Zeichenkette so zu manipulieren, dass nur N Zeichen nach dem Dezimalpunkt stehen.

Angenommen, Ihre Nummer ist in der Variablen num enthalten, dann werden mit der folgenden Funktion alle Dezimalstellen außer den ersten N entfernt, und die nachfolgenden Nullen (und der Dezimalpunkt, wenn sie alle Nullen waren) werden entfernt.

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 mit dem Kürzungsaspekt nicht zufrieden sind (der 0.12399 In 0.123 Verwandelt, anstatt ihn auf 0.124 Zu runden), können Sie die bereits von bereitgestellten Rundungsfunktionen verwenden printf. Sie müssen lediglich die Zahl vorab 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;
}

Der springende Punkt von nDecimals() ist in diesem Fall, 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 richtig gerundeten Wert haben, können Sie diesen erneut an morphNumericString() übergeben, 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 rufen Sie morphNumericString am Ende von nDecimals auf, aber in diesem Fall würde ich wahrscheinlich nur die beiden in eine Funktion kombinieren) und Sie erhalten:

  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
80
paxdiablo

Um die nachfolgenden 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 gefragt, sondern auch die Begrenzung der Ausgabe auf drei Dezimalstellen. Ich denke, das geht nicht allein mit Strings im Sprintf-Format. Wie Pax Diablo hervorhob, wäre eine Manipulation der Zeichenfolgen erforderlich.

52
Tomalak

Ich mag die Antwort von R. leicht optimiert:

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

'1000' bestimmt die Präzision.

Potenz zur 0,5-Rundung.

EDIT

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

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

Nun sind alle Kriterien erfüllt:

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

Leider ist diese Antwort zweizeilig, da sprintf die Zeichenfolge nicht zurückgibt.

18
Juha

Was ist mit so etwas (möglicherweise sind Rundungsfehler und Probleme mit negativen Werten aufgetreten, die behoben werden müssen und dem Leser als Übung überlassen bleiben):

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

Es ist ein wenig programmatisch, aber es bringt Sie nicht dazu, Zeichenfolgen zu manipulieren.

2
R..

Ich suche die Zeichenkette (ganz rechts beginnend) nach dem ersten Zeichen im Bereich 1 Bis 9 (ASCII-Wert 49 - 57) Und dann null (auf 0 gesetzt) ​​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;
    }
}
2
DaveR

Eine einfache Lösung, die jedoch die Arbeit erledigt, eine bekannte Länge und Genauigkeit zuweist und die Möglichkeit eines exponentiellen Formats vermeidet (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. Beispiel:

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 umwandeln, 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);
}
1
Iaijutsu

Ich habe in einigen der angegebenen Lösungen Probleme 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);
}
1
magnusviri

Einige der hoch bewerteten Lösungen legen nahe, dass %g Konvertierungsspezifizierer von printf. Das ist falsch, weil es Fälle gibt, in denen %g erzeugt eine wissenschaftliche Notation. Andere Lösungen verwenden Mathematik, um die gewünschte Anzahl von Dezimalstellen zu drucken.

Ich denke, die einfachste Lösung ist die Verwendung von sprintf mit dem %f Konvertierungsspezifizierer und zum manuellen Entfernen von nachgestellten Nullen und möglicherweise einem Dezimalpunkt aus dem Ergebnis. 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.

1
nwellnhof

Leichte Abweichung von oben:

  1. Eliminiert 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 Gefahr eines Überlaufs, seien Sie also vorsichtig

0
David Thornley

Warum nicht einfach so?

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