[英]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.