是否有使用modulo进行向后环绕的表达式(“反向溢出”)?

问题描述 投票:16回答:5

对于由范围R = [x,y]限制的任何整数输入W,对于缺少更好的项,“溢出”,W对R的是W % (y-x+1) + x。如果W超过y,这会导致它回绕。

作为这个原则的一个例子,假设我们迭代一个日历的月份:

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

其中两个整数都在0到11之间(包括0和11)。因此,上面的表达式将整数“钳制”到范围R = [0,11]。这种使用表达式的方法简单,优雅且有利,因为它省略了分支。

现在,如果我们想要做同样的事情,但倒退呢?以下表达式有效:

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

但这是深奥的。它怎么能被美化?


tl; dr - 可以进一步简化表达式((x-1) % k + k) % k吗?

注意:指定C ++标记,因为其他语言以不同方式处理模运算符的负操作数。

c++ modulo algebra
5个回答
20
投票

你的表达应该是((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;}
}

这也允许使用标记为0到11或1到12的月份,相应地设置min_valmax_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。如果需要,请随意添加断言。


7
投票

k%k将始终为0.我不是100%确定你要做什么,但似乎你希望上个月被夹在0到11之间(包括0和11)。

(this_month + 11) % 12

应该就够了。


4
投票

一般的解决方案是编写一个计算所需值的函数:

//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);
}

2
投票

Easy Peasy,不要使用第一个模块操作符,这是多余的:

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

这是一般情况

在这种情况下,你可以写11,但我仍然会做-1 + 11,因为它更清楚地说明你想要实现的目标。


1
投票

不确定你是否和我有同样的问题,但我的问题基本上是我想将所有数字限制在一定的范围内。假设范围是0-6,因此使用%7意味着任何高于6的数字将回绕到0或更高。实际问题是小于零的数字没有回绕到6.我有一个解决方案(其中X是数字范围的上限,0是最小值):

if(inputNumber <0)//If this is a negative number
{
(X-(inputNumber*-1))%X; 
}
else
{
inputNumber%X;
}
© www.soinside.com 2019 - 2024. All rights reserved.