Як я можу округлити значення поплавця (наприклад, 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...з - за більш обмежену точність floatVS. 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) не є десятковою плаваючою комою - вони двійкові з плаваючою комою - тому округлення до десяткових позицій безглуздо. Однак ви можете округлити вихід.