程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 大整數算法[13] 單數位乘法,整數乘法

大整數算法[13] 單數位乘法,整數乘法

編輯:關於C語言

大整數算法[13] 單數位乘法,整數乘法


        ★ 引子

        最近在折騰 wxWidgets,同時拖延症又犯了,所以中斷了好久。這次來講講單數位乘法,前面講到 Comba 和 Karatsuba 乘法,這兩個算法適合用來處理比較大的整數,但是對於一個大整數和一個單精度數相乘,其效果反而會不好,因為計算量過多。實際上單數位乘法只是基線乘法的一個特例,不存在嵌套循環進位,因此可以通過優化減少計算量。另外與完整的乘法不同的是,單數位乘法不需要什麼臨時變量存儲和內存分配(目標精度增加除外)。

 

        ★ 算法思路

        單數位乘法類似於計算 1234567890 * 8 這種計算,第二個數只有一位,在大整數中就是一個單精度變量,只需要執行 O(n) 次單精度乘法就可以完成主要的計算。每一次單精度乘法計算完後,執行進位傳遞。具體的實現思路如下:

      

         計算 z = x * y,其中 z 和 x 是 bignum,y 是無符號的單精度數。

         1. olduse = z->used       記錄當前 z 使用了多少數位,用於輔助後面的高位清零。

         2. 目標精度增加 1 : z->used = x->used +1

         3. z->sign = x->sign (因為 y 為無符號數,所以結果符號只和 x 有關)

         4. 初始化進位: u = 0

         5. 對於 i 從 0 到 x->used - 1 之間進行循環:

              5.1  r = u + x(i) * y                //r 是雙精度變量

              5.2  z(i) = r & BN_MASK0     //取低半部分作為本位  (32 bit 下,BN_MASK0 = 0xFFFFFFFF,其他情況同理,為 2^n - 1)

              5.3  u = r >> biL                  //取高半部分作為進位

          6. z(x->used) = u                      //傳遞最後一個進位

          7. 剩余的高位清零

          8. 壓縮多余位

 

          這裡 y 是無符號數,如果想計算和 -y 的乘積,在計算完後將 z 的符號取反即可。

 

        ★ 實現

         和 Comba 乘法一樣,先給出總體實現,具體細節後面講。考慮到計算效率和可移植性的問題,第五步的循環關鍵代碼還是寫在宏定義裡面,然後按照 1,4,8,16 和 32 的步進展開乘法器,減少循環控制的開銷。

