大整数加法代码

时间:2015-01-15 19:45:26

标签: c math cuda biginteger

我正在尝试使用以下代码在CUDA中实现大整数添加

__global__ void add(unsigned *A, unsigned *B, unsigned *C/*output*/, int radix){

    int id = blockIdx.x * blockDim.x + threadIdx.x; 
    A[id ] = A[id] + B[id]; 

    C[id ] =  A[id]/radix;
    __syncthreads();
    A[id] =  A[id]%radix + ((id>0)?C[id -1]:0);
    __syncthreads();
    C[id] = A[id];
}

但它不能正常工作,我现在也不知道如何处理额外的进位。感谢

1 个答案:

答案 0 :(得分:1)

TL; DR 构建一个进位超前加法器,其中每个加法器都添加模数基数,而不是模数2

增加需要进入

你模型中的问题是你有一个涟漪。请参阅Rippling carry adders

如果您在FPGA中不会出现问题,因为他们有专门的逻辑来快速完成(carry chains, they're cool)。但是,唉,你在GPU上了!

也就是说,对于给定的id,当只有较小{{1}的所有总和时,您只知道输入进位(因此您是要求A[id]+B[id]还是A[id]+B[id]+1})已经计算了值。事实上,最初,你只知道第一次携带。

id

表征进位输出

每个总和也有一个进位输出,它不在图纸上。因此,您必须将此较大方案中的添加视为具有3个输入和2个输出的函数: A[3]+B[3] + ? A[2]+B[2] + ? A[1]+B[1] + ? A[0]+B[0] + 0 | | | | v v v v C[3] C[2] C[1] C[0]

为了不等待O(n)完成总和(其中n是您的总和被切入的项目数),您可以预先计算每个(C, c_out) = add(A, B, c_in)的所有可能结果。由于idA不会改变,所以这并不是很大的工作量。因此,您有两种可能的输出:B(c_out0, C) = add(A, B, 0)

现在有了所有这些结果,我们需要基本实现carry lookahead unit

为此,我们需要弄清楚每个和的进位输出(c_out1, C') = add(A, B, 1)P的函数:

  • G a.k.a.以下所有定义
    • 传播
    • "如果有一个随身携带,那么随身携带就会超出这个数额"
    • P
    • c_out1 && !c_out0
  • A + B == radix-1 a.k.a.以下所有定义
    • 生成
    • "无论随身携带什么,随身携带都将超出此笔费用
    • G
    • c_out1 && c_out0
    • c_out0

所以换句话说,A + B >= radix。所以现在我们有一个算法的开始,它可以很容易地告诉我们每个id作为其进位输入函数的进位输出:

  1. 每个c_out = G or (P and c_in),计算id
  2. 获取C[id] = A[id]+B[id]+0
  3. 获取G[id] = C[id] > radix -1
  4. 对数树

    现在我们可以在O(log(n))中完成,即使在GPU上看起来很糟糕,但仍然比等待更短。实际上,我们可以从彼此相邻的两个新增内容中获得一个组P[id] = C[id] == radix-1和一个组G

    Pid

    1. id+1
    2. step = 2
    3. if id % step == 0, do steps 6 through 10, otherwise, do nothing
    4. group_P = P[id] and P[id+step/2]
    5. group_G = (P[id+step/2] and G[id]) or G[id+step/2]
    6. c_in[id+step/2] = G[id] or (P[id] and c_in[id])
    7. step = step * 2
    8. 最后(在每次使用if step < n, go to 5次更少的树的每个级别重复步骤5-10之后),所有内容都将以idP的形式表示您计算的s和G c_in[0]。在维基百科页面上有分组的公式,而不是2,这将得到O(log_4(n))而不是O(log_2(n))的答案。

      因此算法结束:

      1. 每次0,获取id
      2. return c_in[id]
      3. 利用硬件

        我们在最后一部分中真正做到的是模仿带有逻辑的进位超前加法器的电路。但是,我们已经在硬件中添加了类似的东西(根据定义)。

        让我们根据基于(C[id]+c_in[id]) % radix的基数替换我们对PG的定义,就像我们硬件中的逻辑一样,模仿2位的总和2每个阶段ab(xor)和P = a ^ b(逻辑和)。换句话说,G = a & ba = P or G。因此,如果我们创建一个b = G整数和一个intP整数,其中每个位分别是intGP,我们从每个G总和计算出来的(限制)我们为64和),然后加法id具有与我们精心设计的逻辑方案完全相同的进位传播。

        我认为减少形成这些整数仍然是一个对数运算,但这是可以预期的。

        有趣的是,总和的每个位都是其进位输入的函数。实际上,总和的每一位最终都是3位(intP | intG) + intG的函数。

        • 如果在该位a+b+c_in % 2,则P == 1a + b == 1
        • 否则a+b+c_in % 2 == !c_ina+b02

        因此,我们可以简单地形成a+b+c_in % 2 == c_in int_cin = ((P|G)+G) ^ P^的整数(或位阵)xor

        因此,我们的算法有一个替代结尾,取代了步骤4及其后的步骤:

          每个id
        1. PG idP = P << idG = G << id
        2. 执行OR减少以获得intGintP OR PG id 0 ..63
        3. 计算(一次)int_cin = ((P|G)+G) ^ P
        4. 在每个id,得到`c_in = int_cin&amp; (1&lt; id)? 1:0;
        5. return (C[id]+c_in) % radix

        6. PS:另外,如果radix很大,请注意数组中的整数溢出。如果它不是那么整个事情真的没有意义我猜...

          PPS:在备用结尾中,如果您有超过64个项目,请按PG来表征,就好像radix2^64一样,并且在更高级别执行相同的步骤(减少,获得c_in),然后返回到较低级别,7

          应用P+G+carry in from higher level