Краткий способ реализовать round () в C? - PullRequest
14 голосов
/ 01 января 2011

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

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

Ответы [ 8 ]

17 голосов
/ 01 января 2011

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

Исходный код 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

Здесь определены макросы для извлечения слов: 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;
}
13 голосов
/ 01 января 2011
int round(double x)
{
    if (x < 0.0)
        return (int)(x - 0.5);
    else
        return (int)(x + 0.5);
}
7 голосов
/ 01 января 2011

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

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

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

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

7 голосов
/ 01 января 2011
int round(float x)
{
    return (int)(x + 0.5);
}

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

2 голосов
/ 01 января 2011

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

0 голосов
/ 09 марта 2019

Вот моя интерпретация решения округления двойного числа до целого числа. Это, конечно, не является стандартом для C, но обеспечивает возможность округления двойного до ближайшего целого числа.

int round(double n){
    int trunc = (int) n;
    double diff = n - (double) trunc;
    if(diff < 0.5){
        return trunc;
    } else {
        return trunc+1;
    }
}
0 голосов
/ 19 марта 2015

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

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);
}
0 голосов
/ 01 января 2011

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

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;
...