繁体   English   中英

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

[英]In C bits, multiply by 3 and divide by 16

我的一个朋友有这些谜题,而这是一个让我望而却步的谜题。 问题来了,你得到一个数字,你想返回这个数字乘以 3 并除以 16 向 0 舍入。应该很容易。 渔获? 您只能使用 ! ~ & ^ | + << >> 运算符,其中只有 12 个的组合。

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

我的尝试是:

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

但这似乎不起作用。 我想我有丢失比特的问题,但我似乎无法想出一种方法来保存它们。 另一个角度会很好。 补充一点,您也只能使用 int 类型的变量,而不能使用循环,如果可以使用语句或函数调用。

现在我的号码是 0xffffffff。 它应该返回 0x2ffffff 但它返回 0x3000000。

对于这个问题,您需要担心划分之前丢失的位(显然)。 本质上,如果它是负数,那么你想在乘以 3 后加上 15。一个简单的if语句(使用你的运算符)就足够了。

我不会给你代码,但一步一步看起来像,

x = x*3

获取符号并将其存储在变量 foo 中。

有另一个变量持有 x + 15;

设置一个 if 语句,如果 x 为负,则使用相加的 15,如果不是,则使用常规数字(我们上面所做的乘以 3)。

然后除以 16,你已经展示了你知道怎么做。 祝你好运!

这似乎有效(只要不发生溢出):

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

试试这个 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
  );
}

你可以做的是先除以 4,然后加 3 次,然后再除以 4。

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

有了这个逻辑,程序可以

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

并使用 0x3fffffff 使 msb 为零。 它甚至会将数字转换为正数。 这使用 2 的负数补码。 使用直接除法方法会损失负数的位精度。 所以使用这项工作将 -ve 转换为 +ve 数字,然后执行除法运算。

数学

当你将一个正整数n除以 16 时,你会得到一个正整数商k和一个余数c < 16

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

(或者简单地应用欧几里得算法。)这个问题要求乘以3/16 ,所以乘以 3

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

数字k是一个整数,所以3k部分仍然是一个整数。 但是, int算术向下舍入,因此如果您先除法,第二项可能会失去精度,并且由于c < 16 ,您可以安全地先乘以而不会溢出(假设sizeof(int) >= 7 )。 所以算法设计可以是

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

该设计

  • 整数k只是n/16向下舍入到 0。因此可以通过应用单个AND运算找到k 两个进一步的操作将给出3k 操作数:3。
  • 余数c也可以使用AND运算(带有丢失的位)找到。 乘以 3 使用另外两个操作。 并轮班完成划分。 操作数:4。
  • 把它们加在一起就是最终的答案。

总操作数:8。

负数

上述算法使用移位操作。 它可能不适用于底片。 然而,假设二进制补码, n的符号存储在符号位中。 可以在应用算法之前将其删除并重新应用于答案。

  • 要查找和存储n的符号,单个AND就足够了。
  • 要删除此标志,可以使用OR
  • 应用上述算法。
  • 要恢复符号位,请对算法输出与存储的符号位使用最终OR运算。

这使最终操作计数达到 11。

请注意,C99 标准在第 6.5.7 节中指出,有符号负整数的右移会调用实现定义的行为。 int由 32 位组成并且有符号整数的右移映射到算术移位指令的规定下,以下代码适用于所有int输入。 一个完全便携的解决方案也可以满足问题中提出的要求,但我现在想不出一个。

我的基本想法是将数字分成高位和低位以防止中间溢出。 高位先除以 16(这是一个精确运算),然后再乘以 3。 低位首先乘以 3,然后除以 16。由于算术右移向负无穷大舍入而不是像整数除法那样向零舍入,因此需要对负数的右移进行校正。 对于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