Краткий способ реализации round() в C?

Встраиваемый C, который я использую, не имеет функции round(), это он math lib, что было бы кратким способом реализовать это в C? Я подумывал распечатать его до строки, ища десятичную точку, а затем найти первый char после периода, затем округлить, если >= 5, иначе вниз. и т.д. Интересно, есть ли что-то более умное.

Спасибо, Фред

Ответ 1

Вы можете изобрести колесо, как и многие другие ответы. В качестве альтернативы вы можете использовать чужое колесо - я бы предложил Newlib's, который лицензирован BSD и предназначен для использования во встроенных системах. Он правильно обрабатывает отрицательные числа, NaNs, бесконечности и случаи, которые не представляют собой целые числа (из-за слишком большого), а также делают это эффективным образом, который использует показатели и маскирование, а не вообще-дорогостоящие операции с плавающей запятой. Кроме того, он регулярно тестируется, поэтому вы знаете, что в нем нет ярких ошибок в корневом каталоге.

Источник Newlib может быть немного неудобным для навигации, поэтому вот нужные вам биты:

Плавающая версия: https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;a=blob;f=newlib/libm/common/sf_round.c;hb=master

Двойная версия: https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;a=blob;f=newlib/libm/common/s_round.c;hb=master

Макросы Word-extract, определенные здесь: https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;a=blob;f=newlib/libm/common/fdlibm.h;hb=master

Если вам нужны другие файлы, родительский каталог будет следующим: https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;a=tree;f=newlib/libm/common;hb=master

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

float roundf(x)
{
  int signbit;
  __uint32_t w;
  /* Most significant word, least significant word. */
  int exponent_less_127;

  GET_FLOAT_WORD(w, x);

  /* Extract sign bit. */
  signbit = w & 0x80000000;

  /* Extract exponent field. */
  exponent_less_127 = (int)((w & 0x7f800000) >> 23) - 127;

  if (exponent_less_127 < 23)
    {
      if (exponent_less_127 < 0)
        {
          w &= 0x80000000;
          if (exponent_less_127 == -1)
            /* Result is +1.0 or -1.0. */
            w |= ((__uint32_t)127 << 23);
        }
      else
        {
          unsigned int exponent_mask = 0x007fffff >> exponent_less_127;
          if ((w & exponent_mask) == 0)
            /* x has an integral value. */
            return x;

          w += 0x00400000 >> exponent_less_127;
          w &= ~exponent_mask;
        }
    }
  else
    {
      if (exponent_less_127 == 128)
        /* x is NaN or infinite. */
        return x + x;
      else
        return x;
    }
  SET_FLOAT_WORD(x, w);
  return x;
}

Ответ 2

int round(double x)
{
    if (x < 0.0)
        return (int)(x - 0.5);
    else
        return (int)(x + 0.5);
}

Ответ 3

int round(float x)
{
    return (int)(x + 0.5);
}

Предостережение: Работает только с положительными номерами.

Ответ 4

IEEE 754 рекомендует использовать подход "от половины до четного": если дробная часть d равна 0,5, то округляется до ближайшего четного целого. Проблема в том, что округление дробной части 0,5 в том же направлении приводит к смещению результатов; поэтому вам придется округлить до 0,5 раза половину времени и в два раза меньше, поэтому бит "круглый до ближайшего четного целого", округленный до ближайшего нечетного, также будет работать, как бы переворачивая честную монету, чтобы определить, перейти.

Я думаю, что что-то более похожее на IEEE-правильно:

#include <math.h>

int is_even(double d) {
    double int_part;
    modf(d / 2.0, &int_part);
    return 2.0 * int_part == d;
}

double round_ieee_754(double d) {
    double i = floor(d);
    d -= i;
    if(d < 0.5)
        return i;
    if(d > 0.5)
        return i + 1.0;
    if(is_even(i))
        return i;
    return i + 1.0;
}

И этот должен быть C99-ish (который, как представляется, указывает, что числа с дробными частями 0,5 должны округляться от нуля):

#include <math.h>
double round_c99(double x) {
    return (x >= 0.0) ? floor(x + 0.5) : ceil(x - 0.5);
}

И более компактный вариант моего первого round_c99(), который лучше справляется с 56-битной границей мантиссы, не полагаясь на x+0.5 или x-0.5 на разумные вещи:

#include <math.h>
double round_c99(double d) {
    double int_part, frac_part;
    frac_part = modf(d, &int_part);
    if(fabs(frac_part) < 0.5)
        return int_part;
    return int_part > 0.0 ? int_part + 1.0 : int_part - 1.0;
}

Это будет иметь проблемы, если |int_part| >> 1, но округление двойника с большим показателем бессмысленно. Я уверен, что есть NaN во всех трех, но мой мазохизм имеет ограничения, а численное программирование действительно не мое.

Вычисление с плавающей точкой имеет достаточно места для тонких ошибок, поэтому краткое не может быть лучшим требованием.

Еще лучше было бы побить своего поставщика компилятора примерно по лицу и шее, пока они не предоставят надлежащую математическую библиотеку.

Ответ 5

В древние дни, когда округление не было четко определено в разных системах, мы написали масштабированную функцию округления, которая сначала умножила число так, чтобы округление было выполнено путем обрезания числа.
Чтобы округлить до двух знаков после запятой, умножьте на 100, добавьте .5, обрезайте результаты и разделите их на 100.
Так было сделано для машинных инструментов Numerical Control, когда элементы управления не могли запускать программу ЧПУ, если она не была на месте (мертвые гайки).

Ответ 6

Можете ли вы использовать целое число? выполните следующие действия:

int roundup(int m, int n)
{
 return (m + n - 1) / n  ;
}

int rounddown(int m, int n)
{
 return m / n;
}

int round(int m, int n)
{
   int mod = m % n;

   if(mod >= (n + 1) / 2)
      return roundup(m, n);
   else
      return rounddown(m, n);
}

Ответ 7

Один из способов использования строковой операции

float var=1.2345;
char tmp[12]={0x0};
sprintf(tmp, "%.2f", var);
var=atof(tmp);

Другой способ использования числовых операций

float var=1.2345;
int i=0;
var*=100;
i=var;
var=i;
var/=100;