【问题标题】:efficient way to divide a very large number stored in 2 registers by a constant将存储在 2 个寄存器中的非常大的数除以常数的有效方法
【发布时间】:2013-10-21 19:25:27
【问题描述】:

假设我要计算以下内容:

A/Z

A 的长度为 128 位,Z 的长度为 64 位。 A 存储在 2 个 64 位寄存器中,因为系统的寄存器最多可以存储 64 位。计算结果的有效方法是什么?

P.S:我已经通过使用 CSD 表示解决了类似的乘法问题。但是,这需要先计算1/Z

【问题讨论】:

  • 允许使用什么样的操作?
  • 我愿意接受所有建议。但我更喜欢一种坚持加法、减法的解决方案。
  • 像恢复师一样?
  • @AnttiHuima 谢谢。这很有帮助。目前正在检查黑客的喜悦多词划分。

标签: algorithm bit-manipulation division


【解决方案1】:

[Edit1] 发现错误已修复

我假设你想要整数除法,所以这里是 8 位类比的数学:

A = { a0 + (a1<<8) }
D = { d0 + (d1<<8) } ... division result
Z = { z0 }
D = (a0/z0) + ((a1*256)/z0) +                      (( (a0%z0) + ((a1*256)%z0) )/z0);
D = (a0/z0) + ((a1/z0)*256) + ((a1%z0)*(256/z0)) + (( (a0%z0) + ((a1%z0)*(256%z0)) )/z0);

现在256/z0256%z0 可以这样计算(C++):

    i0=0xFF/z0; if ((z0&(z0-1))==0) i0++;   // i0 = 256/z0
    i1=i0*z0; i1^=0xFF; i1++;               // i1 = 256%z0

所以 i0 只是在 z0 是 2 的幂的情况下增加,而 i1 只是从除法中计算出的余数。

    a/b = d + r/b
    r = a - a*d

这里测试8位代码

//---------------------------------------------------------------------------
// unsigned 8 bit ALU in C++
//---------------------------------------------------------------------------
BYTE cy;                                                    // carry flag cy = { 0,1 }
void inc(BYTE &a);                                          // a++
void dec(BYTE &a);                                          // a--
void add(BYTE &c,BYTE a,BYTE b);                            // c = a+b
void adc(BYTE &c,BYTE a,BYTE b);                            // c = a+b+cy
void sub(BYTE &c,BYTE a,BYTE b);                            // c = a-b
void sbc(BYTE &c,BYTE a,BYTE b);                            // c = a-b-cy
void mul(BYTE &h,BYTE &l,BYTE a,BYTE b);                    // (h,l) = a/b
void div(BYTE &h,BYTE &l,BYTE &r,BYTE ah,BYTE al,BYTE b);   // (h,l) = (ah,al)/b ; r = (ah,al)%b
//---------------------------------------------------------------------------
void inc(BYTE &a) { if (a==0xFF) cy=1; else cy=0; a++; }
void dec(BYTE &a) { if (a==0x00) cy=1; else cy=0; a--; }
void add(BYTE &c,BYTE a,BYTE b)
    {
    c=a+b;
    cy=BYTE(((a &1)+(b &1)   )>>1);
    cy=BYTE(((a>>1)+(b>>1)+cy)>>7);
    }
void adc(BYTE &c,BYTE a,BYTE b)
    {
    c=a+b+cy;
    cy=BYTE(((a &1)+(b &1)+cy)>>1);
    cy=BYTE(((a>>1)+(b>>1)+cy)>>7);
    }
void sub(BYTE &c,BYTE a,BYTE b)
    {
    c=a-b;
    if (a<b) cy=1; else cy=0;
    }
void sbc(BYTE &c,BYTE a,BYTE b)
    {
    c=a-b-cy;
    if (cy) { if (a<=b) cy=1; else cy=0; }
    else    { if (a< b) cy=1; else cy=0; }
    }
void mul(BYTE &h,BYTE &l,BYTE a,BYTE b)
    {
    BYTE ah,al;
    h=0; l=0; ah=0; al=a;
    if ((a==0)||(b==0)) return;
    // long binary multiplication
    for (;b;b>>=1)
        {
        if (BYTE(b&1))
            {
            add(l,l,al);    // (h,l)+=(ah,al)
            adc(h,h,ah);
            }
        add(al,al,al);      // (ah,al)<<=1
        adc(ah,ah,ah);
        }
    }
