Как ограничить значение с плавающей запятой только двумя знаками после десятичной точки в C?


214

Как можно округлить значение с плавающей запятой (например, 37,777779) до двух десятичных знаков (37,78) в C?


15
Вы не можете правильно округлить само число, потому что floatdouble) не являются десятичными числами с плавающей точкой - они являются двоичными числами с плавающей точкой - поэтому округление до десятичных позиций не имеет смысла. Вы можете округлить вывод, однако.
Павел Минаев

63
Это не бессмысленно; это неточно. Есть большая разница.
Брукс Моисей

2
Какое округление вы ожидаете? Половина или округление до ближайшего четного?
Искатель Правды Рангван

Ответы:


407

Если вы просто хотите округлить число для целей вывода, тогда "%.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 */

Обратите внимание, что есть три различных правила округления, которые вы можете выбрать: округление в меньшую сторону (то есть усечение после двух десятичных знаков), округление до ближайшего и округление в большую сторону. Обычно вы хотите округлить до ближайшего.

Как отметили некоторые другие, из-за особенностей представления с плавающей запятой, эти округленные значения могут быть не совсем «очевидными» десятичными значениями, но они будут очень и очень близки.

Чтобы получить гораздо больше информации о округлении, и особенно о правилах разрыва связей для округления до ближайшего, см. Статью Википедии о округлении .


4
Можно ли изменить его для поддержки округления до произвольной точности?

1
@slater Когда вы говорите «произвольная точность», вы спрашиваете о округлении, например, до трех вместо двух десятичных знаков, или используете библиотеки, которые реализуют десятичные значения с неограниченной точностью? Если первое, внесите, как я надеюсь, очевидные корректировки постоянной 100; в противном случае выполните точно такие же вычисления, как показано выше, только с любой библиотекой с множественной точностью.
Дейл Хэгглунд

2
@DaleHagglung Бывший, спасибо. Является ли корректировка для замены 100 на pow (10, (int) требуемая точность)?

3
Ага. Для округления после k десятичных знаков используйте масштабный коэффициент 10 ^ k. Это должно быть действительно легко увидеть, если вы запишите некоторые десятичные значения вручную и поиграете с коэффициентами, кратными 10. Предположим, вы работаете со значением 1.23456789 и хотите округлить его до 3 десятичных знаков. Доступная вам операция округляется до целого числа . Итак, как вы перемещаете первые три знака после запятой, чтобы они остались от десятичной точки? Надеюсь, понятно, что вы умножаете на 10 ^ 3. Теперь вы можете округлить это значение до целого числа. Затем вы кладете обратно три младших разряда, разделив их на 10 ^ 3.
Дейл Хэгглунд

1
Могу ли я сделать эту работу doublesслишком как-то? Кажется, не выполняет ту работу, которую я хочу :( (использую floorи ceil).
Ms. Nobody

87

Использование % .2f в printf. Он печатает только 2 десятичных знака.

Пример:

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

Вывод:

37.77

Этот способ лучше, потому что нет потери точности.
Альберт

2
@albert Это также имеет то преимущество, что нет потери floatдальности, поскольку val * 100может переполниться.
chux - Восстановить Монику

42

Предполагая, что вы говорите о значении для печати, ответ Эндрю Колесона и Арака верен:

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

Но учтите, что если вы хотите округлить число до 37,78 для внутреннего использования (например, для сравнения с другим значением), то это не очень хорошая идея из-за способа работы чисел с плавающей запятой: обычно вы этого не делаете хотите сделать сравнение равенства для плавающей запятой, вместо этого используйте целевое значение +/- сигма-значение. Или закодируйте число в виде строки с известной точностью и сравните это.

Смотрите ссылку в ответе Грега Хьюгилла на связанный вопрос , который также объясняет, почему вы не должны использовать плавающую точку для финансовых расчетов.


1
Голосовали за решение вопроса о том, что может быть за вопросом (или вопрос, который должен был быть за вопросом!). Это довольно важный момент.
Брукс Моисей

На самом деле 37.78 можно представить именно с плавающей запятой. Float имеет от 11 до 12 цифр для точности. Этого должно быть достаточно, чтобы адрес 3778 377,8 или все виды 4 десятичных цифр.
анонимный белый

@HaryantoCiu да, честно, я немного отредактировал свой ответ.
Джон Картер

динамическая точность:printf("%.*f", (int)precision, (double)number);
Минхас Камаль

24

Как насчет этого:

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

4
-1: а) это не сработает для отрицательных чисел (хорошо, пример положительный, но все же). б) вы не упоминаете, что невозможно сохранить точное десятичное значение в float
Джон Картер

32
@therefromhere: (а) Ты прав (б) Что это? Тест средней школы?
Даниил

1
почему вы добавили 0,5?
Мухаммед Тайяб

1
Необходимо соблюдать правила округления.
Даниил

1
правила округления в контексте комментария @Daniil округляются до ближайшего
Шмиль Кот

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

Если вы хотите написать в C-строку:

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

@ Синан: Почему редактировать? @AraK: Нет, ты должен позаботиться о размере :). Используйте snprintf ().
AIB

1
@aib: Я думаю, потому что / ** / - это комментарии в стиле C, а вопрос помечен для C
Майкл Харен

5
C89 допускает только / ** / - стиль, C99 вводит поддержку // - стиля. Используйте неработающий / старый компилятор (или включите режим C89), и вы не сможете использовать // - стиль. Сказав, что это 2009, давайте рассмотрим их как стиль C и C ++.
Эндрю Колсон

