Vermeiden Sie nachgestellte Nullen in printf()

Lesezeit: 9 Minuten

Benutzeravatar von Gorpik
Gorpik

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 maximal gegebenen Anzahl von Ziffern nach dem Dezimalkomma zu drucken. Wenn ich verwende:

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?

  • Gleitkomma-Ungenauigkeit bedeutet wirklich, dass Sie die Rundung selbst vornehmen sollten. Nehmen Sie eine Variante der Antwort von R und Juha (die die abschließenden Nullen nicht ganz verarbeiten) und korrigieren Sie sie.

    – wnoise

    9. Februar 2011 um 0:56 Uhr

Benutzeravatar von paxdiablo
paxdiablo

Das geht mit dem normalen nicht printf Formatbezeichner. 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 die gesamt numerische Breite so

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

bricht es.

Was du kann tun ist zu sprintf("%.20g") die Zahl in einen String-Puffer und bearbeiten Sie dann den String so, dass er nur N Zeichen nach dem Dezimalkomma enthält.

Angenommen, Ihre Nummer befindet sich in der Variablen num, entfernt die folgende Funktion alle bis auf die erste N Dezimalstellen, entfernen Sie dann die nachgestellten 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 mit dem Abschneideaspekt nicht zufrieden sind (was sich ändern würde 0.12399 hinein 0.123 anstatt es zu runden 0.124), können Sie die bereits von bereitgestellten Rundungsfunktionen verwenden printf. Sie müssen die Zahl nur 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;
}

Der springende Punkt von nDecimals() in diesem Fall ist es, die Feldbreiten korrekt zu berechnen und dann die Zahl mit einem darauf basierenden Formatstring zu formatieren. Der Prüfstand 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 an übergeben morphNumericString() um nachgestellte Nullen zu entfernen, indem Sie einfach Folgendes ändern:

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

hinein:

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

(Oder anrufen morphNumericString am Ende von nDecimals aber in diesem Fall würde ich wahrscheinlich nur die beiden zu einer Funktion kombinieren), und Sie erhalten am Ende:

  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

  • Beachten Sie, dass diese Antwort davon ausgeht, dass die Dezimalstelle ist . In manchen Gegenden ist die Dezimalstelle tatsächlich a , Komma.

    Benutzer609020

    9. Februar 2011 um 0:32 Uhr


  • Hier ist tatsächlich ein kleiner Tippfehler – p = strchr (str,’.’); sollte eigentlich p = strchr (s,’.’) sein; Um die Funktion param anstelle der globalen var zu verwenden.

    – n3wtz

    2. Mai 2011 um 14:15 Uhr


  • Die Verwendung zu vieler Ziffern kann zu unerwarteten Ergebnissen führen … zum Beispiel “%.20g” mit 0,1 zeigt Müll an. Was Sie tun müssen, wenn Sie die Benutzer nicht überraschen möchten, ist, mit beispielsweise 15 Ziffern zu beginnen und weiter zu inkrementieren, bis atof gibt den gleichen Wert zurück.

    – 6502

    21. Juli 2015 um 16:36 Uhr

  • @ 6502, das liegt daran, dass es in IEEE754 keine 0,1 gibt 🙂 Dies wird jedoch (zumindest für diesen Wert) seit dem Ergebnis kein Problem verursachen 0.10000000000000000555 wird sein 0.1 beim Zurückziehen. Das Problem kann auftreten, wenn Sie einen Wert haben, der leicht unter der nächsten Darstellung von liegt 42.1 war 42.099999999314159. Wenn Sie das wirklich handhaben wollten, müssten Sie wahrscheinlich basierend auf der letzten entfernten Ziffer runden, anstatt abzuschneiden.

    – paxdiablo

    22. Juli 2015 um 1:20 Uhr


  • @paxdiablo: Es ist nicht erforderlich, dynamisch eine Formatzeichenfolge für zu erstellen printf-ähnliche Funktionen, da sie bereits dynamische Parameter unterstützen (* Sonderzeichen): zum Beispiel printf("%*.*f", total, decimals, x); gibt eine Zahl mit der dynamisch angegebenen Gesamtfeldlänge und Dezimalstellen aus.

    – 6502

    31. August 2015 um 5:57 Uhr

