简体   繁体   中英

In C bits, multiply by 3 and divide by 16

A buddy of mine had these puzzles and this is one that is eluding me. Here is the problem, you are given a number and you want to return that number times 3 and divided by 16 rounding towards 0. Should be easy. The catch? You can only use the ! ~ & ^ | + << >> operators and of them only a combination of 12.

int mult(int x){
    //some code here...
return y;
}

My attempt at it has been:

    int hold = x + x + x;
    int hold1 = 8;
    hold1 = hold1 & hold;
    hold1 = hold1 >> 3;
    hold = hold >> 4;
    hold = hold + hold1;
    return hold;

But that doesn't seem to be working. I think I have a problem of losing bits but I can't seem to come up with a way of saving them. Another perspective would be nice. Just to add, you also can only use variables of type int and no loops, if statements or function calls may be used.

Right now I have the number 0xfffffff. It is supposed to return 0x2ffffff but it is returning 0x3000000.

For this question you need to worry about the lost bits before your division (obviously). Essentially, if it is negative then you want to add 15 after you multiply by 3. A simple if statement (using your operators) should suffice.

I am not going to give you the code but a step by step would look like,

x = x*3

get the sign and store it in variable foo.

have another variable hold x + 15;

Set up an if statement so that if x is negative it uses that added 15 and if not then it uses the regular number (times 3 which we did above).

Then divide by 16 which you already showed you know how to do. Good luck!

This seems to work (as long as no overflow occurs):

((num<<2)+~num+1)>>4

Try this JavaScript code, run in console:

for (var num = -128; num <= 128; ++num) {
  var a = Math.floor(num * 3 / 16);
  var b = ((num<<2)+~num+1)>>4;
  console.log(
    "Input:", num,
    "Regular math:", a,
    "Bit math:", b,
    "Equal: ", a===b
  );
}

what you can do is first divide by 4 then add 3 times then again devide by 4.

3*x/16=(x/4+x/4+x/4)/4

with this logic the program can be

main()
{
   int x=0xefffffff;
   int y;
   printf("%x",x);
   y=x&(0x80000000);
   y=y>>31;
   x=(y&(~x+1))+(~y&(x));
   x=x>>2;
   x=x&(0x3fffffff);
   x=x+x+x;
   x=x>>2;
   x=x&(0x3fffffff);
    x=(y&(~x+1))+(~y&(x));
   printf("\n%x %d",x,x);
}

AND with 0x3fffffff to make msb's zero. it'l even convert numbers to positive. This uses 2's complement of negative numbers. with direct methods to divide there will be loss of bit accuracy for negative numbers. so use this work arround of converting -ve to +ve number then perform division operations.

The Maths

When you divide a positive integer n by 16, you get a positive integer quotient k and a remainder c < 16 :

    (n/16) = k + (c/16).

(Or simply apply the Euclidan algorithm.) The question asks for multiplication by 3/16 , so multiply by 3

    (n/16) * 3 = 3k + (c/16) * 3.

The number k is an integer, so the part 3k is still a whole number. However, int arithmetic rounds down, so the second term may lose precision if you divide first, And since c < 16 , you can safely multiply first without overflowing (assuming sizeof(int) >= 7 ). So the algorithm design can be

    (3n/16) = 3k + (3c/16).

The design

  • The integer k is simply n/16 rounded down towards 0. So k can be found by applying a single AND operation. Two further operations will give 3k . Operation count: 3.
  • The remainder c can also be found using an AND operation (with the missing bits). Multiplication by 3 uses two more operations. And shifts finishes the division. Operation count: 4.
  • Add them together gives you the final answer.

Total operation count: 8.

Negatives

The above algorithm uses shift operations. It may not work well on negatives. However, assuming two's complement, the sign of n is stored in a sign bit. It can be removed beforing applying the algorithm and reapplied on the answer.

  • To find and store the sign of n , a single AND is sufficient.
  • To remove this sign, OR can be used.
  • Apply the above algorithm.
  • To restore the sign bit, Use a final OR operation on the algorithm output with the stored sign bit.

This brings the final operation count up to 11.

Note that the C99 standard states in section section 6.5.7 that right shifts of signed negative integer invokes implementation-defined behavior. Under the provisions that int is comprised of 32 bits and that right shifting of signed integers maps to an arithmetic shift instruction, the following code works for all int inputs. A fully portable solution that also fulfills the requirements set out in the question may be possible, but I cannot think of one right now.

My basic idea is to split the number into high and low bits to prevent intermediate overflow. The high bits are divided by 16 first (this is an exact operation), then multiplied by three. The low bits are first multiplied by three, then divided by 16. Since arithmetic right shift rounds towards negative infinity instead of towards zero like integer division, a correction needs to be applied to the right shift for negative numbers. For a right shift by N, one needs to add 2 N -1 prior to the shift if the number to be shifted is negative.

#include <stdio.h>
#include <stdlib.h>

int ref (int a)
{
  long long int t = ((long long int)a * 3) / 16;
  return (int)t;
}

int main (void)
{
  int a, t, r, c, res;
  a = 0;
  do {
    t = a >> 4;         /* high order bits */
    r = a & 0xf;        /* low order bits */
    c = (a >> 31) & 15; /* shift correction. Portable alternative: (a < 0) ? 15 : 0 */
    res = t + t + t + ((r + r + r + c) >> 4);
    if (res != ref(a)) {
      printf ("!!!! error a=%08x  res=%08x  ref=%08x\n", a, res, ref(a));
      return EXIT_FAILURE;
    }
    a++;
  } while (a);
  return EXIT_SUCCESS;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM