Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Weird results of modulo arithmetic with negative numbers and unsigned denominators

Tags:

c

math

gcc

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;
}
like image 365
Hubert Kario Avatar asked Jan 29 '12 01:01

Hubert Kario


2 Answers

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. :-)

like image 57
R.. GitHub STOP HELPING ICE Avatar answered Nov 15 '22 14:11

R.. GitHub STOP HELPING ICE


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.

like image 40
Carl Norum Avatar answered Nov 15 '22 14:11

Carl Norum