When does the signedness of an integer really matter? - c

Due to the way conversions and operations are defined in C, it seems to rarely matter whether you use a signed or an unsigned variable:
uint8_t u; int8_t i;
u = -3; i = -3;
u *= 2; i *= 2;
u += 15; i += 15;
u >>= 2; i >>= 2;
printf("%u",u); // -> 2
printf("%u",i); // -> 2
So, is there a set of rules to tell under which conditions the signedness of a variable really makes a difference?

It matters in these contexts:
division and modulo: -2/2 = 1, -2u/2 = UINT_MAX/2-1, -3%4 = -3, -3u%4 = 1
shifts. For negative signed values, the result of >> and << are implementation defined or undefined, resp. For unsigned values, they are always defined.
relationals -2 < 0, -2u > 0
overflows. x+1 > x may be assumed by the compiler to be always true iff x has signed type.

Yes. Signedness will affect the result of Greater Than and Less Than operators in C. Consider the following code:
unsigned int a = -5;
unsigned int b = 7;
if (a < b)
printf("Less");
else
printf("More");
In this example, "More" is incorrectly output, because the -5 is converted to a very high positive number by the compiler.
This will also affect your arithmetic with different sized variables. Again, consider this example:
unsigned char a = -5;
signed short b = 12;
printf("%d", a+b);
The returned result is 263, not the expected 7. This is because -5 is actually treated as 251 by the compiler. Overflow makes your operations work correctly for same-sized variables, but when expanding, the compiler does not expand the sign bit for unsigned variables, so it treats them as their original positive representation in the larger sized space. Study how two's compliment works and you'll see where this result comes from.

It affects the range of values that you can store in the variable.

It is relevant mainly in comparison.
printf("%d", (u-3) < 0); // -> 0
printf("%d", (i-3) < 0); // -> 1

Overflow on unsigned integers just wraps around. On signed values this is undefined behavior, everything can happen.

