Jak zaokrąglić liczbę zmiennoprzecinkową (taką jak 37,777779) do dwóch miejsc po przecinku (37,78) w C?
c
floating-point
decimal
Sakib Arifin
źródło
źródło
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.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: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 .
źródło
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:
Wynik:
źródło
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:
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.
źródło
printf("%.*f", (int)precision, (double)number);
Co powiesz na to:
źródło
Jeśli chcesz napisać do ciągu C:
źródło
Nie ma sposobu na zaokrąglenie a
float
do drugiego,float
ponieważ zaokrągleniefloat
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.źródło
float
do n miejsc dziesiętnych, a następnie oczekiwać, że wynik zawsze będzie miał n miejsc dziesiętnych. Nadal otrzymaszfloat
, ale nie ten, którego się spodziewałeś.Ponadto, jeśli używasz C ++, możesz po prostu utworzyć taką funkcję:
Następnie możesz wypisać dowolne podwójne
myDouble
zn
miejscami po przecinku z kodem takim jak ten:źródło
Nadal możesz używać:
przykład:
źródło
W C ++ (lub w C z rzutami w stylu C) możesz utworzyć funkcję:
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ę.
źródło
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
: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:
Zobaczysz ten wynik:
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żywanieround(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: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:
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ść to863119242376661248.0
. Dzieląc to z powrotem przez 100, otrzymujesz8631192423766612.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 .źródło
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
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. Ponadto0.01
najtrudniejsze jest precyzyjne zaokrąglenie do najbliższego we wszystkich przypadkach „w połowie drogi”.Chociaż „1.115” jest „w połowie drogi” między 1,11 a 1,12, po przekształceniu
float
na wartość ta jest1.115000009537...
i nie jest już „w połowie drogi”, ale jest bliższa 1,12 i zaokrągla do najbliższejfloat
z1.120000004768...
„1,125” oznacza „w połowie drogi” między 1,12 a 1,13, w przypadku konwersji
float
na wartość jest dokładnie1.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ższegofloat
z1.129999995232...
Chociaż „1.135” jest „w połowie drogi” między 1,13 a 1,14, po przekształceniu
float
na wartość ta jest1.134999990463...
i nie jest już „w połowie drogi”, ale jest bliższa 1,13 i zaokrągla do najbliższejfloat
z1.129999995232...
Jeśli użyty kod
Chociaż „1.135” jest „w połowie drogi” między 1,13 a 1,14, to po przekształceniu
float
wartość jest1.134999990463...
i nie jest już „w połowie”, ale bliższa 1,13, ale niepoprawnie zaokrągla do lubfloat
z1.139999985695...
powodu bardziej ograniczonej precyzjifloat
vs.double
. Ta niepoprawna wartość może być postrzegana jako poprawna, w zależności od celów kodowania.źródło
Zrobiłem to makro do zaokrąglania liczb zmiennoprzecinkowych. Dodaj go w nagłówku / pliku
Oto przykład:
x równa się 3,14 :)
źródło
Oto
n
liczba miejsc po przecinkuprzykład:
źródło
dval
jest ogromne 3) dziwneif
/else
blok, w którym robisz dokładnie to samo w każdej gałęzi oraz 4) nadmiernie skomplikowane użyciesprintf
do budowania specyfikatora formatu dla drugiegosprintf
wywołania; łatwiej jest po prostu użyć.*
i przekazać podwójną wartość i liczbę miejsc dziesiętnych jako argumenty do tego samegosprintf
wywołania.Definicja kodu:
Wyniki:
źródło
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:
Zaokrąglanie do 2 miejsc po przecinku w celu prezentacji wyniku można wykonać jako:
Ponieważ
val = 6.825
wynik jest6.83
jak oczekiwano.Ponieważ
val = 6.824999
wynik jest6.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 jest6.83
. W9
tym przypadku 7 miejsce po przecinku powoduje, żeRound(val,6)
funkcja daje oczekiwany wynik. W tym przypadku może występować dowolna liczba znaków końcowych9
.Ponieważ
val = 6.824999499999
wynik jest6.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 jako6.824999499999...
.Wreszcie przykład z pytania ...
val = 37.777779
daje wynik37.78
.Podejście to można dodatkowo uogólnić jako:
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.
źródło
Prosty kod C do zaokrąglania liczby:
Spowoduje to:
źródło
... lub możesz to zrobić w staromodny sposób bez żadnych bibliotek:
To oczywiście, jeśli chcesz usunąć dodatkowe informacje z numeru.
źródło
ta funkcja przyjmuje liczbę i precyzję i zwraca zaokrągloną liczbę
konwertuje liczbę zmiennoprzecinkową na int, przesuwając w lewo punkt i sprawdzając warunek większy niż pięć.
źródło