简体   繁体   English

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

[英]Is there an expression using modulo to do backwards wrap-around (“reverse overflow”)?

For any whole number input W restricted by the range R = [ x , y ], the "overflow," for lack of a better term, of W over R is W % (y-x+1) + x .对于受范围R = [ x , y ] 限制的任何整数输入W ,由于缺乏更好的术语, W超过R的“溢出”是W % (y-x+1) + x This causes it wrap back around if W exceeds y .如果W超过y ,这会导致它回绕。

As an example of this principle, suppose we iterate over a calendar's months:作为这个原则的一个例子,假设我们迭代一个日历的月份:

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

where both integers will be between 0 and 11, inclusive.其中两个整数都在 0 到 11 之间,包括 0 和 11。 Thus, the expression above "clamps" the integer to the range R = [0,11].因此,上面的表达式将整数“限制”在R = [0,11] 的范围内。 This approach of using an expression is simple, elegant, and advantageous as it omits branching .这种使用表达式的方法简单、优雅且有利,因为它省略了分支

Now, what if we want to do the same thing, but backwards?现在,如果我们想做同样的事情,但是倒退怎么办? The following expression works:以下表达式有效:

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

but it's abstruse.但它很深奥。 How can it be beautified?怎样才能美化?


tl;dr - Can the expression ((x-1) % k + k) % k be simplified further? tl;dr - 表达式((x-1) % k + k) % k可以进一步简化吗?

Note: C++ tag specified because other languages handle negative operands for the modulo operator differently.注意:指定 C++ 标记是因为其他语言以不同方式处理模运算符的负操作数。

Your expression should be ((x-1) + k) % k .你的表达式应该是((x-1) + k) % k This will properly wrap x=0 around to 11. In general, if you want to step back more than 1, you need to make sure that you add enough so that the first operand of the modulo operation is >= 0.这将正确地将 x=0 环绕到 11。 一般来说,如果您想后退超过 1,您需要确保添加足够多,以便模运算的第一个操作数 >= 0。

Here is an implementation in C++:这是 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;}
}

This also allows to use months labeled from 0 to 11 or from 1 to 12, setting min_val and max_val accordingly.这也允许使用标记为 0 到 11 或 1 到 12 的min_val ,相应地设置min_valmax_val

Since this answer is so highly appreciated, here is an improved version without branching, which also handles the case where the initial value v is smaller than minval .由于这个答案非常受欢迎,这里有一个没有分支的改进版本,它也处理初始值v小于minval I keep the other example because it is easier to understand:我保留另一个例子,因为它更容易理解:

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

The only issue remaining is if minval is larger than maxval .剩下的唯一问题是minval是否大于maxval Feel free to add an assertion if you need it.如果需要,请随意添加断言。

k % k will always be 0. I'm not 100% sure what you're trying to do but it seems you want the last month to be clamped between 0 and 11 inclusive. k % k将始终为 0。我不是 100% 确定您要做什么,但似乎您希望将上个月限制在 0 和 11 之间(包括 0 和 11)。

(this_month + 11) % 12

Should suffice.应该够了。

The general solution is to write a function that computes the value that you want:一般的解决方案是编写一个函数来计算您想要的值:

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

Easy Peasy, do not use the first module operator, it is superfluous: Easy Peasy,不要使用第一个模块运算符,它是多余的:

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

which is the general case这是一般情况

In this instance you can write 11 , but I would still do the -1 + 11 as it more clearly states what you want to achieve.在这种情况下,您可以编写11 ,但我仍然会使用-1 + 11因为它更清楚地说明了您想要实现的目标。

Note that normal mod causes the pattern 0...11 to repeat at 12...23 , 24...35 , etc. but doesn't wrap on -11...-1 .请注意,正常模式会导致模式0...1112...2324...35等处重复,但不会在-11...-1上换行。 In other words, it has two sets of behaviors.换句话说,它有两组行为。 One from -infinity...-1 , and a different set of behavior from 0...infinity .来自-infinity...-1 ,以及来自0...infinity一组不同的行为。

The expression ((x-1) % k + k) % k fixes -11...-1 but has the same problem as normal mod with -23...-12 .表达式((x-1) % k + k) % k修复-11...-1但与普通 mod 具有相同的问题-23...-12 Ie while it fixes 12 additional numbers, it doesn't wrap around infinitely.即虽然它修复了 12 个额外的数字,但它不会无限地环绕。 It still has one set of behavior from -infinity...-12 , and a different behavior from -11...+infinity .它仍然具有来自-infinity...-12一组行为,以及来自-11...+infinity的不同行为。

This means that if you're using the function for offsets, it could lead to buggy code.这意味着,如果您将该函数用于偏移量,则可能会导致代码错误。

If you want a truly wrap around mod, it should handle the entire range, -infinity...infinity in exactly the same way.如果你想要一个真正的环绕 mod,它应该以完全相同的方式处理整个范围, -infinity...infinity

There is probably a better way to implement this, but here is an easy to understand implementation:可能有更好的方法来实现这一点,但这里有一个易于理解的实现:

// n must be greater than 0
func wrapAroundMod(a: Int, n: Int) -> Int {
    var offsetTimes: Int = 0

    if a < 0 {
        offsetTimes = (-a / n) + 1
    }

    return (a + n * offsetTimes) % n
}

Not sure if you were having the same problem as me, but my problem was essentially that I wanted to constrain all numbers to a certain range.不确定你是否和我有同样的问题,但我的问题本质上是我想将所有数字限制在某个范围内。 Say that range was 0-6, so using %7 means that any number higher than 6 will wrap back around to 0 or above.假设范围是 0-6,所以使用 %7 意味着任何大于 6 的数字都会回绕到 0 或更高。 The actual problem is that numbers less than zero didn't wrap back around to 6. I have a solution to that (where X is the upper limit of your number range and 0 is the minimum):实际问题是小于零的数字没有回绕到 6。我有一个解决方案(其中 X 是数字范围的上限,0 是最小值):

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM