是否可以使用整数算术实现位运算符?
Posted
技术标签:
【中文标题】是否可以使用整数算术实现位运算符?【英文标题】:Is it possible to implement bitwise operators using integer arithmetic? 【发布时间】:2011-02-28 06:58:46 【问题描述】:我正面临一个相当特殊的问题。我正在为不支持按位运算的架构开发编译器。但是,它处理带符号的 16 位整数算术,我想知道是否可以仅使用以下方式实现按位运算:
加法 (c = a + b) 减法 (c = a - b) 除法(c = a / b) 乘法 (c = a * b) 模数 (c = a % b) 最小值 (c = min(a, b)) 最大值 (c = max(a, b)) 比较(c = (a ) 跳跃(goto、for 等)我希望能够支持的按位运算是:
或 (c = a | b) 和(c = a & b) 异或 (c = a ^ b) 左移 (c = a ) 右移 (c = a >> b) (所有整数都有符号,所以这是个问题) 有符号移位 (c = a >>> b) 补码 (a = ~b) (已经找到解决方案,见下文)通常情况正好相反;如何使用按位 hack 实现算术优化。但是在这种情况下不是。
在这种架构上,可写内存非常稀缺,因此需要按位运算。按位函数本身不应使用大量临时变量。但是,常量只读数据和指令内存是丰富的。附带说明一下,跳转和分支并不昂贵,并且所有数据都可以轻松缓存。跳转花费的周期是算术(包括加载/存储)指令的一半。换句话说,以上所有支持的函数都花费了单次跳转周期的两倍。
一些可能有帮助的想法:
我发现您可以使用以下代码进行一个补码(否定位):
// 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 & Jump 方法:
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;
【问题讨论】:
只是出于好奇,如今没有布尔运算符,CPU 究竟如何构建?这是十进制机器吗? 这是我最近在 Stack Overflow 上看到的最有趣的问题。 如果运算成本的关系是准确的,这确实是一台非常奇怪的机器——整数除法与乘法相同的速度?我的猜测可能是由离散逻辑构建的,也许就像他们在早期太空探测器中使用的 NASA 定制构建计算机? 为了平息你的好奇心,也许也让你失望,这不是美国宇航局太空探测器的东西。 (如果我说是,我必须杀了你)。实际上,这种架构来自一款名为 RoboCom 的游戏。游戏有一个有趣、简单的想法;你为一个机器人编写程序集,然后它试图超越其他机器人。每个机器人的内存非常稀缺(大约 40 字节),我想编写一个高级编译器,该编译器还可以提供稍微昂贵的 bitpacker 来压缩更多信息。可以通过包含 SET 操作数的数据库来模拟常量内存和表。程序员的游戏! 如果有什么好处的话,IBM 1620 不仅没有内置的二进制运算符,它甚至不能添加。加法必须通过查表来完成。另一方面,由于它是一个十进制机器,它可以处理任意精度的十进制数(在商业中很有用)。 【参考方案1】:移位的第一个解决方案(移位是移位距离,不能为负数,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 位时 a
编辑:我实际上测试了所有可能的操作数值(-32768 到 32767)的移位范围从 0 到 31 的正确性,并且它工作正常(假设整数除法)。对于 AND/OR/XOR 代码,在我的机器上进行详尽的测试花费的时间太长,但由于这些代码非常简单,所以无论如何都不应该出现边缘情况。
【讨论】:
【参考方案2】:在这种环境下,最好设置为实际使用算术运算符来剥离整数的组成部分。
例如
if (a & 16) becomes if ((a % 32) > 15)
a &= 16 becomes if ((a % 32) < 15) a += 16
如果您将 RHS 限制为 2 的恒定幂,这些运算符的转换就足够明显了。
剥掉两个或四个位也很容易。
【讨论】:
【参考方案3】:一个老问题的不完整答案,这里主要关注 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;
【讨论】:
你知道这个查找表是怎么计算出来的吗? @Alexandre,有几种可能性。本来我是用一个小辅助程序,现在改答案改用宏了。 Durandal 同胞的回应,他实现了轮班操作符,你知道实现这些操作符的另一种方法吗?【参考方案4】:您可以逐位操作(如 Mark Byers 建议的那样),通过提取每一个会很慢的位。
或者您可以加速处理并使用 2d 查找表来存储结果,例如,对于两个 4 位操作数并对其进行操作。与在位上操作相比,您需要更少的提取。
您还可以使用加法、减法和 >= 操作来完成所有操作。 每个位操作都可以使用宏展开成这样的:
/*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 的剩余值与“掩码”(即“c”参数)进行比较。然后将掩码添加到适合您操作的 if 分支中的结果。如果设置了位,则从值中减去掩码。
根据您的平台,它可能比使用 % 和 / 提取每个位,然后使用乘法将其放回更快。
你自己看看哪个更适合你。
【讨论】:
【参考方案5】:只要你愿意它非常昂贵,就可以。
基本上,您将明确地将数字放入以 2 为底的表示形式中。就像将数字放入以 10 为底的数字一样(例如,打印出来),也就是通过重复除法。
这会将您的数字转换为布尔数组(或 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] ;
【讨论】:
【参考方案6】:只是其他一些方法
例如 16 位 AND:
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;
double 解决方案 2-bit AND 没有循环或表查找:
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);
【讨论】:
【参考方案7】:这是我想出的一种方法,使用双 64 整数加法并行处理按位 XOR 16 位:
[gmn]awk ' CONVFMT = OFMT = "%.20g"
c = (a=3e15+("1011000111110101"))+
(b=3e15+("1101010010101110"))
sub(/[7]/, "1",c)
gsub(/[268]/ ,"0",c)
sub(/^[^01]+/,"",c); print c '
位串看起来像这样(为了清楚起见,我在这里去掉了3e15
保护数字):
a = 1011 0001 1111 0101
b = 1101 0100 1010 1110
c = 8112 0101 2121 1211 (intermediate)
-------------------------------------------
c = 0110 0101 0101 1011 (output)
一个 52 位无符号整数加法,几乎没有几个字符串替换调用,输出已经处于可以向下传递的状态。
此添加将攀升至的绝对最高值是 8222,2222,222,222,略低于 53 位硬限制。
对于按位与,将前导 6 或 7 的所有 1 转换为 0:只有 2 和前导 8 是真正的位,然后应将其转换为 1。
对于按位 OR,情况正好相反 - 任何不是 0 或 6 的东西在输出字符串中都是“1”。
对于逐位补码,更简单——从 1,111,111,111,111,111 开始,减去 2 个字节的串联位串即可得到。
【讨论】:
以上是关于是否可以使用整数算术实现位运算符?的主要内容,如果未能解决你的问题,请参考以下文章