Полезна ли функция div (stdlib.h)?

В C, C++ есть функция div (stdlib.h).

div_t div(int numer, int denom);

typedef struct _div_t
{
  int quot;
  int rem;
} div_t;

Но в C, C++ есть операторы / и %.

У меня вопрос: "Когда есть операторы / и %, полезна ли функция div?"


person Amir Saniyan    schedule 16.07.2011    source источник
comment
на него уже был дан ответ в этом вопросе   -  person phuclv    schedule 13.09.2014
comment
Возможный дубликат Какова цель библиотечной функции div()? (Примечание для не-трафальмадорцев: линейное время не имеет значения. На этот вопрос есть несколько лучших, более современных ответов, чем на этот.)   -  person underscore_d    schedule 16.06.2019
comment
Его ответы чрезмерно сосредоточены на одной детали, которая перестала иметь значение в C99, что было 20 лет назад...   -  person underscore_d    schedule 17.06.2019


Ответы (6)


Функция div() возвращает структуру, содержащую частное и остаток от деления первого параметра (числителя) на второй (знаменатель). Есть четыре варианта:

  1. div_t div(int, int)
  2. ldiv_t ldiv(long, long)
  3. lldiv_t lldiv(long long, long long)
  4. imaxdiv_t imaxdiv(intmax_t, intmax_t (intmax_t представляет самый большой целочисленный тип, доступный в системе)

Структура div_t выглядит так:

typedef struct
  {
    int quot;           /* Quotient.  */
    int rem;            /* Remainder.  */
  } div_t;

Реализация просто использует операторы / и %, так что это не совсем сложная или необходимая функция, но она является частью стандарта C (согласно определению [ISO 9899:201x][1]).

См. реализацию в GNU libc:

/* Return the `div_t' representation of NUMER over DENOM.  */
div_t
div (numer, denom)
     int numer, denom;
{
  div_t result;

  result.quot = numer / denom;
  result.rem = numer % denom;

  /* The ANSI standard says that |QUOT| <= |NUMER / DENOM|, where
     NUMER / DENOM is to be computed in infinite precision.  In
     other words, we should always truncate the quotient towards
     zero, never -infinity.  Machine division and remainer may
     work either way when one or both of NUMER or DENOM is
     negative.  If only one is negative and QUOT has been
     truncated towards -infinity, REM will have the same sign as
     DENOM and the opposite sign of NUMER; if both are negative
     and QUOT has been truncated towards -infinity, REM will be
     positive (will have the opposite sign of NUMER).  These are
     considered `wrong'.  If both are NUM and DENOM are positive,
     RESULT will always be positive.  This all boils down to: if
     NUMER >= 0, but REM < 0, we got the wrong answer.  In that
     case, to get the right answer, add 1 to QUOT and subtract
     DENOM from REM.  */

  if (numer >= 0 && result.rem < 0)
    {
      ++result.quot;
      result.rem -= denom;
    }

  return result;
}
person psYchotic    schedule 16.07.2011
comment
@Jørgen: источник в любом случае определяется реализацией :-) - person Vlad; 16.07.2011
comment
Влад: Смотрите мой ответ для уточнения. - person Jørgen Fogh; 16.07.2011
comment
Со спецификацией C11 (и C99?) на деление и остаток, будет ли совместимый компилятор даже иметь numer >= 0 && result.rem < 0? - person chux - Reinstate Monica; 06.03.2014
comment
Отличный ответ. Просто хочу показать, что если в стандартной библиотеке есть функция, которая кажется ненужной, есть неплохая вероятность того, что у кого-то была веская причина для ее написания. - person AAT; 09.09.2014
comment
Чтобы было понятнее. C11 определяет divas для вычисления numer/denom и numer % denom в одной операции. Начиная с C99, блок if (numer >= 0 && result.rem < 0) не нужен. - person chux - Reinstate Monica; 31.10.2018

Да, это так: он вычисляет частное и остаток за одну операцию.

Кроме того, такого же поведения можно добиться с помощью /+% (и приличный оптимизатор все равно оптимизирует их в один div).

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

