Округление положительного значения от половины до двух знаков после запятой в C

Как правило, округление до 2 знаков после запятой очень легко с

printf("%.2lf",<variable>);

Тем не менее, система округления обычно округляет до ближайшего четного. Например,

2.554 -> 2.55
2.555 -> 2.56
2.565 -> 2.56
2.566 -> 2.57

И чего я хочу добиться, так это

2.555 -> 2.56
2.565 -> 2.57

Фактически, округление до половины возможно в C, но только для Integer;

int a = (int)(b+0.5)

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

3 ответа

Не ясно, хотите ли вы на самом деле "округлить половину вверх" или, скорее, "округлить половину от нуля", что требует другого подхода к отрицательным значениям.

Двоичный файл одинарной точности float с точностью до 6 знаков после запятой и 20 для doubleтаким образом, смещение значения FP с помощью DBL_EPSILON (определенного в float.h) приведет к округлению до следующего printf( "%.2lf", x ) для значений n.nn5. без влияния на отображаемое значение для значений не n.nn5

double x2  = x * (1 + DBL_EPSILON) ; // round half-away from zero
printf( "%.2lf", x2 ) ;

Для разных режимов округления:

double x2  = x * (1 - DBL_EPSILON) ;  // round half-toward zero
double x2  = x + DBL_EPSILON ;        // round half-up
double x2  = x - DBL_EPSILON ;        // round half-down

[Изменить] ОП пояснил, что только напечатанное значение необходимо округлить до 2 десятичных знаков.

Замечание ОП о том, что округление чисел "на полпути" за "округление до четности" или "округление от нуля" вводит в заблуждение. Из 100 "наполовину" чисел, таких как 0,005, 0,015, 0,025, ... 0,995, только 4 обычно являются "наполовину": 0,125, 0,375, 0,625, 0,875. Это связано с тем, что в формате чисел с плавающей запятой используется base-2, а числа типа 2.565 не могут быть точно представлены.

Вместо этого номера образцов, такие как 2.565 иметь как ближайший double ценность 2.564999999999999947... предполагая двоичный код64. Округление этого числа до ближайшего 0,01 должно составлять 2,56, а не 2,57, как того требует OP.

Таким образом, только числа, оканчивающиеся на 0,125 и 0,625, находятся точно на полпути и округляются, а не вверх, как того требует OP. Предложите принять это и использовать:

printf("%.2lf",variable);  // This should be sufficient

Чтобы приблизиться к цели ОП, цифры можно А) проверить против окончания с 0,125 или 0,625 или Б) немного увеличить. Наименьшее увеличение будет

#include <math.h>
printf("%.2f", nextafter(x, 2*x));

Другой метод подталкивания найден в @Clifford.


[Бывший ответ, который округляет double до ближайшего double кратный 0,01]

Типичная плавающая точка использует форматы, такие как двоичный 64, который использует base-2. "Округление до ближайшего математического 0,01 и связи от 0,0" является сложной задачей.

Как отмечает @Pascal Cuoq, числа с плавающей точкой, такие как 2.555 как правило, только рядом 2.555 и иметь более точное значение, как 2.555000000000000159872... что не на полпути.

Решение @BLUEPIXY, представленное ниже, является лучшим и практичным.

x = round(100.0*x)/100.0;

"Функции округления округляют свой аргумент до ближайшего целочисленного значения в формате с плавающей запятой, округляя полпути до нуля независимо от текущего направления округления". C11dr §7.12.9.6.

((int)(100 * (x + 0.005)) / 100.0) Подход имеет 2 проблемы: он может округляться в неправильном направлении для отрицательных чисел (OP не указывал), а целые числа обычно имеют гораздо меньший диапазон (INT_MIN в INT_MAX) тот double,


Есть еще несколько случаев, когда, например, когда double x = atof("1.115"); который заканчивается около 1.12, когда это действительно должно быть 1.11 так как 1.115, как double действительно ближе к 1.11 а не "на полпути".

string   x                         rounded x 
1.115 1.1149999999999999911182e+00 1.1200000000000001065814e+00

ОП не указал округление отрицательных чисел, предполагая, y = -f(-x),

Ниже приведен точный код для округления double до ближайшего 0.01 double,

Код функционирует как x = round(100.0*x)/100.0; за исключением того, что он использует манипуляции, чтобы гарантировать, что масштабирование на 100.0 выполняется точно без потери точности.

Вероятно, это больше кода, чем интересует OP, но он работает.

Работает на весь double спектр -DBL_MAX в DBL_MAX, (еще надо сделать больше юнит-тестирования).
Это зависит от FLT_RADIX == 2, что является общим.

#include <float.h>
#include <math.h>

void r100_best(const char *s) {
  double x;
  sscanf(s, "%lf", &x);

  // Break x into whole number and fractional parts.  
  // Code only needs to round the fractional part.  
  // This preserves the entire `double` range.
  double xi, xf;
  xf = modf(x, &xi);

  // Multiply the fractional part by N (256). 
  // Break into whole and fractional parts.
  // This provides the needed extended precision.
  // N should be >= 100 and a power of 2.
  // The multiplication by a power of 2 will not introduce any rounding.
  double xfi, xff;
  xff = modf(xf * 256, &xfi);

  // Multiply both parts by 100.  
  // *100 incurs 7 more bits of precision of which the preceding code
  //   insures the 8 LSbit of xfi, xff are zero.
  int xfi100, xff100;
  xfi100 = (int) (xfi * 100.0);
  xff100 = (int) (xff * 100.0); // Cast here will truncate (towards 0)

  // sum the 2 parts.  
  // sum is the exact truncate-toward-0 version of xf*256*100
  int sum = xfi100 + xff100;
  // add in half N
  if (sum < 0)
    sum -= 128;
  else
    sum += 128;

  xf = sum / 256;
  xf /= 100;
  double y = xi + xf;
  printf("%6s %25.22f ", "x", x);
  printf("%6s %25.22f %.2f\n", "y", y, y);
}

int main(void) {
  r100_best("1.105");
  r100_best("1.115");
  r100_best("1.125");
  r100_best("1.135");
  r100_best("1.145");
  r100_best("1.155");
  r100_best("1.165");
  return 0;
}
Другие вопросы по тегам