Wie beschränke ich einen Float-Wert auf nur zwei Stellen nach dem Dezimalkomma in C?

Lesezeit: 8 Minuten

Wie kann ich einen Gleitkommawert (z. B. 37,777779) in C auf zwei Dezimalstellen (37,78) runden?

  • Sie können die Zahl selbst nicht richtig runden, weil float (und double) sind keine dezimalen Gleitkommazahlen, sondern binäre Gleitkommazahlen, sodass das Runden auf Dezimalstellen bedeutungslos ist. Sie können die Ausgabe jedoch runden.

    – Pavel Minaev

    27. August 2009 um 21:49 Uhr

  • Es ist nicht bedeutungslos; es ist ungenau. Es ist ein ziemlicher Unterschied.

    – Brooks Moses

    28. August 2009 um 3:08 Uhr

  • Welche Art von Rundung erwarten Sie? Half-up oder Rundung auf die nächste gerade Zahl?

    – Wahrheitssucher Rangwan

    3. August 2014 um 13:21 Uhr

Benutzeravatar von Dale Hagglund
Dale Hagglund

Wenn Sie die Zahl nur für Ausgabezwecke runden möchten, dann ist die "%.2f" format string ist in der Tat die richtige Antwort. Wenn Sie den Gleitkommawert jedoch tatsächlich für die weitere Berechnung runden möchten, funktioniert so etwas wie das Folgende:

#include <math.h>

float val = 37.777779;

float rounded_down = floorf(val * 100) / 100;   /* Result: 37.77 */
float nearest = roundf(val * 100) / 100;  /* Result: 37.78 */
float rounded_up = ceilf(val * 100) / 100;      /* Result: 37.78 */

Beachten Sie, dass es drei verschiedene Rundungsregeln gibt, die Sie wählen können: Abrunden (dh nach zwei Dezimalstellen abschneiden), auf die nächste Dezimalstelle runden und aufrunden. Normalerweise möchten Sie auf den nächsten runden.

Wie mehrere andere darauf hingewiesen haben, sind diese gerundeten Werte aufgrund der Macken der Gleitkommadarstellung möglicherweise nicht genau die “offensichtlichen” Dezimalwerte, aber sie werden sehr, sehr nahe beieinander liegen.

Für viel (viel!) mehr Informationen zum Runden und insbesondere zu den Regeln zum Aufbrechen von Gleichständen beim Runden auf den nächsten Wert, siehe den Wikipedia-Artikel zum Runden.

  • Kann es geändert werden, um das Runden auf beliebige Genauigkeit zu unterstützen?

    Benutzer472308

    14. Mai 2014 um 20:12 Uhr


  • @slater Wenn Sie “beliebige Genauigkeit” sagen, fragen Sie nach dem Runden auf z. B. drei anstelle von zwei Dezimalstellen oder nach der Verwendung von Bibliotheken, die Dezimalwerte mit unbegrenzter Genauigkeit implementieren? Wenn ersteres der Fall ist, nehmen Sie, wie ich hoffe, offensichtliche Anpassungen an der Konstante 100 vor; Führen Sie andernfalls genau die oben gezeigten Berechnungen durch, nur mit der von Ihnen verwendeten Multi-Präzisions-Bibliothek.

    – Dale Hagglund

    15. Mai 2014 um 7:09 Uhr

  • @DaleHagglung Ersteres, danke. Soll 100 durch pow(10, (int)desiredPrecision) ersetzt werden?

    Benutzer472308

    15. Mai 2014 um 15:09 Uhr

  • Ja. Um nach k Dezimalstellen zu runden, verwenden Sie einen Skalierungsfaktor von 10^k. Dies sollte wirklich leicht zu erkennen sein, wenn Sie einige Dezimalwerte von Hand aufschreiben und mit Vielfachen von 10 herumspielen. Angenommen, Sie arbeiten mit dem Wert 1,23456789 und möchten ihn auf 3 Dezimalstellen runden. Die für Sie verfügbare Operation ist auf Ganzzahl runden. Wie verschieben Sie also die ersten drei Dezimalstellen so, dass sie links vom Dezimalkomma liegen? Ich hoffe, es ist klar, dass Sie mit 10 ^ 3 multiplizieren. Jetzt können Sie diesen Wert auf eine Ganzzahl runden. Als nächstes setzen Sie die drei niederwertigen Ziffern zurück, indem Sie durch 10^3 dividieren.

    – Dale Hagglund

    16. Mai 2014 um 3:19 Uhr

  • Kann ich damit arbeiten doubles auch irgendwie? Scheint nicht den Job zu machen, den ich will 🙁 (mit floor und ceil).

    – Frau Niemand

    20. Juni 2014 um 12:40 Uhr


Benutzeravatar von Andrew Coleson
Andreas Coleson

Verwenden %.2f im Druckf. Es werden nur 2 Dezimalstellen gedruckt.

Beispiel:

printf("%.2f", 37.777779);

Ausgabe:

37.77

  • Dieser Weg ist besser, da es keinen Präzisionsverlust gibt.

    – Albert

    1. Februar 2014 um 18:45 Uhr

  • @albert Das hat auch den Vorteil, dass es keine Verluste gibt float Bereich als val * 100 überlaufen könnte.

    – chux – Wiedereinsetzung von Monica

    3. August 2014 um 15:52 Uhr

  • Hier wurde darüber diskutiert %.2f ist implementierungsabhängig.

    – tueda

    24. August um 16:13 Uhr

Benutzeravatar von John Carter
John Carter

Angenommen, Sie sprechen über den Wert zum Drucken, dann ist die Antwort von Andrew Coleson und AraK richtig:

printf("%.2f", 37.777779);

Aber beachten Sie, dass wenn Sie darauf abzielen, die Zahl für den internen Gebrauch auf genau 37,78 zu runden (z. B. um sie mit einem anderen Wert zu vergleichen), dies aufgrund der Funktionsweise von Fließkommazahlen keine gute Idee ist: Sie tun dies normalerweise nicht Gleichheitsvergleiche für Gleitkommazahlen durchführen möchten, verwenden Sie stattdessen einen Zielwert +/- einen Sigma-Wert. Oder codieren Sie die Zahl als Zeichenfolge mit bekannter Genauigkeit und vergleichen Sie diese.

Siehe den Link in Greg Hewgills Antwort auf eine verwandte Frage, die auch behandelt, warum Sie keine Gleitkommazahlen für Finanzberechnungen verwenden sollten.

  • Upvoted für die Beantwortung dessen, was die Frage hinter der Frage sein könnte (oder die Frage, die hinter der Frage hätte stehen sollen!). Das ist ein ziemlich wichtiger Punkt.

    – Brooks Moses

    28. August 2009 um 3:13 Uhr

  • Tatsächlich kann 37,78 genau per Fließkomma dargestellt werden. Float hat 11 bis 12 Ziffern für die Genauigkeit. Das sollte ausreichen, um 3778 377,8 oder alle Arten von 4 Dezimalziffern zu adressieren.

    – Anonymes Weiß

    28. Oktober 2012 um 9:56 Uhr

  • @HaryantoCiu ja, fair genug, ich habe meine Antwort ein wenig bearbeitet.

    – John Carter

    8. November 2012 um 19:00 Uhr

  • dynamische Präzision: printf("%.*f", (int)precision, (double)number);

    – Minhas Kamal

    26. Februar 2020 um 5:10 Uhr

  • @AnonymousWhite Das ist nicht korrekt, der nächste Wert zu 37,78 mit 32-Bit-Gleitkomma ist 37,779998779296875. Es kann nicht genau dargestellt werden.

    – orlp

    22. September um 10:49 Uhr

Wie wäre es damit:

float value = 37.777779;
float rounded = ((int)(value * 100 + .5) / 100.0);

Benutzeravatar von Khaled Alshaya
Khaled Alshaya

printf("%.2f", 37.777779);

Wenn Sie in C-String schreiben möchten:

char number[24]; // dummy size, you should take care of the size!
sprintf(number, "%.2f", 37.777779);

  • @Sinan: Warum die Bearbeitung? @AraK: Nein, Sie sollte auf die größe achten :). Verwenden Sie snprintf().

    – Aib

    28. August 2009 um 14:17 Uhr

  • @aib: Ich würde vermuten, weil /**/ Kommentare im C-Stil sind und die Frage für C markiert ist

    – Michael Haren

    28. August 2009 um 14:20 Uhr

  • C89 erlaubte nur den /**/-Stil, C99 führte die Unterstützung für den //-Stil ein. Verwenden Sie einen lahmen/alten Compiler (oder erzwingen Sie den C89-Modus), und Sie können den //-Stil nicht verwenden. Abgesehen davon ist es 2009, betrachten wir sie sowohl im C- als auch im C++-Stil.

    – Andrew Coleson

    4. September 2009 um 17:28 Uhr

Verwenden Sie immer die printf Funktionsfamilie dafür. Auch wenn Sie den Wert als Float erhalten möchten, verwenden Sie am besten snprintf um den gerundeten Wert als String zu erhalten und ihn dann zurück zu parsen atof:

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

double dround(double val, int dp) {
    int charsNeeded = 1 + snprintf(NULL, 0, "%.*f", dp, val);
    char *buffer = malloc(charsNeeded);
    snprintf(buffer, charsNeeded, "%.*f", dp, val);
    double result = atof(buffer);
    free(buffer);
    return result;
}

Ich sage das, weil der Ansatz, der von der derzeit am besten bewerteten Antwort und mehreren anderen hier gezeigt wird – Multiplikation mit 100, Rundung auf die nächste ganze Zahl und dann erneute Division durch 100 – in zweierlei Hinsicht fehlerhaft ist:

  • Bei einigen Werten wird in die falsche Richtung gerundet, da die Multiplikation mit 100 die Dezimalstelle, die die Rundungsrichtung bestimmt, aufgrund der Ungenauigkeit von Gleitkommazahlen von einer 4 auf eine 5 oder umgekehrt ändert
  • Bei manchen Werten führt das Multiplizieren und anschließende Dividieren durch 100 nicht zum Roundtrip, was bedeutet, dass das Endergebnis auch dann falsch ist, wenn keine Rundung stattfindet

Um die erste Art von Fehler zu veranschaulichen – die Rundungsrichtung ist manchmal falsch – versuchen Sie, dieses Programm auszuführen:

int main(void) {
    // This number is EXACTLY representable as a double
    double x = 0.01499999999999999944488848768742172978818416595458984375;

    printf("x: %.50f\n", x);

    double res1 = dround(x, 2);
    double res2 = round(100 * x) / 100;

    printf("Rounded with snprintf: %.50f\n", res1);
    printf("Rounded with round, then divided: %.50f\n", res2);
}

Sie sehen diese Ausgabe:

x: 0.01499999999999999944488848768742172978818416595459
Rounded with snprintf: 0.01000000000000000020816681711721685132943093776703
Rounded with round, then divided: 0.02000000000000000041633363423443370265886187553406

Beachten Sie, dass der Wert, mit dem wir begonnen haben, kleiner als 0,015 war, sodass die mathematisch korrekte Antwort beim Runden auf 2 Dezimalstellen 0,01 ist. 0,01 ist das natürlich nicht exakt als Double darstellbar, aber wir erwarten, dass unser Ergebnis das Double ist, das 0,01 am nächsten liegt. Verwenden snprintf gibt uns dieses Ergebnis, aber mit round(100 * x) / 100 gibt uns 0,02, was falsch ist. Wieso den? Da 100 * x gibt uns als Ergebnis genau 1,5. Die Multiplikation mit 100 ändert also die richtige Richtung zum Aufrunden.

Zur Veranschaulichung der zweite Art von Fehler – das Ergebnis ist manchmal falsch aufgrund von * 100 und / 100 nicht wirklich zueinander invers sind – wir können eine ähnliche Übung mit einer sehr großen Zahl machen:

int main(void) {
    double x = 8631192423766613.0;

    printf("x: %.1f\n", x);

    double res1 = dround(x, 2);
    double res2 = round(100 * x) / 100;

    printf("Rounded with snprintf: %.1f\n", res1);
    printf("Rounded with round, then divided: %.1f\n", res2);
}

Unsere Zahl hat jetzt nicht einmal einen Bruchteil; Es ist ein ganzzahliger Wert, der nur mit Typ gespeichert wird double. Das Ergebnis nach dem Runden sollte also dieselbe Zahl sein, mit der wir begonnen haben, richtig?

Wenn Sie das obige Programm ausführen, sehen Sie Folgendes:

x: 8631192423766613.0
Rounded with snprintf: 8631192423766613.0
Rounded with round, then divided: 8631192423766612.0

Hoppla. Unser snprintf -Methode gibt wieder das richtige Ergebnis zurück, aber der Multiplizieren-dann-runden-dann-Dividieren-Ansatz schlägt fehl. Denn der mathematisch korrekte Wert von 8631192423766613.0 * 100, 863119242376661300.0, ist nicht genau als Double darstellbar; der nächste Wert ist 863119242376661248.0. Wenn Sie das wieder durch 100 teilen, erhalten Sie 8631192423766612.0 – eine andere Nummer als die, mit der Sie begonnen haben.

Hoffentlich ist das eine ausreichende Demonstration, dass using roundf zum Runden auf eine Anzahl von Dezimalstellen gebrochen ist, und die Sie verwenden sollten snprintf stattdessen. Wenn sich das für Sie wie ein schrecklicher Hack anfühlt, werden Sie vielleicht beruhigt sein, wenn Sie wissen, dass es so ist im Grunde das, was CPython tut.

  • @Sinan: Warum die Bearbeitung? @AraK: Nein, Sie sollte auf die größe achten :). Verwenden Sie snprintf().

    – Aib

    28. August 2009 um 14:17 Uhr

  • @aib: Ich würde vermuten, weil /**/ Kommentare im C-Stil sind und die Frage für C markiert ist

    – Michael Haren

    28. August 2009 um 14:20 Uhr

  • C89 erlaubte nur den /**/-Stil, C99 führte die Unterstützung für den //-Stil ein. Verwenden Sie einen lahmen/alten Compiler (oder erzwingen Sie den C89-Modus), und Sie können den //-Stil nicht verwenden. Abgesehen davon ist es 2009, betrachten wir sie sowohl im C- als auch im C++-Stil.

    – Andrew Coleson

    4. September 2009 um 17:28 Uhr

Benutzeravatar von synaptik
synaptik

Wenn Sie C++ verwenden, können Sie auch einfach eine Funktion wie diese erstellen:

string prd(const double x, const int decDigits) {
    stringstream ss;
    ss << fixed;
    ss.precision(decDigits); // set # places after decimal
    ss << x;
    return ss.str();
}

Sie können dann ein beliebiges Double ausgeben myDouble mit n Stellen nach dem Komma mit Code wie diesem:

std::cout << prd(myDouble,n);

1426330cookie-checkWie beschränke ich einen Float-Wert auf nur zwei Stellen nach dem Dezimalkomma in C?

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

Privacy policy