void div(BYTE &d1,BYTE &d0,BYTE &r,BYTE a1,BYTE a0,BYTE z0)
    {
    //      D = (a0/z0) + ((a1*256)/z0) +                      (( (a0%z0) + ((a1*256)%z0) )/z0);
    //      D = (a0/z0) + ((a1/z0)*256) + ((a1%z0)*(256/z0)) + (( (a0%z0) + ((a1%z0)*(256%z0)) )/z0);
    // edge cases
    if (z0==0){ d0= 0; d1= 0; r=0; }
    if (z0==1){ d0=a0; d1=a1; r=0; }
    // normal division
    if (z0>=2)
        {
        BYTE i0,i1,e0,e1,f0,f1,t,dt;
        i0=0xFF/z0; if ((z0&(z0-1))==0) i0++;   // i0 = 256/z0
        i1=i0*z0; i1^=0xFF; i1++;               // i1 = 256%z0
        t=a1%z0;
        mul(e1,e0,t,i0);                        // e = (a1%z0)*(256/z0)
        mul(f1,f0,t,i1);                        // f = (a1%z0)*(256%z0)
        add(f0,f0,a0%z0);                       // f = (a0%z0) + (a1%z0)*(256%z0)
        adc(f1,f1,0);
        add(d0,a0/z0,e0);
        adc(d1,a1/z0,e1);
        // t = division of problematic term by z0
        t=0;
        for (;f1;)
            {
            dt=f1*i0;
            mul(e1,e0,dt,z0);
            sub(f0,f0,e0);
            sbc(f1,f1,e1);
            t+=dt;
            }
        if (f0>=z0) t+=f0/z0;
        // correct output
        add(d0,d0,t);
        adc(d1,d1,0);
        // remainder
        r=d0*z0;
        r=a0-r;
        }
    }
//---------------------------------------------------------------------------

8bit ALU 根本没有优化,我现在只是将它破坏来测试它,因为原始项目无处可寻……我假设你是在 asm 中做的,所以你可以使用 CPU/ALU 指令来代替。唯一重要的函数是div

注意事项:

这只有 8 位。要将其转换为 64 位,只需将所有 0xFF 更改为 0xFFFFFFFFFFFFFFFFBYTE 为您的数据类型,并将 &lt;&lt;8 更改为 &lt;&lt;64

除法结果在d0d1,余数在r 代码不处理负值。

可悲的是这个词:

(( (a0%z0) + ((a1%z0)*(256%z0)) )/z0);

在其当前状态下还需要 16 位除法(虽然结果不是完全的,但不是任意的,而是两个 mod z0 值的组合)。我设法避免了长除数(对于 16 位:8 位是最坏的情况 7)迭代。但是,我的直觉告诉我,应该使用一些我现在不知道或想不到的模块化数学身份来更简单地计算它。这使得这种划分相对缓慢。

【讨论】:

  • (a0/z0) + ((a1&lt;&lt;64)/z0) 这不起作用,因为将最高有效字节除以常数可能会产生一个无理数。比如说a1=1z0=3,然后是a1/z0 = 0.333333333---,在这种情况下,无论你移动这个数字多少次,你总是会得到错误的答案。在下面查看我的答案
  • 我的方法是 100% 工作和测试......对于无符号整数。完全没有问题...如果它应该处理小数部分,那么应该在某处提到它(例如浮点/定点除法,而不是值是 128 位和 64 位寄存器,这通常意味着整数运算)
  • 在 8+8/8 位、16+16/16 位所有组合上测试,没有任何错误,...它仅使用 3 个半位除法和 2 个全位(或 4 个半位)位)添加没有循环,你可以看到
  • 经过全面测试,真的吗?对于 8 位实现,对于 0x0200 / 0x80(以及 0x20000000ULL / 0x80000000UL,对于 32 位实现),我得到 2 而不是 4,对于 0x0304 / 0x56,我得到 6 而不是 8。但是许多其他组合是正确的(包括您的 0x123456789ABCDEF0ULL / 0x23,它为 d0 返回 0xC297AE99UL),所以,我的代码的 C 实现似乎是正确的。
  • @VTiTux +1 很好,不确定当时发生了什么,但很可能我的测试(已经测试了所有 256^3 组合)有一些误报(之前确实发生在我身上,因为没有乘法的快速 SQRT与编程环境构建的命名冲突)。再也找不到原来的项目了……我已经发现了一些差异……一旦解决就会更新(几乎在那里)。从我发现如果z0 是 2 的幂,(0xFFFFFFFF/z0) 的结果必须增加,但是仍然存在一些差异
【解决方案2】:

解决此类问题的正确方法是回归基础:

  • 将最重要的寄存器除以分母
  • 计算商Q 和其余的R
  • 定义一个新的临时寄存器,最好与其他 2 个长度相同
  • 其余的应该占据临时寄存器中的最高有效位
  • 将较低有效寄存器向右移动 iR 包含的相同位数,并将结果添加到临时寄存器。
  • 返回步骤 1

除法后,所得的余数必须转换为double,除以分母,然后加到商。

【讨论】:

    猜你喜欢
    • 2014-06-11
    • 2017-10-29
    • 2023-04-01
    • 2019-09-01
    • 2013-10-13
    • 2023-04-09
    • 1970-01-01
    • 1970-01-01
    • 2021-09-19
    相关资源
    最近更新 更多