1

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));
pootyy
  • 55
  • 6

2 Answers2

3

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.

Grzegorz Szpetkowski
  • 36,988
  • 6
  • 90
  • 137
1

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
Community
  • 1
  • 1
ad absurdum
  • 19,498
  • 5
  • 37
  • 60
  • Code uses `-` in `(1UL << n_bits) - (unsigned)(x)`. I do not see `-` in "can only use ! ~ & ^ | + << >>" – chux - Reinstate Monica Feb 01 '17 at 02:18
  • Curious, why `L` in `1UL`. `unsigned long` may not be any wider than `unsigned`. Perhaps `(1UL << n_bits) - (unsigned)(x)` --> `~(unsigned)(x) + 1;`? As in my [comment](http://stackoverflow.com/questions/41968998/how-can-i-divide-a-signed-integer-using-only-binary-operators#comment71117864_41968998) – chux - Reinstate Monica Feb 01 '17 at 02:23
  • @chux-- I have updated my answer. It seems to me that the cast to `unsigned` is not necessary at all. In other words, `~(unsigned)(x) + 1` --> `~x + 1`. Am I missing something here? – ad absurdum Feb 01 '17 at 03:00
  • `~(unsigned)(some_int) + 1` in itself will not cause UB for all values of `some_int`. `~some_int` is UB on select machines, maybe in a computer graveyard. `~` on non-2's complement does not lend itself to arithmetic negation. IAC, it is all a learning exercise. "If the implementation does not support negative zeros, the behavior of the &, |, ^, ~, <<, and >> operators with operands that would produce such a value is undefined. " C11 §6.2.6.2 4 – chux - Reinstate Monica Feb 01 '17 at 03:04
  • @chux-- Appreciate the informative comments. Final question: I changed my code to cast `x` before the first transformation to a positive magnitude, but if negative zero representations are the only issue, could we not instead test for zero before applying `~`, and proceed accordingly? – ad absurdum Feb 01 '17 at 03:16
  • 1
    `INT_MIN` is an issue. Consider `x = ~(unsigned)(INT_MIN) + 1;`. Suggest using well behaved `unsigned` for the `>>` shift rather than `int x` with its ID behavior. – chux - Reinstate Monica Feb 01 '17 at 03:20
  • @chux-- you are suggesting `x >>= n` --> `x = (unsigned)x >> n`? When I changed this the code worked for `x = INT_MIN`; without the cast the sign was lost on my machine. Will have to think about this some more. Good tip. – ad absurdum Feb 01 '17 at 03:44