我面临一个相当特殊的问题。我正在研究一种不支持按位运算的架构编译器。但是,它处理带符号的16位整数算术,我想知道是否可以仅使用以下方式实现按位运算:
我希望能够支持的按位操作是:
通常问题是相反的;如何使用按位黑客实现算术优化。但不是在这种情况下。
此架构上的可写内存非常稀缺,因此需要按位操作。按位函数本身不应使用大量临时变量。但是,常量只读数据&指令记忆很丰富。这里的一个注意事项是跳转和分支并不昂贵,所有数据都很容易被缓存。算术(包括加载/存储)指令的跳转成本是周期的一半。换句话说,所有上述支持的函数都会花费两倍于单次跳转的周期。
我发现您可以使用以下代码执行一个补码(否定位):
// Bitwise one's complement
b = ~a;
// Arithmetic one's complement
b = -1 - a;
我还记得用2的幂除法时的旧移位黑客,所以按位移位可以表示为:
// Bitwise left shift
b = a << 4;
// Arithmetic left shift
b = a * 16; // 2^4 = 16
// Signed right shift
b = a >>> 4;
// Arithmetic right shift
b = a / 16;
对于其余的按位操作,我有点无能为力。我希望这个架构的架构师能够提供位操作。
我还想知道是否有一种快速/简单的方法来计算2的功率(用于移位操作)而不使用存储器数据表。一个天真的解决方案是跳进乘法领域:
b = 1;
switch (a)
{
case 15: b = b * 2;
case 14: b = b * 2;
// ... exploting fallthrough (instruction memory is magnitudes larger)
case 2: b = b * 2;
case 1: b = b * 2;
}
或Set&amp;跳跃方式:
switch (a)
{
case 15: b = 32768; break;
case 14: b = 16384; break;
// ... exploiting the fact that a jump is faster than one additional mul
// at the cost of doubling the instruction memory footprint.
case 2: b = 4; break;
case 1: b = 2; break;
}
答案 0 :(得分:24)
用于移位的第一种解决方案(移位是移位距离,不得为负,a是要移位的操作数,并且在完成时也包含结果)。所有三个班次操作都使用电源表。
// table used for shift operations
powtab = { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, -32768 };
// logical shift left
if (shift > 15) {
a = 0; // if shifting more than 15 bits to the left, value is always zero
} else {
a *= powtab[shift];
}
// logical shift right (unsigned)
if (shift > 15) {
a = 0; // more than 15, becomes zero
} else if (shift > 0) {
if (a < 0) {
// deal with the sign bit (15)
a += -32768;
a /= powtab[shift];
a += powtab[15 - shift];
} else {
a /= powtab[shift];
}
}
// arithmetic shift right (signed)
if (shift >= 15) {
if (a < 0) {
a = -1;
} else {
a = 0;
}
} else if (shift > 0) {
if (a < 0) {
// deal with the sign bit
a += -32768;
a /= powtab[shift];
a -= powtab[15 - shift];
} else {
// same as unsigned shift
a /= powtab[shift];
}
}
对于AND,OR和XOR,我无法想出一个简单的解决方案,所以我会在每个位上循环。这可能是一个更好的技巧。伪代码假设a和b是输入操作数,c是结果值,x是循环计数器(每个循环必须正好运行16次):
// XOR (^)
c = 0;
for (x = 0; x <= 15; ++x) {
c += c;
if (a < 0) {
if (b >= 0) {
c += 1;
}
} else if (b < 0) {
c += 1;
}
a += a;
b += b;
}
// AND (&)
c = 0;
for (x = 0; x <= 15; ++x) {
c += c;
if (a < 0) {
if (b < 0) {
c += 1;
}
}
a += a;
b += b;
}
// OR (|)
c = 0;
for (x = 0; x <= 15; ++x) {
c += c;
if (a < 0) {
c += 1;
} else if (b < 0) {
c += 1;
}
a += a;
b += b;
}
这假设所有变量都是16位且所有操作都表现为有符号(因此当设置第15位时,&lt; 0实际上为真)。
编辑:我实际测试了所有可能的操作数值(-32768到32767),范围从0到31的正确性,它正常工作(假设整数除)。对于AND / OR / XOR代码,在我的机器上进行详尽的测试需要太长时间,但由于这些代码非常简单,所以无论如何都应该没有边缘情况。
答案 1 :(得分:4)
在这种环境中,最好是设置实际使用算术运算符来剥离整数的组件。
E.G。
if (a & 16) becomes if ((a % 32) > 15)
a &= 16 becomes if ((a % 32) < 15) a += 16
如果将RHS限制为2的恒定幂,则这些运算符的变换非常明显。
剥离两个或四个位也很容易。
答案 2 :(得分:4)
关于旧问题的不完整答案,这里集中于AND,OR,XOR。一旦找到其中一个按位操作的解决方案,就可以导出另外两个。有几种方法,一种在以下测试程序中显示(在gcc版本4.6.3(Ubuntu / Linaro 4.6.3-1ubuntu5)上编译)。
2018年12月,我在解决方案中发现了一个错误。以下评论的XOR仅起作用,因为a+b-2*AND(a,b)
中的中间结果被提升为int
,对于所有现代编译器,其大于16位。
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
//#define XOR(a,b) (a + b - 2*AND(a,b)) // Error. Intermediate overflow
#define XOR(a,b) (a - AND(a,b) + b - AND(a,b) )
#define IOR(a,b) XOR(XOR(a,b),AND(a,b)) // Credit to Jan Gray, Gray Research LLC, for IOR
static const uint16_t andlookup[256] = {
#define C4(a,b) ((a)&(b)), ((a)&(b+1)), ((a)&(b+2)), ((a)&(b+3))
#define L(a) C4(a,0), C4(a,4), C4(a,8), C4(a,12)
#define L4(a) L(a), L(a+1), L(a+2), L(a+3)
L4(0), L4(4), L4(8), L4(12)
#undef C4
#undef L
#undef L4
};
uint16_t AND(uint16_t a, uint16_t b) {
uint16_t r=0, i;
for ( i = 0; i < 16; i += 4 ) {
r = r/16 + andlookup[(a%16)*16+(b%16)]*4096;
a /= 16;
b /= 16;
}
return r;
}
int main( void ) {
uint16_t a = 0, b = 0;
do {
do {
if ( AND(a,b) != (a&b) ) return printf( "AND error\n" );
if ( IOR(a,b) != (a|b) ) return printf( "IOR error\n" );
if ( XOR(a,b) != (a^b) ) return printf( "XOR error\n" );
} while ( ++b != 0 );
if ( (a & 0xff) == 0 )
fprintf( stderr, "." );
} while ( ++a != 0 );
return 0;
}
答案 3 :(得分:2)
你可以逐位操作(正如Mark Byers建议的那样),通过提取每一个很慢的位来进行操作。
或者你可以加速进程并使用2d查找表来存储结果,例如,对于两个4位操作数并对它们进行操作。与使用位操作相比,您需要的提取量更少。
你也可以使用加法,减法和&gt; =操作来完成所有事情。 可以使用宏将每个按位操作展开为类似的东西:
/*I didn't actually compile/test it, it is just illustration for the idea*/
uint16 and(uint16 a, uint16 b){
uint16 result = 0;
#define AND_MACRO(c) \
if (a >= c){ \
if (b >= c){\
result += c;\
b -= c;\
}\
a -= c;\
}\
else if (b >= c)\
b -= c;
AND_MACRO(0x8000)
AND_MACRO(0x4000)
AND_MACRO(0x2000)
AND_MACRO(0x1000)
AND_MACRO(0x0800)
AND_MACRO(0x0400)
AND_MACRO(0x0200)
AND_MACRO(0x0100)
AND_MACRO(0x0080)
AND_MACRO(0x0040)
AND_MACRO(0x0020)
AND_MACRO(0x0010)
AND_MACRO(0x0008)
AND_MACRO(0x0004)
AND_MACRO(0x0002)
AND_MACRO(0x0001)
#undef AND_MACRO
return result;
}
你需要3个变量来实现它。
每个按位操作都将围绕类似于AND_MACRO的宏 - 您将a和b的剩余值与“mask”(即“c”参数)进行比较。然后将掩码添加到适合您的操作的if分支的结果中。如果设置了位,则从值中减去掩码。
根据您的平台,它可能比使用%和/提取每个位更快,然后使用乘法将其放回。
亲自看看哪个更适合你。
答案 4 :(得分:2)
只要你愿意它非常昂贵,是的。
基本上,您将明确地将数字放入base-2表示中。你这样做就像你将一个数字放入基数10(例如,将其打印出来),即重复分割一样。
这会将您的数字转换为bool数组(或0,1范围内的整数),然后我们添加函数来操作这些数组。
再次,并不是说这比按位操作要昂贵得多,并且几乎所有架构都会提供按位运算符。在C中(当然,在C中你有按位运算符,但是......)一个实现可能是:
include <limits.h>
const int BITWIDTH = CHAR_BIT;
typedef int[BITWIDTH] bitpattern;
// fill bitpattern with base-2 representation of n
// we used an lsb-first (little-endian) representation
void base2(char n, bitpattern array) {
for( int i = 0 ; i < BITWIDTH ; ++i ) {
array[i] = n % 2 ;
n /= 2 ;
}
}
void bitand( bitpattern op1, bitpattern op2, bitpattern result ) {
for( int i = 0 ; i < BITWIDTH ; ++i ) {
result[i] = op1[i] * op2[i];
}
}
void bitor( bitpattern op1, bitpattern op2, bitpattern result ) {
for( int i = 0 ; i < BITWIDTH ; ++i ) {
result[i] = (op1[i] + op2[i] != 0 );
}
}
// assumes compiler-supplied bool to int conversion
void bitxor( bitpattern op1, bitpattern op2, bitpattern result ) {
for( int i = 0 ; i < BITWIDTH ; ++i ) {
result[i] = op1[i] != op2[i] ;
}
}
答案 5 :(得分:1)
例如 16位和:
int and(int a, int b) {
int d=0x8000;
int result=0;
while (d>0) {
if (a>=d && b>=d) result+=d;
if (a>=d) a-=d;
if (b>=d) b-=d;
d/=2;
}
return result;
}
双解决方案 2位和没有循环或表格查找:
int and(int a, int b) {
double x=a*b/12;
return (int) (4*(sign(ceil(tan(50*x)))/6+x));
}
32位整数解决方案 2位和:
int and(int a, int b) {
return ((684720128*a*a -b) * a) % (b+1);
}
16位整数解决方案 2位和:
int and(int a, int b) {
return ((121 * a) % 16) % (b+1);
}
16位整数解决方案 3位:
int and(int a, int b) {
return sign(a) * ((((-23-a) * (40+b)) % 2)+40+b) % ((10624 * ((((-23-a) * (40+b))%2)+40+b)) % (a%2 - 2 -a) - a%2 + 2 +a);
}