Benutzeravatar von Tomalak
Tomalak

Um die abschließenden Nullen loszuwerden, sollten Sie das Format “%g” verwenden:

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

Nachdem die Frage etwas präzisiert wurde, wurde nicht nur das Unterdrücken von Nullen gefragt, sondern auch die Begrenzung der Ausgabe auf drei Nachkommastellen. Ich denke, das geht nicht allein mit sprintf-Formatzeichenfolgen. Wie Pax Diablo betonte, wäre eine String-Manipulation erforderlich.

  • Siehe die MSDN-Hilfeseite: msdn.microsoft.com/en-us/library/0ecbz014(VS.80).aspx

    – xtofl

    10. November 2008 um 12:49 Uhr

  • @Tomalak: Das macht nicht das, was ich will. Ich möchte eine maximale Anzahl von Nachkommastellen angeben können. 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 immer noch keine Antwort auf mein Problem sehen

    – Gorpik

    10. November 2008 um 12:55 Uhr

  • Der Autor möchte 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

    4. Juni 2013 um 14:58 Uhr


Benutzeravatar von Juha
Juha

Ich mag die Antwort von R. leicht abgeändert:

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

‘1000’ bestimmt die Genauigkeit.

Power auf die 0,5 Rundung.

BEARBEITEN

Ok, diese Antwort wurde ein paar Mal bearbeitet und ich habe den Überblick verloren, was ich vor ein paar Jahren gedacht habe (und ursprünglich hat es nicht alle Kriterien erfüllt). 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 der 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 ein Zweizeiler, da sprintf gibt den String nicht zurück.

  • Dies scheint jedoch keine nachgestellten Nullen zu kürzen? So etwas wie 1,1000 kann er gerne ausspucken.

    – Dekan J

    4. September 2013 um 2:24 Uhr

  • Die Frage und meine Antwort sind nicht mehr die Originale … Ich werde später nachsehen.

    – Juha

    9. September 2013 um 7:11 Uhr

  • Fehlgeschlagene Testfälle: 1.012 mit Formatierer „%.2g“ ergibt 1.012 statt 1.01.

    – wtl

    13. Februar 2014 um 13:54 Uhr

  • @wtl Schöner Fang. Jetzt behoben.

    – Juha

    14. Februar 2014 um 20:32 Uhr

  • @Jeroen: Ich denke, die Floats versagen auch irgendwann, wenn die Zahlen größer werden … aber im Grunde sollte die Typumwandlung in long, long long oder int64 funktionieren.

    – Juha

    10. Oktober 2014 um 18:41 Uhr

Warum nicht einfach so?

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

Benutzeravatar von DaveR
DaveR

Ich suche die Zeichenfolge (beginnend ganz rechts) nach dem ersten Zeichen im Bereich 1 zu 9 (ASCII-Wert 4957) dann null (einstellen 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;
    }
}

Benutzeravatar von Sri Harsha Chilakapati
Sri Harsha Chilakapati

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

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

Es ist leicht programmatisch, aber zumindest lässt es Sie keine Zeichenfolgen manipulieren.

Benutzeravatar von nwellnhof
nwellnhof

Einige der hoch bewerteten Lösungen schlagen vor %g Konvertierungsbezeichner von printf. Das ist falsch, weil es Fälle gibt, in denen %g erzeugt 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 sprintf mit dem %f Konvertierungsspezifizierer und um nachgestellte Nullen und möglicherweise ein Dezimalkomma 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 geht von einem Gebietsschema in C oder US-Englisch aus.

  • −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?

    – Delfin

    13. Juli 2020 um 21:49 Uhr

1423250cookie-checkVermeiden Sie nachgestellte Nullen in printf()

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

Privacy policy