11

Нет способа округлить a floatдо другого, floatпотому что округленное floatможет быть не представимым (ограничение чисел с плавающей запятой). Например, допустим, вы округлили 37,777779 до 37,78, но ближайшее представимое число - 37,781.

Тем не менее, вы можете "округлить" float, используя функцию форматирования строки.


3
Это ничем не отличается от высказывания «нет способа разделить два числа с плавающей точкой и получить число с плавающей точкой, потому что разделенный результат может быть не представимым», что может быть в точности верно, но не имеет значения. Поплавки всегда неточны, даже для чего-то такого базового, как сложение; всегда предполагается, что на самом деле вы получаете «число с плавающей точкой, которое наиболее близко соответствует точному округленному ответу».
Брукс Моисей

Я имел в виду, что вы не можете округлить floatдо n десятичных знаков, а затем ожидать, что результат всегда будет иметь n десятичных знаков. Вы все еще получите float, но не тот, который вы ожидали.
Эндрю Китон

9

Кроме того, если вы используете 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);

7

Вы все еще можете использовать:

float ceilf(float x); // don't forget #include <math.h> and link with -lm.

пример:

float valueToRound = 37.777779;
float roundedValue = ceilf(valueToRound * 100) / 100;

Это усекает в десятичной точке (то есть будет производить 37), и он должен округлить до двух мест после десятичной точки.
Павел Минаев

Однако округление до двух знаков после запятой является тривиальным вариантом (но все же следует упомянуть в ответе; ZeroCool, хотите добавить правку?): Float roundedValue = ceilf (valueToRound * 100.0) / 100.0;
Брукс Моисей

В состояние сна :)
ZeroCool

Почему это решение не так популярно? Это работает именно так, как должно с минимальным кодом. Есть ли какое-то предостережение с этим?
Энди

7

В 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 переменных в этой функции, но я оставляю их там, чтобы вы могли видеть логику. Возможно, есть более простые решения, но это хорошо работает для меня - тем более, что это позволяет мне корректировать количество цифр после десятичного знака по мере необходимости.


5

Всегда используйте printfсемейство функций для этого. Даже если вы хотите получить значение как число с плавающей запятой, лучше всего использовать 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 - ошибочно в двух отношениях:

  • Для некоторых значений он будет округляться в неправильном направлении, потому что умножение на 100 изменяет десятичную цифру, определяющую направление округления, с 4 на 5 или наоборот, из-за неточности чисел с плавающей запятой
  • Для некоторых значений умножение, а затем деление на 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, и поэтому математически правильный ответ при округлении до 2 десятичных знаков равен 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 .


+1 за конкретный пример того, что не так с моим ответом и схожими с ним, благодаря странности IEEE с плавающей запятой и предоставлению простой альтернативы. Я был на периферии, довольно давно, много усилий вложил в печать, и друзья мне безопасны для обходных значений с плавающей запятой. Я полагаю, что проделанная работа может появиться здесь.
Дейл Хэгглунд

Гм ... Извините за слово салат в конце там, который сейчас слишком поздно редактировать. То, что я хотел сказать, было "... много усилий было приложено к принфу и друзьям, чтобы сделать их безопасными ..."
Дейл Хэгглунд

4

Использование 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сравнения double, Это неправильное значение может рассматриваться как правильное в зависимости от целей кодирования.


4

Я сделал этот макрос для округления чисел с плавающей точкой. Добавьте это в свой заголовок / существо файла

#define ROUNDF(f, c) (((float)((int)((f) * (c))) / (c)))

Вот пример:

float x = ROUNDF(3.141592, 100)

х равен 3,14 :)


Это усекает, но вопрос требует округления. Кроме того, он подвержен ошибкам округления в операциях с плавающей запятой.
Эрик Постпишил

3
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

-1 по четырем причинам: 1) отсутствие объяснения, 2) уязвимость переполнения буфера - это увлекает, и поэтому вполне возможно , аварии, если dvalэто огромный 3) сверхъестественный if/ elseблок , где вы делаете одно и то же в каждой отрасли и 4) слишком сложное использование sprintfдля создания спецификатора формата для второго sprintfвызова; проще использовать .*и передавать двойное значение и количество десятичных знаков в качестве аргументов для одного и sprintfтого же вызова.
Марк Амери

3

Определение кода:

#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

0

Позвольте мне сначала попытаться обосновать причину добавления еще одного ответа на этот вопрос. В идеальном мире округление не имеет большого значения. Однако в реальных системах вам, возможно, придется столкнуться с несколькими проблемами, которые могут привести к округлению, которое может не соответствовать вашим ожиданиям. Например, вы можете выполнять финансовые расчеты, где окончательные результаты округляются и отображаются для пользователей в виде 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этом случае седьмое десятичное место приводит к тому, что Round(val,6)функция дает ожидаемый результат. В этом случае может быть любое количество конечных 9s.

Ибо val = 6.824999499999результат есть 6.83. Округление до восьмого десятичного знака в качестве первого шага, т. 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 - точность, которая должна поддерживаться для всех промежуточных вычислений для чисел с плавающей запятой / двойников. Это работает и на отрицательных значениях. Я не знаю, является ли этот подход математически правильным для всех возможностей.



-1

... или вы можете сделать это старомодным способом без каких-либо библиотек:

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;

Это, конечно, если вы хотите удалить дополнительную информацию из номера.


-2

эта функция принимает число и точность и возвращает округленное число

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, сдвигая точку влево и проверяя условие больше пяти.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.