简体   繁体   中英

Weird results of modulo arithmetic with negative numbers and unsigned denominators

I have an array in C that I want to address in manner similar to a circular buffer, so for example: a[-1] would return me the last element of the array.

To do that I tried to use modulo arithmetic (obviously), problem is, I'm getting quite weird results when negative numbers are involved:

-1 % 4 = -1
-1 % 4U = 3

So far, so good.

-1 % 4000 = -1
(-1+4000U) % 4000U = 3999
(-1) % 4000U = 3295

Question: The value (3295) does hold for the (a/b)*b + a%b shall equal a, truncated towards zero (for a=-1, b=4000 ) from C standard (6.5.5#6) so it's not a bug per se , but why is the standard defined this way?! Surely, there must be some logic in this...

How do I have to write a%b to get sensible results for negative a (as (a+b)%b stops working when abs(a)>b )?

Test application:

#include <stdio.h>
int main(int argc, char **argv) {
  int i=0;
#define MAX_NUM 4000U
  int weird = (i-1)%MAX_NUM;
  printf("%i\n", weird);
  printf("%i\n", (i-1+MAX_NUM))%MAX_NUM);
  printf("a: %i, b: %i, a from equation: %i\n", i-1, MAX_NUM,
    ((i-1)/MAX_NUM)*MAX_NUM + weird);
  return 0;
}

Arithmetic in C always (short of some oddities with bit shift operators) promotes all operands to a common type before performing the operation. Thus:

(-1) % 4000U

is promoted as (assuming 32 bit ints):

0xffffffffu % 4000u

which yields 3295.

If you want to use modular arithmetic for array offsets that could be negative, you first need to abandon using unsigned arithmetic on the offsets. As such, your results will now be in the range -MAX_NUM+1 to MAX_NUM-1 , due to C's ugly definition of signed integer division and remainder. If the code is not performance critical, just add if (result<0) result+=MAX_NUM; and be done with it. If you really need to avoid the branch (and you've measured to determine that you need to avoid it) then ask again how to optimize this computation and myself or someone brighter than me on SO will surely be able to help. :-)

As 6.5.3 says, "The usual arithmetic conversions are performed on the operands." In the case of your example:

(-1) % 4000U

that means converting the -1 into an unsigned int . Your -1, therefore, is really being interpreted as 4294967295... for which the remainder is exactly what you're seeing: 3295.

The "Usual arithmetic conversions" are described in 6.3.1.8.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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