Функции С++ для целочисленного деления с четко определенной стратегией округления

Я хочу что-то в С++, что позволяет мне делать эффективное целочисленное деление с заданным действием округления, примерно так:

div_down(-4,3)        ==> -2
div_up(4,3)           ==> 2
div_to_zero(-4,3)     ==> -1
div_to_nearest(5,3)   ==> 2

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

Это существует?

Если нет, какой хороший способ сделать это? Я могу придумать несколько возможных подходов:

  • Попробуйте реализовать их как отдельные выражения, которые статически оптимизируют
  • Используйте константные выражения для определения поведения цели и выбора из нескольких реализаций, возможно используя шаблоны (но как именно?)

Ответ 1

Это то, что у меня есть до сих пор, с предварительным условием d > 0. Все они работают, но могут ли они быть упрощены?

int div_down(int n, int d) {
  if (n < 0) {
    return -((d - n - 1) / d);
  } else {
    return n / d;
  }
}

int div_up(int n, int d) {
  if (n < 0) {
    return -(-n / d);
  } else {
    return (n + d - 1) / d;
  }
}

int div_to_zero(int n, int d) {
  return n / d;
}

int div_to_nearest(int n, int d) {
  if (n < 0) {
    return (n - d/2 + 1) / d;
  } else {
    return (n + d/2) / d;
  }
}

Ответ 2

Последний черновик С++ 11, n3242, который почти идентичен фактическому стандарту С++ 11, говорит об этом в пункте 5.6 пункта 4 (стр. 118):

Для интегральных операндов оператор/дает алгебраическое отношение с отброшенной фракцией; (см. примечание 80)

Примечание 80 состояний (обратите внимание, что примечания являются ненормативными):

80) Это часто называют усечением к нулю.

Для полноты, точка 4 продолжает указывать:

если частное a/b представимо в типе результата, (a/b) * b + a% b равно a.

который, как можно показать, требует, чтобы знак a%b был таким же, как знак a (если не ноль).

Примечание. Фактический стандарт С++ 11 не доступен в режиме онлайн. Однако проекты есть. К счастью, различия между последним проектом (N3242) и фактическим стандартом малы. См. этот ответ.

ПРИМЕЧАНИЕ. Я не уверен, какие компиляторы придерживаются стандарта С++ 11.


So div_to_zero() - регулярное деление /.

Для других функций, боюсь, вам придется протестировать знаки a и b и соответствующим образом скорректировать. Иногда может потребоваться дополнительная проверка того, равен ли a%b нулю. Итак, мы смотрим на 12 тестовых примеров на каждую функцию здесь (3 для знака или нулевой точки a, раз 2 для знака b, раз 2, если a%b равно нулю или нет).

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

Я знаю, что я не ответил на ваш вопрос, но информация выше показалась мне ценной и была слишком большой, чтобы вписаться в комментарий.

Ответ 3

Старый пост, но вот он идет. Просьба принять и оценить его, если хотите.

int div_to_zero(int n, int d) { return n / d; }
//as per C++11 standard note 80

int div_up(int n, int d) {
    return n / d + (((n < 0) ^ (d > 0)) && (n % d));
} //i.e. +1 iff (not exact int && positive result)

int div_down(int n, int d) {
    return n / d - (((n > 0) ^ (d > 0)) && (n % d));
} //i.e. +1 iff (not exact int && negative result)

int div_to_nearest(int n, int d) {
    return (2*n - d + 2*(true&&(n<0^d>0))*d) / (2*d); 
} //i.e. +-0.5 as per pre-rounding result sign, then div_to-zero 
//it however rounds numbers like +/- 3.5 towards 0 and not even.

Примечание. Большинство современных компиляторов будут использовать операцию единичного деления для n/d и n% d, используемых совместно. Таким образом, их производительность лучше всего уменьшает количество перемещений памяти.