Есть ли выражение, использующее modulo для обратной обработки ( "обратное переполнение" )?

Для любого целого числа W, ограниченного диапазоном R = [x, y], "переполнение" из-за отсутствия лучшего термина W над R равно W % (y-x+1) + x. Это заставляет его обертываться назад, если W превышает y.

В качестве примера этого принципа предположим, что мы перебираем за календарные месяцы:

int this_month = 5;
int next_month = (this_month + 1) % 12;

где оба целых числа будут от 0 до 11 включительно. Таким образом, выражение выше "фиксирует" целое число в диапазоне R = [0,11]. Этот подход использования выражения простой, изящный и выгодный, поскольку он опускает ветвление.

Теперь, что, если мы хотим сделать то же самое, но назад? Следующее выражение работает:

int last_month = ((this_month - 1) % 12 + 12) % 12;

но это заумно. Как это можно украсить?


tl; dr - Можно ли еще упростить выражение ((x-1) % k + k) % k?

Примечание. Тег С++ указан, потому что другие языки обрабатывают отрицательные операнды для оператора modulo по-разному.

Ответ 1

Ваше выражение должно быть ((x-1) + k) % k. Это позволит правильно обернуть x = 0 до 11. В общем, если вы хотите сделать шаг назад больше, чем на 1, вам нужно убедиться, что вы добавили достаточно, чтобы первый операнд операции по модулю был> = 0.

Вот реализация в C++:

int wrapAround(int v, int delta, int minval, int maxval)
{
  const int mod = maxval + 1 - minval;
  if (delta >= 0) {return  (v + delta                - minval) % mod + minval;}
  else            {return ((v + delta) - delta * mod - minval) % mod + minval;}
}

Это также позволяет использовать месяцы с min_val от 0 до 11 или от 1 до 12, соответственно устанавливая min_val и max_val.

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

int wrapAround(int v, int delta, int minval, int maxval)
{
  const int mod = maxval + 1 - minval;
  v += delta - minval;
  v += (1 - v / mod) * mod;
  return v % mod + minval;
}

Единственная проблема остается, если minval больше, чем maxval. Не стесняйтесь добавлять утверждение, если вам это нужно.

Ответ 2

k% k всегда будет 0. Я не уверен на 100%, что вы пытаетесь сделать, но кажется, что вы хотите, чтобы последний месяц был зажат между 0 и 11 включительно.

(this_month + 11) % 12

Достаточно.

Ответ 3

Общее решение - написать функцию, которая вычисляет требуемое значение:

//Returns floor(a/n) (with the division done exactly).
//Let ÷ be mathematical division, and / be C++ division.
//We know
//    a÷b = a/b + f (f is the remainder, not all 
//                   divisions have exact Integral results)
//and
//    (a/b)*b + a%b == a (from the standard).
//Together, these imply (through algebraic manipulation):
//    sign(f) == sign(a%b)*sign(b)
//We want the remainder (f) to always be >=0 (by definition of flooredDivision),
//so when sign(f) < 0, we subtract 1 from a/n to make f > 0.
template<typename Integral>
Integral flooredDivision(Integral a, Integral n) {
    Integral q(a/n);
    if ((a%n < 0 && n > 0) || (a%n > 0 && n < 0)) --q;
    return q;
}

//flooredModulo: Modulo function for use in the construction
//looping topologies. The result will always be between 0 and the
//denominator, and will loop in a natural fashion (rather than swapping
//the looping direction over the zero point (as in C++11),
//or being unspecified (as in earlier C++)).
//Returns x such that:
//
//Real a = Real(numerator)
//Real n = Real(denominator)
//Real r = a - n*floor(n/d)
//x = Integral(r)
template<typename Integral>
Integral flooredModulo(Integral a, Integral n) {
    return a - n * flooredDivision(a, n);
}

Ответ 4

Easy Peasy, не используйте первый оператор модуля, он лишний:

 int last_month = (this_month - 1 + 12) % 12;

который является общим случаем

В этом случае вы можете написать 11, но я все равно сделаю -1 + 11, поскольку он более четко заявляет, чего вы хотите достичь.

Ответ 5

Не уверен, что у вас была такая же проблема, как у меня, но моя проблема заключалась в том, что я хотел сдерживать все числа до определенного диапазона. Скажем, что диапазон был 0-6, поэтому использование% 7 означает, что любое число выше 6 обернется вокруг 0 ​​или выше. Фактическая проблема заключается в том, что числа, меньшие нуля, не обертывались вокруг до 6. У меня есть решение этого (где X - верхний предел вашего диапазона чисел, а 0 - минимальный):

if(inputNumber <0)//If this is a negative number
{
(X-(inputNumber*-1))%X; 
}
else
{
inputNumber%X;
}