简体   繁体   English

在 C 位中,乘以 3 并除以 16

[英]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.问题来了,你得到一个数字,你想返回这个数字乘以 3 并除以 16 向 0 舍入。应该很容易。 The catch?渔获? You can only use the !您只能使用 ! ~ & ^ | ~ & ^ | + << >> operators and of them only a combination of 12. + << >> 运算符,其中只有 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.补充一点,您也只能使用 int 类型的变量,而不能使用循环,如果可以使用语句或函数调用。

Right now I have the number 0xfffffff.现在我的号码是 0xffffffff。 It is supposed to return 0x2ffffff but it is returning 0x3000000.它应该返回 0x2ffffff 但它返回 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.本质上,如果它是负数,那么你想在乘以 3 后加上 15。一个简单的if语句(使用你的运算符)就足够了。

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.获取符号并将其存储在变量 foo 中。

have another variable hold x + 15;有另一个变量持有 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).设置一个 if 语句,如果 x 为负,则使用相加的 15,如果不是,则使用常规数字(我们上面所做的乘以 3)。

Then divide by 16 which you already showed you know how to do.然后除以 16,你已经展示了你知道怎么做。 Good luck!祝你好运!

This seems to work (as long as no overflow occurs):这似乎有效(只要不发生溢出):

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

Try this JavaScript code, run in console:试试这个 JavaScript 代码,在控制台中运行:

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.你可以做的是先除以 4,然后加 3 次,然后再除以 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.并使用 0x3fffffff 使 msb 为零。 it'l even convert numbers to positive.它甚至会将数字转换为正数。 This uses 2's complement of negative numbers.这使用 2 的负数补码。 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.所以使用这项工作将 -ve 转换为 +ve 数字,然后执行除法运算。

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

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

(Or simply apply the Euclidan algorithm.) The question asks for multiplication by 3/16 , so multiply by 3 (或者简单地应用欧几里得算法。)这个问题要求乘以3/16 ,所以乘以 3

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

The number k is an integer, so the part 3k is still a whole number.数字k是一个整数,所以3k部分仍然是一个整数。 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 ).但是, int算术向下舍入,因此如果您先除法,第二项可能会失去精度,并且由于c < 16 ,您可以安全地先乘以而不会溢出(假设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.整数k只是n/16向下舍入到 0。因此可以通过应用单个AND运算找到k Two further operations will give 3k .两个进一步的操作将给出3k Operation count: 3.操作数:3。
  • The remainder c can also be found using an AND operation (with the missing bits).余数c也可以使用AND运算(带有丢失的位)找到。 Multiplication by 3 uses two more operations.乘以 3 使用另外两个操作。 And shifts finishes the division.并轮班完成划分。 Operation count: 4.操作数:4。
  • Add them together gives you the final answer.把它们加在一起就是最终的答案。

Total operation count: 8.总操作数: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.然而,假设二进制补码, n的符号存储在符号位中。 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.要查找和存储n的符号,单个AND就足够了。
  • To remove this sign, OR can be used.要删除此标志,可以使用OR
  • Apply the above algorithm.应用上述算法。
  • To restore the sign bit, Use a final OR operation on the algorithm output with the stored sign bit.要恢复符号位,请对算法输出与存储的符号位使用最终OR运算。

This brings the final operation count up to 11.这使最终操作计数达到 11。

Note that the C99 standard states in section section 6.5.7 that right shifts of signed negative integer invokes implementation-defined behavior.请注意,C99 标准在第 6.5.7 节中指出,有符号负整数的右移会调用实现定义的行为。 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.int由 32 位组成并且有符号整数的右移映射到算术移位指令的规定下,以下代码适用于所有int输入。 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.高位先除以 16(这是一个精确运算),然后再乘以 3。 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.低位首先乘以 3,然后除以 16。由于算术右移向负无穷大舍入而不是像整数除法那样向零舍入,因此需要对负数的右移进行校正。 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.对于N的右移,如果要移位的数为负数,则需要在移位前加上 2 N -1。

#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;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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