Як я можу округлити значення поплавця (наприклад, 37,777779) до двох знаків після коми (37,78) у С?
Як я можу округлити значення поплавця (наприклад, 37,777779) до двох знаків після коми (37,78) у С?
Відповіді:
Якщо ви просто хочете округлити число для цілей виведення, то "%.2f"
рядки формату справді є правильною відповіддю. Однак якщо ви дійсно хочете округлити значення з плаваючою комою для подальшого обчислення, працює щось на зразок наступних:
#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 */
Зауважте, що ви можете обрати три різні правила округлення: округлення вниз (тобто скорочення після двох знаків після коми), округлене до найближчого та округлене. Зазвичай, ви хочете, щоб найближчий круглий.
Як зазначають декілька інших, через химерність подання з плаваючою точкою ці округлі значення можуть бути не «явними» десятковими значеннями, але вони будуть дуже близькими.
Більше (набагато!) Більше інформації про округлення, і особливо про правила розбиття краватки до найближчого, дивіться у статті Вікіпедії про округлення .
doubles
занадто якось? Здається, не виконую роботу, яку я хочу :( (використовую floor
та ceil
).
Якщо припустити, що ви говорите про кругле значення для друку, то відповідь Ендрю Колсона та AraK правильна:
printf("%.2f", 37.777779);
Але зауважте, що якщо ви маєте на меті округлити число до 37,78 для внутрішнього використання (наприклад, для порівняння з іншим значенням), це не є хорошою ідеєю, через те, як працюють номери з плаваючою комою: зазвичай ви не робите Ви хочете зробити порівняння рівності для плаваючої точки, замість цього використовуйте цільове значення +/- значення сигми. Або кодуйте число як рядок з відомою точністю і порівняйте це.
Дивіться посилання у відповіді Грега Хьюгілла на пов'язане питання , яке також висвітлює, чому не слід використовувати плаваючу крапку для фінансових розрахунків.
printf("%.*f", (int)precision, (double)number);
Як щодо цього:
float value = 37.777779;
float rounded = ((int)(value * 100 + .5) / 100.0);
printf("%.2f", 37.777779);
Якщо ви хочете записати на C-рядок:
char number[24]; // dummy size, you should take care of the size!
sprintf(number, "%.2f", 37.777779);
Не існує способу округлення а float
до іншої, float
тому що округлене float
може не бути представним (обмеження чисел з плаваючою комою). Наприклад, скажімо, що ви обійшли від 37,777779 до 37,78, але найближче представницьке число - 37,781.
Однак ви можете "округлити" a float
за допомогою функції рядка формату.
float
до n десяткових знаків і тоді очікуєте, що результат завжди матиме n десятичних знаків. Ви все одно отримаєте float
, тільки не той, якого ви очікували.
Крім того, якщо ви використовуєте C ++, ви можете просто створити таку функцію:
string prd(const double x, const int decDigits) {
stringstream ss;
ss << fixed;
ss.precision(decDigits); // set # places after decimal
ss << x;
return ss.str();
}
Потім ви можете вивести будь-який подвійний myDouble
з n
місцями після десяткової крапки з таким кодом:
std::cout << prd(myDouble,n);
Ви все ще можете використовувати:
float ceilf(float x); // don't forget #include <math.h> and link with -lm.
приклад:
float valueToRound = 37.777779;
float roundedValue = ceilf(valueToRound * 100) / 100;
У програмі C ++ (або в C із кастами у стилі C) ви можете створити функцію:
/* 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;
}
Тоді std::cout << showDecimals(37.777779,2);
було б вироблено: 37,78.
Очевидно, вам не потрібно створювати всі 5 змінних у цій функції, але я залишаю їх там, щоб ви могли бачити логіку. Можливо, є більш прості рішення, але це добре працює для мене - тим більше, що це дозволяє мені коригувати кількість цифр після десяткового знака, як мені потрібно.
Завжди використовуйте для цього printf
сімейство функцій. Навіть якщо ви хочете отримати значення як float, вам найкраще використовувати snprintf
округлене значення як рядок, а потім розбирати його назад з 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;
}
Я говорю це тому, що підхід, показаний у відповіді, що наразі відповідає головою, та декілька інших тут - множення на 100, округлення до найближчого цілого числа, а потім повторне ділення на 100 - помилково двома способами:
Щоб проілюструвати помилку першого типу - напрямок округлення, часом невірний - спробуйте запустити цю програму:
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);
}
Ви побачите цей вихід:
x: 0.01499999999999999944488848768742172978818416595459
Rounded with snprintf: 0.01000000000000000020816681711721685132943093776703
Rounded with round, then divided: 0.02000000000000000041633363423443370265886187553406
Зауважимо, що значення, з якого ми розпочали, було менше 0,015, і тому математично правильна відповідь при округлянні її до двох десяткових знаків становить 0,01. Звичайно, 0,01 не є точно репрезентативним як подвійний, але ми очікуємо, що наш результат буде подвійним найближчим до 0,01. Використання snprintf
дає нам такий результат, але використання round(100 * x) / 100
дає нам 0,02, що неправильно. Чому? Тому що 100 * x
дає нам рівно 1,5. Помноження на 100, таким чином, змінює правильний напрямок на округлення.
Для ілюстрації другого роду помилки - результат іноді бути неправильно з - за * 100
і на / 100
самому ділі не бути зворотним один одного - ми можемо зробити подібне вправу з дуже великим числом:
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);
}
Наше число зараз навіть не має дробової частини; це ціле значення, просто збережене з типом double
. Отже результат після округлення повинен бути тим самим числом, з якого ми почали, правда?
Якщо запустити програму вище, ви побачите:
x: 8631192423766613.0
Rounded with snprintf: 8631192423766613.0
Rounded with round, then divided: 8631192423766612.0
На жаль Наш snprintf
метод знову повертає правильний результат, але підхід «множина-тоді-кругообіг» та «ділення» не вдається. Це тому , що математично правильне значення 8631192423766613.0 * 100
, 863119242376661300.0
є не зовсім представимо в вигляді подвійної; найближче значення 863119242376661248.0
. Якщо ви розділите це назад на 100, ви отримаєте 8631192423766612.0
- інше число, ніж те, з якого ви почали.
Сподіваємось, це є достатньою демонстрацією того, що використання roundf
для округлення до числа десяткових знаків порушено, і що ви повинні використовувати його snprintf
замість. Якщо це здасться вам жахливим злом, можливо, вас заспокоїть знання, що це в основному те, що робить CPython .
Використовуйте float roundf(float x)
.
"Функції" круглого "округлюють аргумент до найближчого цілого значення у форматі з плаваючою комою, округлюючи половину випадків від нуля, незалежно від поточного напрямку округлення." C11dr §7.12.9.5
#include <math.h>
float y = roundf(x * 100.0f) / 100.0f;
Залежно від вашої float
реалізації, цифри, які можуть здатися на півдорозі, не є. як плаваюча точка, як правило, орієнтована на базу-2. Далі, саме округлення до найближчого 0.01
у всіх випадках "на півдорозі" є найбільш складним.
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
Хоча «1.115» є «півдорозі» між 1.11 і 1.12, при перетворенні в float
значенні не є 1.115000009537...
і більше не « на півдорозі», але ближче до 1.12 і патронам до найближчого float
з1.120000004768...
"1.125" є "на півдорозі" між 1,12 та 1,13, при перетворенні float
в значення є рівно 1.125
і є "на півдорозі". Вона округлює в стороні 1,13 з - за зв'язки з навіть правил і округляється до найближчого float
з1.129999995232...
Хоча "1.135" є "на півдорозі" між 1,13 і 1,14, при перетворенні в float
це значення є 1.134999990463...
і вже не "на півдорозі", а ближче до 1,13 і округляється до найближчого float
з1.129999995232...
Якщо використовується код
y = roundf(x*100.0f)/100.0f;
Хоча «1,135» є « на півдорозі» між 1.13 і 1.14, при перетворенні в float
значенні не є 1.134999990463...
і більше не « на півдорозі», але ближче до 1,13 , але неправильно округляється до float
з 1.139999985695...
з - за більш обмежену точність float
VS. double
. Це неправильне значення може розглядатися як правильне, залежно від цілей кодування.
Я зробив цей макрос для округлення плаваючих чисел. Додайте його до свого заголовка / буття файлу
#define ROUNDF(f, c) (((float)((int)((f) * (c))) / (c)))
Ось приклад:
float x = ROUNDF(3.141592, 100)
х дорівнює 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));
}
Ось n
кількість десятків
приклад:
double d = 100.23456;
printf("%f", f_round(d, 4));// result: 100.2346
printf("%f", f_round(d, 2));// result: 100.23
dval
є величезним 3) дивний if
/ else
блок, де ви робите точно те саме в кожній гілці і 4) надскладне використання sprintf
для побудови специфікатора формату для другого sprintf
виклику; простіше просто використовувати .*
та передавати подвійне значення та кількість десяткових знаків як аргументи на один і той же sprintf
виклик.
#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
Дозвольте спершу спробувати виправдати свою причину для того, щоб додати ще одну відповідь на це питання. В ідеальному світі округлення насправді не є великою справою. Однак у реальних системах вам може знадобитися вирішити декілька питань, які можуть призвести до округлення, яке може бути не таким, як ви очікуєте. Наприклад, ви можете виконувати фінансові розрахунки, коли кінцеві результати округляються і відображаються користувачам у вигляді 2 знаків після коми; ці ж значення зберігаються з фіксованою точністю в базі даних, яка може містити більше 2 знаків після коми (з різних причин; немає оптимальної кількості місць для зберігання ... залежить від конкретних ситуацій, які повинна підтримувати кожна система, наприклад, крихітні предмети, ціни яких є частками копійки на одиницю); і, обчислення з плаваючою комою, що виконуються на значеннях, де результати плюс / мінус епсилон. Я стикався з цими питаннями і розробляв власну стратегію протягом багатьох років. Я не буду стверджувати, що зіткнувся з кожним сценарієм або маю найкращу відповідь, але нижче наводиться приклад мого підходу, який подолав ці проблеми:
Припустимо, що 6 десяткових знаків розглядаються як достатня точність для розрахунків на поплавці / парні (довільне рішення для конкретного застосування), використовуючи наступну функцію / метод округлення:
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;
}
}
Округлення до 2 знаків після коми для подання результату може бути виконано у вигляді:
double val;
// ...perform calculations on val
String(Round(Round(Round(val,8),6),2));
Бо val = 6.825
результат є6.83
як очікувалося.
Бо val = 6.824999
результат є 6.82
. Тут припущення полягає в тому, що розрахунок привів точно6.824999
і 7-й десятковий знак дорівнює нулю.
Бо val = 6.8249999
результат є 6.83
. У 9
цьому випадку 7-й десятковий знак призводить до того, що Round(val,6)
функція дає очікуваний результат. У цьому випадку може бути будь-яка кількість трейлінгу9
s.
Бо val = 6.824999499999
результат є 6.83
. Округлення до 8-го десяткових знаків як перший крок, тобто Round(val,8)
береться за один неприємний випадок, коли обчислений результат з плаваючою комою обчислюється до6.8249995
, але внутрішньо представлений як6.824999499999...
.
Нарешті, приклад із питання ... val = 37.777779
приводить до37.78
.
Цей підхід можна було б узагальнити як:
double val;
// ...perform calculations on val
String(Round(Round(Round(val,N+2),N),2));
де N - необхідно підтримувати точність для всіх проміжних розрахунків на поплавці / парні. Це працює і на негативних значеннях. Я не знаю, чи такий підхід математично правильний для всіх можливостей.
Простий код C для округлення числа:
float n = 3.56;
printf("%.f", n);
Це виведе:
4
... або ви можете зробити це старомодно без будь-яких бібліотек:
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;
Звичайно, якщо ви хочете видалити зайву інформацію з номера.
ця функція приймає число і точність і повертає округлене число
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;
}
він перетворює номер плаваючої точки в int шляхом зсуву точки вліво і перевірки наявності на більш ніж п’ять умов.
float
(іdouble
) не є десятковою плаваючою комою - вони двійкові з плаваючою комою - тому округлення до десяткових позицій безглуздо. Однак ви можете округлити вихід.