Jak zaokrąglić liczbę zmiennoprzecinkową (taką jak 37,777779) do dwóch miejsc po przecinku (37,78) w C?
Jak zaokrąglić liczbę zmiennoprzecinkową (taką jak 37,777779) do dwóch miejsc po przecinku (37,78) w C?
Odpowiedzi:
Jeśli chcesz zaokrąglić liczbę do celów wyjściowych, "%.2f"
łańcuch formatu jest rzeczywiście poprawną odpowiedzią. Jeśli jednak chcesz zaokrąglić wartość zmiennoprzecinkową w celu dalszego obliczenia, działa coś takiego:
#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 */
Zauważ, że możesz wybrać trzy różne reguły zaokrąglania: zaokrąglaj w dół (tj. Obcinaj po dwóch miejscach dziesiętnych), zaokrąglaj do najbliższego i zaokrąglaj w górę. Zwykle chcesz zaokrąglić do najbliższego.
Jak zauważyło kilka innych, ze względu na dziwactwa reprezentacji zmiennoprzecinkowej, te zaokrąglone wartości mogą nie być dokładnie „oczywistymi” wartościami dziesiętnymi, ale będą bardzo bardzo zbliżone.
Aby uzyskać dużo (dużo!) Więcej informacji na temat zaokrąglania, a zwłaszcza zasad rozstrzygania zaokrąglania do najbliższego, zobacz artykuł w Wikipedii na temat Zaokrąglania .
doubles
jakiś sposób sprawić, by to zadziałało ? floor
ceil
Używanie % .2f w printf. Drukuje tylko 2 kropki dziesiętne.
Przykład:
printf("%.2f", 37.777779);
Wynik:
37.77
float
zasięgu, ponieważ val * 100
mogłoby to spowodować przepełnienie.
Zakładając, że mówisz o zaokrągleniu wartości do drukowania, wtedy odpowiedzi Andrew Colesona i AraK są poprawne:
printf("%.2f", 37.777779);
Pamiętaj jednak, że jeśli chcesz zaokrąglić liczbę do dokładnie 37,78 do użytku wewnętrznego (np. Aby porównać z inną wartością), to nie jest to dobry pomysł, ze względu na sposób działania liczb zmiennoprzecinkowych: zwykle nie chcę wykonać porównania równości dla liczb zmiennoprzecinkowych, zamiast tego użyj wartości docelowej +/- wartości sigma. Lub zakoduj liczbę jako ciąg znaków ze znaną precyzją i porównaj ją.
Zobacz link w odpowiedzi Grega Hewgilla na powiązane pytanie , która obejmuje również dlaczego nie należy używać zmiennoprzecinkowego do obliczeń finansowych.
printf("%.*f", (int)precision, (double)number);
Co powiesz na to:
float value = 37.777779;
float rounded = ((int)(value * 100 + .5) / 100.0);
printf("%.2f", 37.777779);
Jeśli chcesz napisać do ciągu C:
char number[24]; // dummy size, you should take care of the size!
sprintf(number, "%.2f", 37.777779);
Nie ma sposobu na zaokrąglenie a float
do drugiego, float
ponieważ zaokrąglenie float
może nie być reprezentowalne (ograniczenie liczb zmiennoprzecinkowych). Załóżmy na przykład, że zaokrąglasz liczbę 37,777779 do 37,78, ale najbliższa reprezentowalna liczba to 37,781.
Jednak możliwe „okrągłe” a float
za pomocą funkcji format string.
float
do n miejsc dziesiętnych, a następnie oczekiwać, że wynik zawsze będzie miał n miejsc dziesiętnych. Nadal otrzymasz float
, ale nie ten, którego się spodziewałeś.
Ponadto, jeśli używasz C ++, możesz po prostu utworzyć taką funkcję:
string prd(const double x, const int decDigits) {
stringstream ss;
ss << fixed;
ss.precision(decDigits); // set # places after decimal
ss << x;
return ss.str();
}
Następnie możesz wypisać dowolne podwójne myDouble
z n
miejscami po przecinku z kodem takim jak ten:
std::cout << prd(myDouble,n);
Nadal możesz używać:
float ceilf(float x); // don't forget #include <math.h> and link with -lm.
przykład:
float valueToRound = 37.777779;
float roundedValue = ceilf(valueToRound * 100) / 100;
W C ++ (lub w C z rzutami w stylu C) możesz utworzyć funkcję:
/* Function to control # of decimal places to be output for x */
double showDecimals(const double& x, const int& numDecimals) {
int y=x;
double z=x-y;
double m=pow(10,numDecimals);
double q=z*m;
double r=round(q);
return static_cast<double>(y)+(1.0/m)*r;
}
Wtedy std::cout << showDecimals(37.777779,2);
wyprodukuje: 37,78.
Oczywiście tak naprawdę nie musisz tworzyć wszystkich 5 zmiennych w tej funkcji, ale zostawiam je tam, abyś mógł zobaczyć logikę. Prawdopodobnie istnieją prostsze rozwiązania, ale działa to dla mnie dobrze - zwłaszcza, że pozwala mi dostosować liczbę cyfr po przecinku, tak jak potrzebuję.
Zawsze używaj do tego printf
rodziny funkcji. Nawet jeśli chcesz uzyskać wartość jako liczbę zmiennoprzecinkową, najlepiej jest użyć tej opcji, snprintf
aby uzyskać zaokrągloną wartość jako ciąg, a następnie przeanalizować ją z powrotem za pomocą 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;
}
Mówię to, ponieważ podejście pokazane w aktualnie głosowanej odpowiedzi i kilku innych tutaj - pomnożenie przez 100, zaokrąglenie do najbliższej liczby całkowitej, a następnie ponowne podzielenie przez 100 - jest błędne na dwa sposoby:
Aby zilustrować pierwszy rodzaj błędu - czasami zaokrąglanie jest nieprawidłowe - spróbuj uruchomić ten program:
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);
}
Zobaczysz ten wynik:
x: 0.01499999999999999944488848768742172978818416595459
Rounded with snprintf: 0.01000000000000000020816681711721685132943093776703
Rounded with round, then divided: 0.02000000000000000041633363423443370265886187553406
Zauważ, że początkowa wartość była mniejsza niż 0,015, a zatem poprawna matematycznie odpowiedź przy zaokrąglaniu do 2 miejsc po przecinku wynosi 0,01. Oczywiście, 0,01 nie jest dokładnie reprezentowalny jako podwójny, ale spodziewamy się, że nasz wynik będzie podwójny najbliższy 0,01. Używanie snprintf
daje nam ten wynik, ale używanie round(100 * x) / 100
daje nam 0,02, co jest błędem. Czemu? Ponieważ 100 * x
daje nam dokładnie 1,5 jako wynik. Mnożenie przez 100 zmienia zatem właściwy kierunek na zaokrąglanie.
Aby zilustrować ten drugi rodzaj błędu - wynik czasami jest źle z powodu * 100
i / 100
nie będąc prawdziwie odwrotności siebie - możemy zrobić podobną ćwiczenia z bardzo dużej liczby:
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);
}
Nasza liczba nie ma teraz nawet części ułamkowej; jest to liczba całkowita, właśnie zapisana z typem double
. Tak więc wynik po zaokrągleniu powinien być taki sam, jak na początku, prawda?
Jeśli uruchomisz powyższy program, zobaczysz:
x: 8631192423766613.0
Rounded with snprintf: 8631192423766613.0
Rounded with round, then divided: 8631192423766612.0
Ups Nasza snprintf
metoda ponownie zwraca właściwy wynik, ale podejście polegające na mnożeniu, a następnie dzieleniu na dwie części kończy się niepowodzeniem. To dlatego, że matematycznie poprawna wartość 8631192423766613.0 * 100
, 863119242376661300.0
nie jest dokładnie reprezentowalna jako podwójne; najbliższa wartość to 863119242376661248.0
. Dzieląc to z powrotem przez 100, otrzymujesz 8631192423766612.0
- inny numer niż ten, od którego zacząłeś.
Mam nadzieję, że jest to wystarczająca demonstracja, że użycie roundf
zaokrąglania do liczby miejsc po przecinku jest zepsute, i że powinieneś użyć snprintf
zamiast tego. Jeśli czujesz, że to okropny hack, być może uspokoi cię wiedza, że to właśnie robi CPython .
Zastosowanie float roundf(float x)
.
„Funkcje zaokrąglania zaokrąglają argument do najbliższej wartości całkowitej w formacie zmiennoprzecinkowym, zaokrąglając przypadki o połowę od zera, niezależnie od bieżącego kierunku zaokrąglania.” C11dr §7.12.9.5
#include <math.h>
float y = roundf(x * 100.0f) / 100.0f;
W zależności od float
implementacji liczby, które mogą wydawać się w połowie, nie są. ponieważ zmiennoprzecinkowy jest zazwyczaj zorientowany na podstawie 2. Ponadto 0.01
najtrudniejsze jest precyzyjne zaokrąglenie do najbliższego we wszystkich przypadkach „w połowie drogi”.
void r100(const char *s) {
float x, y;
sscanf(s, "%f", &x);
y = round(x*100.0)/100.0;
printf("%6s %.12e %.12e\n", s, x, y);
}
int main(void) {
r100("1.115");
r100("1.125");
r100("1.135");
return 0;
}
1.115 1.115000009537e+00 1.120000004768e+00
1.125 1.125000000000e+00 1.129999995232e+00
1.135 1.134999990463e+00 1.139999985695e+00
Chociaż „1.115” jest „w połowie drogi” między 1,11 a 1,12, po przekształceniu float
na wartość ta jest 1.115000009537...
i nie jest już „w połowie drogi”, ale jest bliższa 1,12 i zaokrągla do najbliższej float
z1.120000004768...
„1,125” oznacza „w połowie drogi” między 1,12 a 1,13, w przypadku konwersji float
na wartość jest dokładnie 1.125
i wynosi „w połowie”. Zaokrągla się w kierunku 1,13 ze względu na powiązania z zasadą równości i zaokrągla do najbliższego float
z1.129999995232...
Chociaż „1.135” jest „w połowie drogi” między 1,13 a 1,14, po przekształceniu float
na wartość ta jest 1.134999990463...
i nie jest już „w połowie drogi”, ale jest bliższa 1,13 i zaokrągla do najbliższej float
z1.129999995232...
Jeśli użyty kod
y = roundf(x*100.0f)/100.0f;
Chociaż „1.135” jest „w połowie drogi” między 1,13 a 1,14, to po przekształceniu float
wartość jest 1.134999990463...
i nie jest już „w połowie”, ale bliższa 1,13, ale niepoprawnie zaokrągla do lub float
z 1.139999985695...
powodu bardziej ograniczonej precyzji float
vs. double
. Ta niepoprawna wartość może być postrzegana jako poprawna, w zależności od celów kodowania.
Zrobiłem to makro do zaokrąglania liczb zmiennoprzecinkowych. Dodaj go w nagłówku / pliku
#define ROUNDF(f, c) (((float)((int)((f) * (c))) / (c)))
Oto przykład:
float x = ROUNDF(3.141592, 100)
x równa się 3,14 :)
double f_round(double dval, int n)
{
char l_fmtp[32], l_buf[64];
char *p_str;
sprintf (l_fmtp, "%%.%df", n);
if (dval>=0)
sprintf (l_buf, l_fmtp, dval);
else
sprintf (l_buf, l_fmtp, dval);
return ((double)strtod(l_buf, &p_str));
}
Oto n
liczba miejsc po przecinku
przykład:
double d = 100.23456;
printf("%f", f_round(d, 4));// result: 100.2346
printf("%f", f_round(d, 2));// result: 100.23
dval
jest ogromne 3) dziwne if
/ else
blok, w którym robisz dokładnie to samo w każdej gałęzi oraz 4) nadmiernie skomplikowane użycie sprintf
do budowania specyfikatora formatu dla drugiego sprintf
wywołania; łatwiej jest po prostu użyć .*
i przekazać podwójną wartość i liczbę miejsc dziesiętnych jako argumenty do tego samego sprintf
wywołania.
#define roundz(x,d) ((floor(((x)*pow(10,d))+.5))/pow(10,d))
a = 8.000000
sqrt(a) = r = 2.828427
roundz(r,2) = 2.830000
roundz(r,3) = 2.828000
roundz(r,5) = 2.828430
Pozwól mi najpierw spróbować uzasadnić powód dodania kolejnej odpowiedzi na to pytanie. W idealnym świecie zaokrąglanie nie jest tak naprawdę wielkim problemem. Jednak w prawdziwych systemach może być konieczne zmaganie się z kilkoma problemami, które mogą spowodować zaokrąglenie, które może nie być zgodne z oczekiwaniami. Na przykład możesz wykonywać obliczenia finansowe, w których wyniki końcowe są zaokrąglane i wyświetlane użytkownikom jako 2 miejsca po przecinku; te same wartości są przechowywane ze stałą dokładnością w bazie danych, która może zawierać więcej niż 2 miejsca po przecinku (z różnych powodów; nie ma optymalnej liczby miejsc do zachowania ... zależy od konkretnych sytuacji, które każdy system musi obsługiwać, np. drobnych przedmiotów, których ceny są ułamkami grosza na jednostkę); oraz obliczenia zmiennoprzecinkowe wykonywane na wartościach, w których wyniki są plus / minus epsilon. Przez lata mierzyłem się z tymi problemami i rozwijałem własną strategię. Nie twierdzę, że napotkałem każdy scenariusz ani nie mam najlepszej odpowiedzi, ale poniżej znajduje się przykład mojego dotychczasowego podejścia, które rozwiązuje te problemy:
Załóżmy, że 6 miejsc po przecinku jest uważane za wystarczającą dokładność do obliczeń na liczbach zmiennoprzecinkowych / podwójnych (arbitralna decyzja dla konkretnego zastosowania), przy użyciu następującej funkcji / metody zaokrąglania:
double Round(double x, int p)
{
if (x != 0.0) {
return ((floor((fabs(x)*pow(double(10.0),p))+0.5))/pow(double(10.0),p))*(x/fabs(x));
} else {
return 0.0;
}
}
Zaokrąglanie do 2 miejsc po przecinku w celu prezentacji wyniku można wykonać jako:
double val;
// ...perform calculations on val
String(Round(Round(Round(val,8),6),2));
Ponieważ val = 6.825
wynik jest6.83
jak oczekiwano.
Ponieważ val = 6.824999
wynik jest 6.82
. Tutaj zakłada się, że obliczenia zakończyły się dokładnie6.824999
a 7. miejsce po przecinku wynosi zero.
Ponieważ val = 6.8249999
wynik jest 6.83
. W 9
tym przypadku 7 miejsce po przecinku powoduje, że Round(val,6)
funkcja daje oczekiwany wynik. W tym przypadku może występować dowolna liczba znaków końcowych9
.
Ponieważ val = 6.824999499999
wynik jest 6.83
. Zaokrąglanie do 8 miejsca po przecinku jako pierwszy krok, tj. Round(val,8)
Zajmuje się jednym nieprzyjemnym przypadkiem, w którym obliczony wynik zmiennoprzecinkowy oblicza się 6.8249995
, ale jest wewnętrznie reprezentowany jako 6.824999499999...
.
Wreszcie przykład z pytania ... val = 37.777779
daje wynik 37.78
.
Podejście to można dodatkowo uogólnić jako:
double val;
// ...perform calculations on val
String(Round(Round(Round(val,N+2),N),2));
gdzie N to precyzja, którą należy zachować dla wszystkich pośrednich obliczeń na liczbach zmiennoprzecinkowych / podwójnych. Działa to również na wartości ujemne. Nie wiem, czy takie podejście jest matematycznie poprawne dla wszystkich możliwości.
Prosty kod C do zaokrąglania liczby:
float n = 3.56;
printf("%.f", n);
Spowoduje to:
4
... lub możesz to zrobić w staromodny sposób bez żadnych bibliotek:
float a = 37.777779;
int b = a; // b = 37
float c = a - b; // c = 0.777779
c *= 100; // c = 77.777863
int d = c; // d = 77;
a = b + d / (float)100; // a = 37.770000;
To oczywiście, jeśli chcesz usunąć dodatkowe informacje z numeru.
ta funkcja przyjmuje liczbę i precyzję i zwraca zaokrągloną liczbę
float roundoff(float num,int precision)
{
int temp=(int )(num*pow(10,precision));
int num1=num*pow(10,precision+1);
temp*=10;
temp+=5;
if(num1>=temp)
num1+=10;
num1/=10;
num1*=10;
num=num1/pow(10,precision+1);
return num;
}
konwertuje liczbę zmiennoprzecinkową na int, przesuwając w lewo punkt i sprawdzając warunek większy niż pięć.
float
(idouble
) nie są dziesiętnymi zmiennoprzecinkowymi - są binarnymi zmiennoprzecinkowymi - więc zaokrąglanie do pozycji dziesiętnych jest bez znaczenia. Możesz jednak zaokrąglić wynik.