pootyy
pootyy

Reputation: 55

How can I divide a signed integer using only binary operators?

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

Answers (2)

ad absurdum
ad absurdum

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

Grzegorz Szpetkowski
Grzegorz Szpetkowski

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

Related Questions