Reputation: 55
I can only use ! ~ & ^ | + << >>
I am writing this in C.
I am trying to divide a number x by 2^n. So i thought if I shift x >> n that would work, but it does not work with odd negative integers. It originally looked like this:
int dl18(int x, int n) {
return (x >> n);
}
but if x = -9 and n = 1 the output should be -4 but it is -5. and if x = -9 and n = 0 the output is correct (-9).
Thanks in advance.
So I figured out doing this makes it work for everything unless n = 0 and x is a negative number:
return (~(x >> 31) & (x >> n)) | ((x >> 31) & ((x >> n) + 1));
Upvotes: 1
Views: 1107
Reputation: 21314
Here is a solution that avoids bit-shifting negative values. It does assume twos-complement representation, but it does not use the unary negative operator.
A bitmask is used to set neg
to a non-zero value if x
is negative, or to zero if x
is non-negative. Here a trick suggested by @Grzegorz Szpetkowski is used to avoid subtraction by 1: adding ~0
instead. If x
is negative, the value of x
is changed to the magnitude of x
. To avoid using the unary negative here, using a trick suggested by @chux, we take advantage of the fact that for a negative value in twos-complement, the corresponding positive value is equal to the bitwise negation of the negative representation plus 1.
This magnitude of x
can be bit-shifted without encountering implementation-dependent behavior. After performing the division, the result is converted back to a negative value if the original value was negative, by performing the same transformation as before.
#include <stdio.h>
#include <limits.h>
int divide_2n(int x, unsigned n);
int main(void)
{
printf("-7 / 4 = %d\n", divide_2n(-7, 2));
printf("27 / 8 = %d\n", divide_2n(27, 3));
printf("-27 / 8 = %d\n", divide_2n(-27, 3));
printf("-9 / 2 = %d\n", divide_2n(-9, 1));
printf("-9 / 1 = %d\n", divide_2n(-9, 0));
return 0;
}
int divide_2n(int x, unsigned n)
{
unsigned n_bits = CHAR_BIT * sizeof(int);
unsigned neg = x & (1U << (n_bits + ~0));
if (neg) {
x = ~(unsigned)x + 1;
}
x = (unsigned)x >> n;
if (neg) {
x = ~x + 1;
}
return x;
}
-7 / 4 = -1
27 / 8 = 3
-27 / 8 = -3
-9 / 2 = -4
-9 / 1 = -9
Upvotes: 1
Reputation: 37924
Assuming two's complement representation of signed integers and arithmetic shift behaviour of >>
operator, the answer could be:
int dl18(int x, int n) {
if (x < 0) {
x += (1 << n) - 1;
}
return x >> n;
}
The addition is necessary, because >>
rounds for negative numbers towards negative infinity. By adding 2^n - 1
, the result is always truncated towards zero, just like it happens for /
operator.
Due to your requirements, assuming that int
has 4
bytes (and to be extra pedantic CHAR_BIT = 8
), the expression may be rewritten (obfuscated) as:
(x + ((x >> 31) & ((1 << n) + ~0))) >> n
The idea of x >> 31
is to replicate MSB bit, so the mask becomes either all ones (i.e. 0xFFFFFFFF
), or all zeros, which is then used to either preserve or eliminate ((1 << n) - 1)
from addition. Parentheses around &
are necessary, because addition has higher precedence than bitwise AND.
This algorithm is also used by GCC compiler. For instance:
int dl18_4(int x) { return x / 4; }
translates with -O1
into:
dl18_4:
lea eax, [rdi+3] ; eax = rdi + 3
test edi, edi ; set sign flag if edi < 0
cmovns eax, edi ; eax = edi if SF = 0
sar eax, 2 ; eax = eax >> 2
ret
Note that shifting by negative number invokes undefined behavior, so it may be safer to declare second parameter as unsigned int
.
Upvotes: 3