int bn_mul_word(bignum *z, const bignum *x, const bn_digit y)
{
    int ret;
    size_t i, olduse;
    bn_digit u, *px, *pz;

    olduse = z->used;
    z->sign = x->sign;
    BN_CHECK(bn_grow(z, x->used + 1));

    u = 0;
    px = x->dp;
    pz = z->dp;

    for(i = x->used; i >= 32; i -= 32)
    {
        MULADDC_WORD_INIT
        MULADDC_WORD_CORE    MULADDC_WORD_CORE
        MULADDC_WORD_CORE    MULADDC_WORD_CORE
        MULADDC_WORD_CORE    MULADDC_WORD_CORE
        MULADDC_WORD_CORE    MULADDC_WORD_CORE

        MULADDC_WORD_CORE    MULADDC_WORD_CORE
        MULADDC_WORD_CORE    MULADDC_WORD_CORE
        MULADDC_WORD_CORE    MULADDC_WORD_CORE
        MULADDC_WORD_CORE    MULADDC_WORD_CORE

        MULADDC_WORD_CORE    MULADDC_WORD_CORE
        MULADDC_WORD_CORE    MULADDC_WORD_CORE
        MULADDC_WORD_CORE    MULADDC_WORD_CORE
        MULADDC_WORD_CORE    MULADDC_WORD_CORE

        MULADDC_WORD_CORE    MULADDC_WORD_CORE
        MULADDC_WORD_CORE    MULADDC_WORD_CORE
        MULADDC_WORD_CORE    MULADDC_WORD_CORE
        MULADDC_WORD_CORE    MULADDC_WORD_CORE
        MULADDC_WORD_STOP
    }
    for(; i >= 16; i -= 16)
    {
        MULADDC_WORD_INIT
        MULADDC_WORD_CORE    MULADDC_WORD_CORE
        MULADDC_WORD_CORE    MULADDC_WORD_CORE
        MULADDC_WORD_CORE    MULADDC_WORD_CORE
        MULADDC_WORD_CORE    MULADDC_WORD_CORE

        MULADDC_WORD_CORE    MULADDC_WORD_CORE
        MULADDC_WORD_CORE    MULADDC_WORD_CORE
        MULADDC_WORD_CORE    MULADDC_WORD_CORE
        MULADDC_WORD_CORE    MULADDC_WORD_CORE
        MULADDC_WORD_STOP
    }
    for(; i >= 8; i -= 8)
    {
        MULADDC_WORD_INIT
        MULADDC_WORD_CORE    MULADDC_WORD_CORE
        MULADDC_WORD_CORE    MULADDC_WORD_CORE
        MULADDC_WORD_CORE    MULADDC_WORD_CORE
        MULADDC_WORD_CORE    MULADDC_WORD_CORE
        MULADDC_WORD_STOP
    }
    for(; i >= 4; i -= 4)
    {
        MULADDC_WORD_INIT
        MULADDC_WORD_CORE    MULADDC_WORD_CORE
        MULADDC_WORD_CORE    MULADDC_WORD_CORE
        MULADDC_WORD_STOP
    }
    for(; i > 0; i--)
    {
        MULADDC_WORD_INIT
        MULADDC_WORD_CORE
        MULADDC_WORD_STOP
    }

    *pz++ = u;

    for(i = x->used + 1; i < olduse; i++)
        *pz++ = 0;

    z->used = x->used + 1;
    bn_clamp(z);

clean:

    return ret;
}

         

          以上是單數位乘法的總體實現,關鍵的地方都在宏定義中,下面將講講不同環境下的實現方式。

 

        ★ 單雙精度變量都有的情況

          此情況下, bn_digit 和 bn_udbl 同時有定義,最容易實現的。三個宏的定義如下:

#define MULADDC_WORD_INIT                          \
{                                                  \
    bn_udbl r;                                     \

#define MULADDC_WORD_CORE                          \
	                                               \
    r = u + (bn_udbl)(*px++) * y;                  \
    *pz++ = (bn_digit)r;                           \
    u = (bn_digit)(r >> biL);                      \

#define MULADDC_WORD_STOP                          \
}
 

           這種情況完全是按照思路實現的,具體原理就不多說了。

 

        ★ 只有單精度變量的情況

        如果遇到這種情況,則 bn_udbl 無定義,單精度乘法需要轉換成 4 個 半精度的乘法來計算,相對比較復雜。具體的實現原理和 Comba 的乘法器類似,參考此處:http://www.cnblogs.com/starrybird/p/4441022.html 。 具體的宏實現如下:

#define MULADDC_WORD_INIT                           \
{                                                   \
    bn_digit a0, a1, b0, b1;                        \
    bn_digit t0, t1, r0, r1;                        \

#define MULADDC_WORD_CORE                           \
	                                                \
    a0 = (*px << biLH) >> biLH;                     \
    b0 = (  y << biLH) >> biLH;                     \
    a1 = *px++ >> biLH;                             \
    b1 =  y    >> biLH;                             \
    r0 = a0 * b0;                                   \
    r1 = a1 * b1;                                   \
    t0 = a1 * b0;                                   \
    t1 = a0 * b1;                                   \
    r1 += (t0 >> biLH);                             \
    r1 += (t1 >> biLH);                             \
    t0 <<= biLH;                                    \
    t1 <<= biLH;                                    \
    r0 += t0;                                       \
    r1 += (r0 < t0);                                \
    r0 += t1;                                       \
    r1 += (r0 < t1);                                \
    r0 += u;                                        \
    r1 += (r0 < u);                                 \
    *pz++ = r0;                                     \
    u = r1;                                         \

#define MULADDC_WORD_STOP                           \
}

 

        ★ 使用內聯匯編的情況

        C 的內聯匯編細節就不多說了,如果你不會可以跳過。

 

        VC x86:

#define MULADDC_WORD_INIT                           \
{                                                   \
    __asm   mov   esi, px                           \
    __asm   mov   edi, pz                           \
    __asm   mov   ecx, u                            \

#define MULADDC_WORD_CORE                           \
	                                                \
    __asm   lodsd                                   \
    __asm   mul   y                                 \
    __asm   add   eax, ecx                          \
    __asm   adc   edx, 0                            \
    __asm   mov   ecx, edx                          \
    __asm   stosd                                   \

#define MULADDC_WORD_STOP                           \
	                                                \
    __asm   mov   px,  esi                          \
    __asm   mov   pz,  edi                          \
    __asm   mov   u,   ecx                          \
}

#endif

 

        GCC x86:

#define MULADDC_WORD_INIT                           \
{                                                   \
    asm                                             \
    (                                               \
       "movl %3, %%esi       \n\t"                  \
       "movl %4, %%edi       \n\t"                  \
       "movl %5, %%ecx       \n\t"                  \

#define MULADDC_WORD_CORE                           \
	                                                \
       "lodsl                \n\t"                  \
       "mull %6              \n\t"                  \
       "addl %%ecx, %%eax    \n\t"                  \
       "adcl $0, %%edx       \n\t"                  \
       "movl %%edx, %%ecx    \n\t"                  \
       "stosl                \n\t"                  \

#define MULADDC_WORD_STOP                           \
                                                    \
       "movl %%esi, %0       \n\t"                  \
       "movl %%edi, %1       \n\t"                  \
       "movl %%ecx, %2       \n\t"                  \
       :"=m"(px),"=m"(pz),"=m"(u)                   \
       :"m"(px),"m"(pz),"m"(u),"m"(y)               \
       :"%eax","%ecx","%edx","%esi","%edi"          \
    );                                              \
}

 

        GCC x64:

#define MULADDC_WORD_INIT                           \
{                                                   \
    asm                                             \
    (                                               \
       "movq %3, %%rsi       \n\t"                  \
       "movq %4, %%rdi       \n\t"                  \
       "movq %5, %%rcx       \n\t"                  \

#define MULADDC_WORD_CORE                           \
	                                                \
       "lodsq                \n\t"                  \
       "mulq %6              \n\t"                  \
       "addq %%rcx, %%rax    \n\t"                  \
       "adcq $0, %%rdx       \n\t"                  \
       "movq %%rdx, %%rcx    \n\t"                  \
       "stosq                \n\t"                  \

#define MULADDC_WORD_STOP                           \
                                                    \
       "movq %%rsi, %0       \n\t"                  \
       "movq %%rdi, %1       \n\t"                  \
       "movq %%rcx, %2       \n\t"                  \
       :"=m"(px),"=m"(pz),"=m"(u)                   \
       :"m"(px),"m"(pz),"m"(u),"m"(y)               \
       :"%rax","%rcx","%rdx","%rsi","%rdi"          \
    );                                              \
}

 

        ★ 總結

        算法也很簡單,按照 Baseline Multiplication 的方法做就行了,注意關鍵的地方優化一下。下一篇講講平方的計算。        

 

   【回到本系列目錄】 

 

版權聲明
原創博文,轉載必須包含本聲明,保持本文完整,並以超鏈接形式注明作者Starrybird和本文原始地址:http://www.cnblogs.com/starrybird/p/4489859.html

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved