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));
10
  • 2
    FYI, the division operator / is binary too. "Binary" means it has two operands as opposed to "unary" and "ternary". Commented Jan 31, 2017 at 22:55
  • Just compensate for the round-down that >> has Commented Jan 31, 2017 at 22:56
  • I specified which binary operators i am allowed to use, and / is not one of them. Also, i dont think its so straight-forward as to round, because then if n = 0 the output will be wrong. Commented Jan 31, 2017 at 23:00
  • "does not work with odd negative integers." -->> Hmmm, first thing I'd try is to negate the number (with ~ and others), shift, and negate again - which certainly should work except for maybe corner cases. Then I would post looking for simplifications. Commented Jan 31, 2017 at 23:08
  • @chux Negating it will make it much more dependent on the implementation. Commented Jan 31, 2017 at 23:10

2 Answers 2

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.

Sign up to request clarification or add additional context in comments.

3 Comments

ok, thanks! I cant use loops, so ill try and figure out how to do this without them.
oh ok this makes sense! Thank you so much!
@Bailey: I made a typo with operator precedence, it's fixed. Also, I noticed that - operator is apparently not allowed, so - 1 is now replaced with + ~0.
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

7 Comments

Code uses - in (1UL << n_bits) - (unsigned)(x). I do not see - in "can only use ! ~ & ^ | + << >>"
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
@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?
~(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
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.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.