The signedness of 2's complement numbers is simply just a matter of how you are interpreting the number. Imagine the 3 bit numbers:
000
001
010
011
100
101
110
111
If you think of 000 as zero and the numbers as they are natural to humans, you would interpret them like this:
000: 0
001: 1
010: 2
011: 3
100: 4
101: 5
110: 6
111: 7
This is called "unsigned integer". You see everything as a number bigger than/equal to zero.
Now, what if you want to have some numbers as negative? Well, 2's complement comes to rescue. 2's complement is known to most people as just a formula, but in truth it's just congruency modulo 2^n where n is the number of bits in your number.
Let me give you a few examples of congruency:
2 = 5 = 8 = -1 = -4 module 3
-2 = 6 = 14 module 8
Now, just for convenience, let's say you decide to have the left most bit of a number as its sign. So you want to have:
000: 0
001: positive
010: positive
011: positive
100: negative
101: negative
110: negative
111: negative
Viewing your numbers congruent modulo 2^3 (= 8), you know that:
4 = -4
5 = -3
6 = -2
7 = -1
Therefore, you view your numbers as:
000: 0
001: 1
010: 2
011: 3
100: -4
101: -3
110: -2
111: -1
As you can see, the actual bits for -3 and 5 (for example) are the same (if the number has 3 bits). Therefore, writing x = -3 or x = 5 gives you the same result.
Interpreting numbers congruent modulo 2^n has other benefits. If you sum 2 numbers, one negative and one positive, it could happen on paper that you have a carry that would be thrown away, yet the result is still correct. Why? That carry was a 2^n which is congruent to 0 modulo 2^n! Isn't that convenient?
Overflow is also another case of congruency. In our example, if you sum two unsigned numbers 5 and 6, you get 3, which is actually 11.
So, why do you use signed and unsigned? For the CPU there is actually very little difference. For you however:
If the number has n bits, the unsigned represents numbers from 0 to 2^n-1
If the number has n bits, the signed represents numbers from -2^(n-1) to 2^(n-1)-1
So, for example if you assign -1 to a an unsigned number, it's the same as assigning 2^n-1 to it.
As per your example, that's exactly what you are doing. you are assigning -3 to a uint8_t, which is illegal, but as far as the CPU is concerned you are assigning 253 to it. Then all the rest of the operations are the same for both types and you end up getting the same result.
There is however a point that your example misses. operator >> on signed number extends the sign when shifting. Since the result of both of your operations is 9 before shifting you don't notice this. If you didn't have the +15, you would have -6 in i and 250 in u which then >> 2 would result in -2 in i (if printed with %u, 254) and 62 in u. (See Peter Cordes' comment below for a few technicalities)
To understand this better, take this example:
(signed)101011 (-21) >> 3 ----> 111101 (-3)
(unsigned)101011 ( 43) >> 3 ----> 000101 ( 5)
If you notice, floor(-21/8) is actually -3 and floor(43/8) is 5. However, -3 and 5 are not equal (and are not congruent modulo 64 (64 because there are 6 bits))

Related

Overflow and underflow in unsigned integers

Suppose, I'm trying to subtract 2 unsigned integers:
247 = 1111 0111
135 = 1000 0111
If we subtract these 2 binary numbers we get = 0111 0000
Is this a underflow, since we only need 7 bits now?? Or how does that work??
Underflow in unsigned subtraction c = a - b occurs whenever b is larger than a.
However, that's somewhat of a circular definition, because how many kinds of machines perform the a < b comparison is by subtracting the operands using wraparound arithmetic, and then detecting the overflow based on the two operands and the result.
Note also that in C we don't speak about "overflow", because there is no error condition: C unsigned integers provide that wraparound arithmetic that is commonly found in hardware.
So, given that we have the wraparound arithmetic, we can detect whether wraparound (or overflow, depending on point of view) has taken place in a subtraction.
What we need is the most significant bits from a, b and c. Let's call them A, B and C. From these, the overflow V is calculated like this:
A B C | V
------+--
0 0 0 | 0
0 0 1 | 1
0 1 0 | 1
0 1 1 | 1
1 0 0 | 0
1 0 1 | 0
1 1 0 | 0
1 1 1 | 1
This simplifies to
A'B + A'C + BC
In other words, overflow in the unsigned subtraction c = a - b happens whenever:
the msb of a is 0 and that of b is 1;
or the msb of a is 0 and that of c is 1;
or the msb of b is 1 and that of c is also 1.
Subtracting 247 - 135 = 112 is clearly not overflow, since 247 is larger than 135. Applying the rules above, A = 1, B = 0 and C = 0. The 1 1 0 row of the table has a 0 in the V column: no overflow.
Generally, “underflow” means the ideal mathematical result of a calculation is below what the type can represent. If 7 is subtracted from 5 in unsigned arithmetic, the ideal mathematical result would be −2, but an unsigned type cannot represent −2, so the operation underflows. Or, in an eight-bit signed type that can represent numbers from −128 to +127, subtracting 100 from −100 would ideally produce −200, but this cannot be represented in the type, so the operation underflows.
In C, unsigned arithmetic is said not to underflow or overflow because the C standard defines the operations to be performed using modulo arithmetic instead of real-number arithmetic. For example, with 32-bit unsigned arithmetic, subtracting 7 from 5 would produce 4,294,967,294 (in hexadecimal, FFFFFFFE16), because it has wrapped modulo 232 = 4,294,967,296. People may nonetheless use the terms “underflow” or “overflow” when discussing these operations, intended to refer to the mathematical issues rather than the defined C behavior.
In other words, for whatever type you are using for arithmetic there is some lower limit L and some upper limit U that the type can represent. If the ideal mathematical result of an operation is less than L, the operation underflows. If the ideal mathematical result of an operation is greater than U, the operation overflows. “Underflow” and “overflow” mean the operation has gone out of the bounds of the type. “Overflow” may also be used to refer to any exceeding of the bounds of the type, including in the low direction.
It does not mean that fewer bits are needed to represent the result. When 100001112 is subtracted from 111101112, the result, 011100002 = 11100002, is within bounds, so there is no overflow or underflow. The fact that it needs fewer bits to represent is irrelevant.
(Note: For integer arithmetic, “underflow” or “overflow” is defined relative to the absolute bounds L and U. For floating-point arithmetic, these terms have somewhat different meanings. They may be defined relative to the magnitude of the result, neglecting the sign, and they are defined relative to the finite non-zero range of the format. A floating-point format may be able to represent 0, then various finite non-zero numbers, then infinity. Certain results between 0 and the smallest non-zero number the format can represent are said to underflow even though they are technically inside the range of representable numbers, which is from 0 to infinity in magnitude. Similarly, certain results above the greatest representable finite number are said to overflow even though they are inside the representable range, since they are less than infinity.)
Long story short, this is what happens when you have:
unsigned char n = 255; /* highest possible value for an unsigned char */
n = n + 1; /* now n is "overflowing" (although the terminology is not correct) to 0 */
printf("overflow: 255 + 1 = %u\n", n);
n = n - 1; /* n will now "underflow" from 0 to 255; */
printf("underflow: 0 - 1 = %u\n", n);
n *= 2; /* n will now be (255 * 2) % 256 = 254;
/* when the result is too high, modulo with 2 to the power of 8 is used */
/* for an 8 bit variable such as unsigned char; */
printf("large overflow: 255 * 2 = %u\n", n);
n = n * (-2) + 100; /* n should now be -408 which is 104 in terms of unsigned char. */
/* (Logic is this: 408 % 256 = 152; 256 - 152 = 104) */
printf("large underflow: 255 * 2 = %u\n", n);
The result of that is (compiled with gcc 11.1, flags -Wall -Wextra -std=c99):
overflow: 255 + 1 = 0
underflow: 0 - 1 = 255
large overflow: 255 * 2 = 254
large underflow: 255 * 2 = 104
Now the scientific version: The comments above represent just a mathematical model of what is going on. To better understand what is actually happening, the following rules apply:
Integer promotion:
Integer types smaller than int are promoted when an operation is
performed on them. If all values of the original type can be
represented as an int, the value of the smaller type is converted to
an int; otherwise, it is converted to an unsigned int.
So what actually happens in memory when the computer does n = 255; n = n + 1; is this:
First, the right side is evaluated as an int (signed), because the result fits in a signed int according to the rule of integer promotion. So the right side of the expression becomes in binary: 0b00000000000000000000000011111111 + 0b00000000000000000000000000000001 = 0b00000000000000000000000100000000 (a 32 bit int).
Truncation
The 32-bit int loses the most significant 24 bits when being assigned
back to an 8-bit number.
So, when 0b00000000000000000000000100000000 is assigned to variable n, which is an unsigned char, the 32-bit value is truncated to an 8-bit value (only the right-most 8 bits are copied) => n becomes 0b00000000.
The same thing happens for each operation. The expression on the right side evaluates to a signed int, than it is truncated to 8 bits.

Minimum number of bits required to represent x in two's complement

I was reading a book which has an exercise as:
/* howManyBits - return the minimum number of bits required to represent x in
* two's complement
* Examples: howManyBits(12) = 5
* howManyBits(0) = 1
* howManyBits(-1) = 1
* howManyBits(0x80000000) = 32
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 90
* Rating: 4
*/
int howManyBits(int x) {
return 0;
}
I don't even understand the question itself, how come 12 needs 5 bits, isn't that 1100, which is 4 bits? And how come -1 only need 1 bit? isn't that 1...1 is -1 in two's complement, so 32 bits are required?
How come 12 needs 5 bits, isn't that 1100, which is 4 bits?
With two's complement, 1 bit more is required to classify the signedness of the value. This is (usually) the left-most bit of the bit pattern, also called "most significant bit" (MSB). If this signed bit is 1 the value is negative, if it is 0 the value is positive. So you need 5 bits to represent the value 12 = 01100, not 4.
And how come -1 only needs 1 bit?
When you only have 1 bit, this bit is used for the signedness of the value too and can either represent the values 0 or -1; -1 instead of 1 since the signed bit set to 1 means negative value.
Both the 12 and the -1 points can be resolved by remembering that this is 2's complement. The question asks for the minimum number of bits needed.
In a 2's complement value, the value is negative if the highest-order bit is a 1.
A 1-bit 2's complement number can represent 2 values, -1 and 0, with the bit patterns 1b and 0b respectively.
It's a little more clear if you look at 2-bit values, which can represent:
-2: 10b
-1: 11b
0: 00b
1: 01b
Note that the 1-bit 2's complement value 1b does not represent 1. Similarly, 12 requires 5 bits: 01100b. The value of the 4-bit 1100b is -4.
A bit is used to represent the sign, 0 for positive and 1 for negative. For example, the minimum number of bits necessary to represent +12 and -12 are five:
+12 = 0 1100
-12 = 1 0100

Formula to convert byte array representing signed integer into integer

This question is more generic without a particular language. I am more interested in solving this generally across languages. Every answer I find references a built-in method of something like getInt32 to extract an integer from a byte array.
I have a byte array which contains the big-endian representation of a signed integer.
1 -> [0, 0, 0, 1]
-1 -> [255, 255, 255, 255]
-65535 -> [255, 255, 0, 1]
Getting the values for the positive cases are easy:
arr[3] | arr[2] << 8 | arr[1] << 16 | arr[0] << 24
What I would like to figure out is the more general case. I have been reading about 2s complement, which lead me to the python function from Wikipedia:
def twos_complement(input_value, num_bits):
'''Calculates a two's complement integer from the given input value's bits'''
mask = 2**(num_bits - 1) - 1
return -(input_value & mask) + (input_value & ~mask)
which in turn lead me to produce this function:
# Note that the mask from the wiki function has an additional - 1
mask = 2**(32 - 1)
def arr_to_int(arr):
uint_val = arr[3] | arr[2] << 8 | arr[1] << 16 | arr[0] << 24
if (determine_if_negative(uint_val)):
return -(uint_val & mask) + (uint_val & ~mask)
else:
return uint_val
In order for my function to work I need to fill in determine_if_negative (I should mask the signed bit and check if it is 1). But is there a standard formula to handle this? One thing I found is that in some languages, like Go, the bitshift might overflow the int value.
This is pretty hard to search because I get a thousand results explaining the difference between big-endian and little-endian or results explaining twos complement, and many more giving examples of using the standard library but I haven't seen a complete formula for bitwise functions.
Is there a canonical example in C or similar language of converting a char array using only array access and bitwise functions (ie, no memcpy or pointer casting or tricky stuff)
The bitwise method only works properly for unsigned values so you will need to build the unsigned integer and then convert to signed. The code could be:
int32_t val( uint8_t *s )
{
uint32_t x = ((uint32_t)s[0] << 24) + ((uint32_t)s[1] << 16) + ((uint32_t)s[2] << 8) + s[3];
return x;
}
Note, this assumes you are on a 2's complement system which also defines unsigned->signed conversion as no change in repesentation. If you want to support other systems too , it would be more complicated.
The casts are necessary so that the shift is performed over the right width.
Even c might be too high level for this. After all, the exact representation of int is machine dependent. On top of that, not all integer types on all systems are 2s complement.
When you mention a byte array and converting it to integer you must specify what format that byte array implies.
If you assume 2s complement and little endian (like intel/amd). Then the last byte contains the sign.
For simplicity's sake lets start with a 4 digit 2s complement integer,then byte byte, then 2 byte integers and then 4.
BIN SIGNED_DEC UNSIGNED_DEC
000 0 0
001 1 1
010 2 2
100 -4(oops) 4
101 -3 5
110 -1 6
111 -1 7
---
123
let each bit be b3,b2,b1, where b1 is the most significant bit(and sign)
then the formula would be:
b3*2^2+b2*2^1-b1*4
for a byte we have 4 bits and the formula would look like this:
b4*2^3 + b3*2^2+b2*2^1-b1*2^3
for 2 bytes it is the same but we have to multiple the most significant byte by 256 and the negative value would be 256^2 or 2^16.
/**
* returns calculated value of 2s complement bit string.
* expects string of bits 0or1. if a chanracter is not 1 it is considered 0.
*
*/
public static long twosComplementFromBitArray(String input) {
if(input.length()<2) throw new RuntimeException("intput too short ");
int sign=input.charAt(0)=='1'?1:0;
long unsignedComplementSum=1;
long unsignedSum=0;
for(int i=1;i<input.length();++i) {
char c=input.charAt(i);
int val=(c=='1')?1:0;
unsignedSum=unsignedSum*2+val;
unsignedComplementSum*=2;
}
return unsignedSum-sign*unsignedComplementSum;
}
public static void main(String[] args) {
System.out.println(twosComplementFromBitArray("000"));
System.out.println(twosComplementFromBitArray("001"));
System.out.println(twosComplementFromBitArray("010"));
System.out.println(twosComplementFromBitArray("011"));
System.out.println(twosComplementFromBitArray("100"));
System.out.println(twosComplementFromBitArray("101"));
System.out.println(twosComplementFromBitArray("110"));
System.out.println(twosComplementFromBitArray("111"));
}
outputs:
0
1
2
3
-4
-3
-2
-1

Next set of n elements in set using bitwise operators

Reading the book "C - A reference manual (Fifth Edition)", I stumbled upon this piece of code (each integer in the SET is represented by a bit position):
typedef unsigned int SET;
#define emptyset ((SET) 0)
#define first_set_of_n_elements(n) (SET)((1<<(n))-1)
/* next_set_of_n_elements(s): Given a set of n elements,
produce a new set of n elements. If you start with the
result of first_set_of_n_elements(k)/ and then at each
step apply next_set_of_n_elements to the previous result,
and keep going until a set is obtained containing m as a
member, you will have obtained a set representing all
possible ways of choosing k things from m things. */
SET next_set_of_n_elements(SET x) {
/* This code exploits many unusual properties of unsigned arithmetic. As an illustration:
if x == 001011001111000, then
smallest == 000000000001000
ripple == 001011010000000
new_smallest == 000000010000000
ones == 000000000000111
returned value == 001011010000111
The overall idea is that you find the rightmost
contiguous group of 1-bits. Of that group, you slide the
leftmost 1-bit to the left one place, and slide all the
others back to the extreme right.
(This code was adapted from HAKMEM.) */
SET smallest, ripple, new_smallest, ones;
if (x == emptyset) return x;
smallest = (x & -x);
ripple = x + smallest;
new_smallest = (ripple & -ripple);
ones = ((new_smallest / smallest) >> 1) -1;
return (ripple | ones);
}
I'm lost at the calculation of 'ones', and it's significance in the calculation. Although I can understand the calculation mathematically, I cannot understand why this works, or how.
On a related note, the authors of the book claim that the calculation for first_set_of_n_elements "exploits the properties of unsigned subtractions". How is (2^n)-1 an "exploit"?
The smallest computation gets the first non-0 bit of your int. How does it works ?
Let n be the bit length of your int. The opposite of a number x (bits bn-1...b0) is computed in a way that when you sum x to -x, you will get 2n. Since your integer is only n-bit long, the resulting bit is discarded and you obtain 0.
Now, let b'n-1...b'0 be the binary representation of -x.
Since x+(-x) must be equal to 2n, when you meet the first bit 1 of x (say at position i), the related -x bit will also be set to 1 and when adding the numbers, you'll get a carry.
To obtain the 2n, this carry must propagate through all the bits until the end of the bit sequence of your int. Thus, the bit of -x at each position j with i < j < n follows the properties below :
bj + b'j + 1 = 10(binary)
Then, from the above we can infer that :
bj = NOT(b'j) and thus, that bj & b'j = 0
On the other hand, the bits b'j of -x located at a position j such that 0 <= j < i are ruled by what follows :
bj + b'j = 0 or 10
Since all the related bj are set to 0, the only option is b'j = 0
Thus, the only bit that is 1 in both x and -x is the one at position i
In your example :
x = 001011001111000
-x = 110100110001000
Thus,
0.0.1.0.1.1.0.0.1.1.1.1.0.0.0
1.1.0.1.0.0.1.1.0.0.0.1.0.0.0 AND
\=====================/
0.0.0.0.0.0.0.0.0.0.1.0.0.0
The ripple then turns every contiguous "1" after position i (bit i included) to 0, and the first following 0 bit to 1 (due to the carry propagation). That's why you ripple is :
r(x) = 0.0.1.0.1.1.0.1.0.0.0.0.0.0.0
Ones is computed as the division of smallest(r(x)) over smallest(x). Since smallest(x) is an integer with only a single bit set to 1 at position i, you have :
(smallest(r(x)) / smallest(x)) >> 1 = smallest(r(x)) >>(i+1)
The resulting integer has also only one bit set to 1, at say index p, thus, substract -1 to this value will get you an integer ones such that :
For each j such that 0 <= j < p,
onesj = 1
For each j such that p <= j < n,
onesj = 0
Finally, the return value is the integer such that :
The first subsequence of 1-bit of the argument is set to 0.
All the 0-bit before the subsequence are set to 1.
The first 0-bit after the subsequence is set to 1.
The remaining bits are left unchanged
Then, I can't explain the remaining part since I did not understand the sentence :
a set is obtained containing m as a member
First of all, this code is rather obscure and doesn't look like anything worth spending time pondering upon, it will only yield useless knowledge.
The "exploit" is that the code relies on implementation-defined behavior of various arithmetric rules.
001 0110 0111 1000 is a 15-bit number. Why the author uses 15 bit numbers instead of 16, I
don't know. Seems like a typo remaining even after 5 editions.
If we put a minus sign in front of that binary number on a two's complement system (explanation of two's complement here), it will turn into 1110 1001 1000 1000. Because the compiler will preserve the decimal presentation of the number (5752) and translate it to its negative equivalent (-5752). (However, the actual data type will remain unsigned int, so if you tried to print it you would get the garbage number 59784.)
0001 0110 0111 1000
AND 1110 1001 1000 1000
= 0000 0000 0000 1000
The C standard does not enforce two's complement, so the code in that book is not portable.
It's a little misleading, because it actually exploits 2's complement. First, the calculation of smallest:
In 2's complement representation, for the x in the comments -x is 110100110001000. Focus on the least significant bit of x that is a one; since two's complement is essentially 1's complement plus 1, that bit will be set in both x and -x and no other bit position after it (on the way to the LSB) will have that property. That's how you get the smallest bit set.
ripple is pretty straightforward and is named as such because it propagates ones to the MSB, and smallest_ripple follows from the description above.
ones is the number we should add to the ripple in order to continue choosing n elements, picture it below:
ones: 11 next set: 100011
ones: 1 next set: 100101
ones: 0 next set: 100110
Running it will indeed show you all the ways of choosing n bits out of CHAR_BIT * sizeof(int) - 1 items (CHAR_BIT * sizeof(int) bits are needed because -x of an n-bit number needs at worst n+1 bits to be represented).
First, here a exemple of the output we can get with n=4. The idea is that we start with 'n' LSB set to '1', and then we iterate through all the combinations of numbers with the same count of bits set to '1':
1111
10111
11011
11101
11110
100111
101011
101101
101110 (*)
110011
110101
110110
111001
111010
111100
1000111
1001011
1001101
It is working the following way. I will use the number with the star above as an exemple:
101110
We get the LSB set to '1' as clearly explained in other answers.
101110
& 010011
= 000010
We "move" the LSB one position to the left by adding it to the original number. If the bit immediately on the left is '0', this is easy to understand, as the subsequent operations will do nothing. If this left bit is '1', we get a carry which will propagate to the left. The problem with this last case is that the numbers of '1' will change, so we have to set back some '1' to keep their count constant.
101110
+ 000010
= 110000
To do so, we retrieve the LSB of the new result, and by dividing it with the previous LSB, we get the number of bits over which the carry has propagated. This is converted to plain '1' at the lowest positions with the '-1',
010000
/ 000010
= 001000
>> 1
- 1
= 000011
We finally OR the result of the addition and the ones.
110011
I would say that the "exploit" is on the unsigned change of sign, in the operation (x & -x).

Homework - C bit puzzle - Perform % using C bit operations (no looping, conditionals, function calls, etc)

I'm completely stuck on how to do this homework problem and looking for a hint or two to keep me going. I'm limited to 20 operations (= doesn't count in this 20).
I'm supposed to fill in a function that looks like this:
/* Supposed to do x%(2^n).
For example: for x = 15 and n = 2, the result would be 3.
Additionally, if positive overflow occurs, the result should be the
maximum positive number, and if negative overflow occurs, the result
should be the most negative number.
*/
int remainder_power_of_2(int x, int n){
int twoToN = 1 << n;
/* Magic...? How can I do this without looping? We are assuming it is a
32 bit machine, and we can't use constants bigger than 8 bits
(0xFF is valid for example).
However, I can make a 32 bit number by ORing together a bunch of stuff.
Valid operations are: << >> + ~ ! | & ^
*/
return theAnswer;
}
I was thinking maybe I could shift the twoToN over left... until I somehow check (without if/else) that it is bigger than x, and then shift back to the right once... then xor it with x... and repeat? But I only have 20 operations!
Hint: In decadic system to do a modulo by power of 10, you just leave the last few digits and null the other. E.g. 12345 % 100 = 00045 = 45. Well, in computer numbers are binary. So you have to null the binary digits (bits). So look at various bit manipulation operators (&, |, ^) to do so.
Since binary is base 2, remainders mod 2^N are exactly represented by the rightmost bits of a value. For example, consider the following 32 bit integer:
00000000001101001101000110010101
This has the two's compliment value of 3461525. The remainder mod 2 is exactly the last bit (1). The remainder mod 4 (2^2) is exactly the last 2 bits (01). The remainder mod 8 (2^3) is exactly the last 3 bits (101). Generally, the remainder mod 2^N is exactly the last N bits.
In short, you need to be able to take your input number, and mask it somehow to get only the last few bits.
A tip: say you're using mod 64. The value of 64 in binary is:
00000000000000000000000001000000
The modulus you're interested in is the last 6 bits. I'll provide you a sequence of operations that can transform that number into a mask (but I'm not going to tell you what they are, you can figure them out yourself :D)
00000000000000000000000001000000 // starting value
11111111111111111111111110111111 // ???
11111111111111111111111111000000 // ???
00000000000000000000000000111111 // the mask you need
Each of those steps equates to exactly one operation that can be performed on an int type. Can you figure them out? Can you see how to simplify my steps? :D
Another hint:
00000000000000000000000001000000 // 64
11111111111111111111111111000000 // -64
Since your divisor is always power of two, it's easy.
uint32_t remainder(uint32_t number, uint32_t power)
{
power = 1 << power;
return (number & (power - 1));
}
Suppose you input number as 5 and divisor as 2
`00000000000000000000000000000101` number
AND
`00000000000000000000000000000001` divisor - 1
=
`00000000000000000000000000000001` remainder (what we expected)
Suppose you input number as 7 and divisor as 4
`00000000000000000000000000000111` number
AND
`00000000000000000000000000000011` divisor - 1
=
`00000000000000000000000000000011` remainder (what we expected)
This only works as long as divisor is a power of two (Except for divisor = 1), so use it carefully.

Resources