person Vlad    schedule 16.07.2011
comment
Хороший оптимизатор заменит / и % двумя умножениями, битовым сдвигом и вычитанием (в большинстве случаев). - person Ben Voigt; 16.07.2011
comment
@Vlad: Вы имеете в виду, что вычисление частного и остатка в одной операции настолько важно, что определена новая функция? - person Amir Saniyan; 16.07.2011
comment
@Ben: 2 умножения, битовый сдвиг и вычитание действительно быстрее, чем одна инструкция DIV (в семействе Intel)? - person Vlad; 16.07.2011
comment
Исторически так было; долгое время деление было очень затратной операцией на ЦП, поэтому приветствовалась любая помощь компилятора. Теперь это не так важно, но нет причин удалять div() из C, поэтому он остается. - person Jonathan Grynspan; 16.07.2011
comment
@Ben, @Jonathan: Intel по-прежнему рекомендует заменить умножение делением, см. 9.2.4 в этот документ. - person Vlad; 16.07.2011
comment
Трюки с умножением работают только тогда, когда знаменатель является константой. - person R.. GitHub STOP HELPING ICE; 16.07.2011
comment
@R: Верно, я забыл об этом ограничении. - person Ben Voigt; 16.07.2011
comment
@Vlad: Разве в этом разделе не рекомендуется заменить деление (на константу) умножением? (И вообще 128-битное деление встречается очень редко) - person Ben Voigt; 16.07.2011
comment
@Ben: я действительно написал замену умножения? О, действительно я сделал. Извините, это была опечатка, мой пример был для подтверждения ваших слов, а не для опровержения. - person Vlad; 16.07.2011
comment
@BenVoigt в большинстве архитектур инструкция div возвращает как частное, так и остаток одновременно, поэтому нет необходимости умножать, сдвигать или вычитать - person phuclv; 13.09.2014
comment
@LưuVĩnhPhúc: Таким образом, ваше очень дорогое разделение дает вам два выхода по цене одного. Комбинация умножения, сдвига и вычитания по-прежнему часто дешевле. - person Ben Voigt; 13.09.2014
comment
@BenVoigt Я имею в виду, что компилятор использует инструкцию div. Потому что можно преобразовать деление на константу только в умножение, поэтому, если вы хотите разделить на переменную, вам часто приходится использовать инструкцию div, что, скорее всего, имеет место, когда кто-то использует div() - person phuclv; 13.09.2014
comment
@LưuVĩnhPhúc: я видел много людей, использующих div с константой 10 для преобразования двоичного числа в десятичное. Но да, когда частное является переменным, необходимо использовать инструкцию div. Библиотечная функция div в этом особо не помогает. - person Ben Voigt; 13.09.2014
comment
@Ben, @LưuVĩnhPhúc: ну, div не гарантировано, что он будет скомпилирован в конкретную инструкцию процессора (в конце концов, на целевой платформе ее может не быть). Если умножение/вычитание возможно и быстрее, оптимизирующий компилятор должен предпочесть этот способ. - person Vlad; 23.09.2014

Семантика div() отличается от семантики % и /, что важно в некоторых случаях. Вот почему следующий код находится в реализации, показанной в ответе psYchotic:

if (numer >= 0 && result.rem < 0)
    {
      ++result.quot;
      result.rem -= denom;
    }

% может вернуть отрицательный ответ, тогда как div() всегда возвращает неотрицательный остаток.

Проверьте запись в Википедии, в частности, "div всегда округляется до 0, в отличие от обычного целочисленного деления в C, где округление для отрицательных чисел зависит от реализации».

person Jørgen Fogh    schedule 16.07.2011
comment
Это неправда. 1) При усечении до нуля остаток будет отрицательным, если numer < 0. 2) Начиная с C11, встроенные / и % гарантированно усекаются до нуля, поэтому div определено так же, как / и %. - person Yakov Galka; 03.08.2016
comment
Согласен с @ybungalobill. тогда как div() всегда возвращает неотрицательный остаток. это неверно. Попробуйте printf("%d\n", div(-7,2).rem); Я ожидаю, что код вернет -1. - person chux - Reinstate Monica; 31.10.2018

div() удовлетворил потребность до C99: переносимость

Pre C99, направление округления частного a / b с отрицательным операнд зависел от реализации. При использовании div() направление округления не является необязательным, а указывается в сторону 0. div() обеспечивает единообразное переносимое деление. Вторичное использование было потенциальной эффективностью, когда код должен был вычислить как частное, так и остаток.

С C99 и более поздними версиями, div() и /, определяющими одно и то же направление раунда, и с лучшими компиляторами, оптимизирующими соседний код a/b и a%b, необходимость уменьшилась.


Это было убедительной причиной для div() и объясняет отсутствие udiv_t udiv(unsigned numer, unsigned denom) в спецификации C: проблемы зависимых от реализации результатов a/b с отрицательными операндами не существовали для unsigned даже до C99.

person chux - Reinstate Monica    schedule 08.09.2014

Вероятно, потому, что на многих процессорах инструкция div выдает оба значения, и вы всегда можете рассчитывать на то, что компилятор распознает, что соседние операторы / и % на одних и тех же входных данных могут быть объединены в одну операцию.

person Richard Pennington    schedule 16.07.2011

Это стоит меньше времени, если вам нужны обе ценности. ЦП всегда вычисляет остаток и частное при выполнении деления. Если использовать «/» один раз и «%» один раз, процессор вычислит оба числа дважды.

(простите мой плохой английский, я не родной)

person cuihao    schedule 16.07.2011
comment
-1: Это не совсем правильно, поскольку компилятор, вероятно, все равно оптимизирует его. Кроме того, div() должен выполнить дополнительную проверку, чтобы гарантировать, что результат неотрицательный. - person Jørgen Fogh; 16.07.2011
comment
@Jørgen Fogh Утверждение, что div() должен выполнить дополнительную проверку, не совсем верно. Различные процессоры предоставляют частное и остаток, как указано в div(), не прибегая к дополнительной проверке. Это проблема, зависящая от платформы. Чек может или может быть необходим. - person chux - Reinstate Monica; 09.09.2014