我想知道如何使用按位运算符来增加一系列二进制位。但是,我有兴趣这样做以找到二进制值的小数部分值。以下是我正在尝试做的一个例子:
鉴于:1010010,
我想使用每个单独的位,以便将其计算为:
1 *(2 ^ -1)+ 0 *(2 ^ -2)+ 1 *(2 ^ -3)+ 0 *(2 ^ -4)......
虽然我对在ARM程序集中执行此操作感兴趣,但在C / C ++中使用示例仍然有用。
我正在考虑使用计数器执行循环,其中每次循环迭代时,计数器将递增,该值将在逻辑上向左移位,以便获取第一位,并乘以2 ^ -counter。
然而,我并不完全确定如何只是将第一位/ MSB加倍,我很困惑如何将该值乘以基数2到一些负功率。
我知道逻辑移位左移将乘以基数2,但那些通常具有正指数。防爆。 LSL r0,2表示r0中的值将乘以2 ^ 2 = 4。
提前致谢!
答案 0 :(得分:2)
仅使用按位运算(AND
,OR
,XOR
,<<
,>>
)乘以两个数字是完全可能的,尽管可能效率不高。您可能希望阅读Adder (electronics)和Binary multiplier上的相关维基百科文章。
自下而上的乘法方法是首先创建一个二进制加法器。对于最低位(位0),half adder工作正常。 S表示总和,C表示携带。
对于其余位,每位需要full adder。 Cin表示'进入',Cout表示'进行':
最简单的几位逻辑电路称为ripple-carry adder:
纹波进位加法器基本上是一系列全加器,进位传播到全加器计算下一个更重要的位。其他更有效的方法确实存在,但由于简单的原因,我跳过它们。所以现在我们有了一个二进制加法器。
二进制乘数是一个更难的情况。但是,正如我认为这更像是一个概念证明,而不是一个实用的方法来乘以两个数字,让我们更简单地绕道而行。
我们假设我们要计算a
和b
,a = 100
,b = 5
的乘积。 a
和b
都是16位无符号整数(也可以是定点)。我们可以创建一个求和数组,在其中我们写入a
(100)b
(5)次的值,反之亦然。由于16位中可表示的最高无符号值为2 ^ 16-1(65535),因此我们要创建一个由零填充的65535个无符号整数的数组。然后我们需要将数组的5个值设置为100,只使用按位运算。
我们可以这样做:首先我们用值a_array
(100)填充数组(让我们称之为a
)。然后,我们希望根据a_array
的值将b
中的某些值归零,以便b
的{{1}}值保持不变,其余值为a_array
被归零。为此,我们使用二进制掩码和a_array
按位运算。
所以我们遍历AND
的位。对于b
的每个位,我们根据b
中该位的值创建二进制掩码。创建这样的二进制掩码只需要位移(b
,<<
),按位>>
和按位AND
。
0 -> 0b0000 0000 0000 0000 1 -> 0b1111 1111 1111 1111
所以,现在我们有了一个二元掩码。但我们如何使用它?那么,OR
的位0对应于0或1的数值。b
的位1对应于数值0或2. b
的位2对应于数值因此,b
的位n对应于0或2 ^ n的数值。因此,当我们遍历b
的位并为每个位创建二进制掩码时,我们b
2 ^ n值AND
和相应的二进制掩码。 a_array
中的相应值将变为零或保持不变。在C代码中,我使用a_array
循环来for
通过AND
,以及递增和递减计数器。递增和递减不按位操作。但是a_array
循环不是必需的,它仅用于可读性(从人的角度来看)。实际上,我首先在x86-64汇编中写了一个4位* 4位= 4位乘法器来尝试这个概念,只使用for
,and
,or
,{{ 1}}(向左移位),xor
(向右移位)和shl
。 shr
是函数或过程调用,即不按位操作,但您可以内联所有这些函数或过程,从而仅使用call
,{{来计算产品1}},call
,AND
和OR
。因此,对XOR
的每个位而不是<<
循环,您可以>>
n(n = 1,2,4,8 ...)对应的for
值使用基于b
的相应位的按位掩码。对于16位* 16位= 16位乘法,需要65535 AND
个命令(没有循环)。计算机对这样的输入没有问题,但是人们在阅读这些代码时往往会遇到问题。因此,使用a_array
循环。
现在我们b
的{{1}}值为AND
,其余为零。其余的很简单:我们只使用按位加法器添加for
的所有值(它是下面C代码中的函数a_array
)。
这是16位* 16位= 16位无符号整数乘法的代码。请注意,函数b
采用little-endian架构。将a
转换为大端架构应该是微不足道的。该代码也适用于定点乘法,最后只需要添加一个位移。转换为不同的变量大小以及实现溢出检测也应该是微不足道的。任务留给读者。使用GCC进行编译,在x86-64 Linux中进行测试。
a_array
答案 1 :(得分:0)
仅使用 按位运算的乘法是受虐狂,加法也是如此。您尝试替换的硬件非常重要。 (注意:此处的其他答案使用本机添加;这不是按位实现。)
如果整数类型的宽度是操作数的两倍,那么只需执行常规乘法并使用右移将小数点对齐到正确的位。如果在[0,1]范围内乘以两个8位分数,那么结果应该向右移动8位,从2 16 的位置取得小数点到2 8 的地方。
鉴于long long
的流行,这种技术可以轻松处理32位分数的乘法。
const uint64_t fix_factor
= static_cast< uint64_t >( std::numeric_limits< uint32_t >::max() ) + 1;
uint32_t a = (7./13) * fix_factor;
uint32_t b = (13./14) * fix_factor;
uint32_t prod = static_cast< uint64_t >( a ) * b / fix_factor;
(使用x86-64 assembly和sensible output进行现场演示。)
如果你有权访问汇编,那么无论如何都不需要任何技巧,因为大多数CPU提供了乘法高指令。 (这用于实现long long
乘法,但是使用汇编可以确保不计算不必要的低阶结果位。)
答案 2 :(得分:0)
作为练习,也许这可能会有所帮助:
#include <iostream>
using namespace std;
int main()
{
unsigned bits = 0x52; // 01010010
int n = 7; // position of the radix point E.g. 0.1010010
double result = 0;
for( int i=0 ; i<n ; ++i )
{
result += (bits>>i) & 1;
result *= 0.5;
}
cout << result << endl; // 0.640625
return 0;
}
想到这一点的一种方法是采取
1*(2^-1) + 0*(2^-2) + 1*(2^-3) + 0*(2^-4) + ...
并将其重写为
(1+(0+(1+(0+(0+(1+0/2)/2)/2)/2)/2)/2)/2
如您所见,原始二进制数1010010直接显示在此评估中。
答案 3 :(得分:0)
听起来你正走在正确的轨道上......就这样做。
如果我有这些二进制数字,我会乘以
abcd
* 1011
=======
其中a,b,c,d只是位,一个或零,这对你来说不会有什么影响
1011如你所知,是1 *(2 ^ 0)+1(2 ^ 1)+ 0 *(2 ^ 2)+1(2 ^ 3)。
与小学数学完全一样,但更简单,因为我们只关心1和0而不是9到0 ..
abcd
* 1011
=======
abcd (abcd * 1 * 2^0)
0000 (abcd * 1 * 2^1)
abcd (abcd * 0 * 2^2)
+ abcd (abcd * 1 * 2^3)
==========
如果灯泡还没出现那么
abcd
* 1011
=======
abcd (abcd << 0)
00000 (abcd << 1)
abcd00 (0 << 2)
+ abcd000 (abcd << 3)
==========
然后你加上这些值。
unsigned long long mymult ( unsigned int a, unsigned int b )
{
unsigned long long ra;
unsigned int rb;
ra=0;
for(rb=0;rb<32;rb++)
{
if(b&(1<<rb)) ra+=(unsigned long long a)<<rb;
}
}
在汇编中应该很容易实现。使用一个简单的计算器(使用十六进制的计算器)你可以非常快速地看到0xFF * 0xFF = 0xFE01,如果你继续尝试,你应该意识到它可能需要两倍于操作数的宽度来保持结果。如果将两个8位数相乘,要处理所有可能的组合,则需要16位结果。现在有太多的处理器实际上并没有这样做,这使得处理器成倍地增加了一些无用的IMO。所以你可以选择做一个简单的32位= 32位* 32位,或者你可以尝试类似上面的东西并做64位= 32位* 32位(假设编译器解释long和int的长度,因为我认为它们是)。您可能希望从32位= 32位* 32位开始并从那里开始。除此之外,它变得相当棘手,另一个话题。 (也可以在C示例中轻松建模,相当简单但不像下面那样简单。)
unsigned int mymult ( unsigned int a, unsigned int b )
{
unsigned int ra;
unsigned int rb;
ra=0;
for(rb=0;rb<32;rb++)
{
if(b&(1<<rb)) ra+=a<<rb;
}
}
负面力量?好吧,如果2 ^ 4意味着左移4然后2 ^( - 4)意味着右移4是吗?这涵盖了负面力量。
就像浮点一样,您需要随意选择小数点的位置并将其标准化。因此,如果您想要4位分数和28位整数,那么如果您想要乘以5 * 7,则需要将这些数字调整为5 <&lt; 4&7&lt;&lt; 4,将它们标准化为您的小数位。 0b1010000和0b1110000相乘得到结果0b1000110000。 35(0x23)没有分数。假想小数点右边的第一位是2 ^ -1或1/2,下一位是2 ^ -2或1/4,依此类推。
这就是浮点的工作方式,但它基本上是以科学记数法的形式进行的,其中大部分数字的肉位于小数点右侧。就像中学或高中的科学记数学一样,你必须遵循这些规则,在简单的数学运算之前和之后准备你的数字。指数必须匹配才能添加,例如,乘法它们不要你添加指数以及乘以数字部分......
答案 4 :(得分:0)
使用按位运算的乘法是完全可行的,并且可以并行化以提高效率;了解如何添加三个位向量efficiently。
int sum_three(int a, int b, int c)
{
int sum=a^b^c,c2;
c=((c&a)|(a&b)|(c&b))<<1;
while (c) {
c2=(c&sum)<<1;
sum^=c;
c=c2;
}
return sum;
}
既然我们有一个可以将三个向量添加到一个的循环,我们可以递归地拆分标准乘法矩阵,或者实现我在前面的答案中已经提到过的CSA树。
abcdefg
* hijklmn
---------
abcdefg * n <-- bitwise AND, sum these three vectors with the given
abcdefg * m <-- bitwise AND algorithm to produce vector NMLNMLNMLNML
abcdefg * l <-- bitwise AND
...
The first three rows will produce a 9-element vector 0000LMNLMNLMN = a1
The next three rows will produce a vector 0IJKIJKIJK000 = b1
And finally h*abcdefgh needs to be added: HHHHHHH000000 = c1
幸运的是,这些部分总和可以通过对sum_three(a1,b1,c1);
迭代形式的位串行乘法器或CSA乘法器可表示为:
acc_0 = 0;
cry_0 = 0;
loop (max 2n) times:
add_n = vectorY.bit0 AND (vectorX << n);
vectorY >>= 1;
cry_i+1 = (cry_i & add_i) | (add_i & acc_i) | (acc_i & cry_i);
acc_i+1 = acc_i ^ cry_i ^ add_i;
当cry_i + 1 == 0和vectorY == 0时,可以打破循环。