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

大整數算法[05] 移位操作,整數05

編輯:關於C語言

大整數算法[05] 移位操作,整數05


         上一篇博文講到了大整數位的相關操作,這次的內容依然和位的操作相關:移位操作。需要說明的是,這裡所講的大整數移位操作都是算術移位(左移的話精度會增加,而不是把移出的位丟掉)。

 

         ★ 兩種移位的區別

         在移位操作當中存在兩種不同的操作方式:邏輯移位和算術移位。在邏輯移位當中,移出的位丟失,移入的位取 0。在算術移位當中,移出的位丟失,左移入的位取 0,右移入的位取符號位,即最高位代表的數據符號保持不變。舉個例子:有如下兩個類型的變量(32位系統下)被定義:

         unsigned int u = 0x80000000;   (8 後面有 7 個 0)

         int s = 0x80000000;

         用二進制表示都是:1000 0000 0000 0000 0000 0000 0000 0000

         u 跟 s 的區別在於 s 的最高位是符號位(0 代表正數,1 代表負數),所以如果用以下語句打印這兩個變量,結果會是:2147483648, -2147483648。

         printf("\nu = %u\n", u);
         printf("\ns = %d\n", s);

         第一個結果好說,無符號數就是 2^31,但是為什麼第二個是 - 2^31 呢?按理說應該是 - 0 才對呀。原因是這些數都是用補碼表示的。

         先來看看 - 2^31 - 1 (-2147483647)這個數用補碼怎麼表示。

         原碼:1111 1111 1111 1111 1111 1111 1111 1111,注意最高位是符號位。要求補碼,先計算反碼,反碼的計算是符號位不變,其余位取反。

         反碼:1000 0000 0000 0000 0000 0000 0000 0000,得到反碼之後,將反碼加 1 就得到補碼了。

         補碼:1000 0000 0000 0000 0000 0000 0000 0001。所以 - 2^31 - 1 用補碼表示就是 0x80000001。

         要表示 - 2^31,只需要將 - 2^31 - 1 的補碼減 1 即可,所以是 0x80000000,這就得到 32 位有符號整形下可表示的最小負整數。

 

         下面看看進行移位後的結果是怎樣的。

         對於左移操作,不管是邏輯左移還是算術左移,左邊移出的位都丟失,右邊移入的位都取 0,所以如果執行如下兩條語句:u <<= 1; s <<= 1; 其結果都是 0。

         對於右移操作,邏輯右移左邊補 0,算術右移左邊補符號位。所以如果執行如下兩條語句:u >>= 1; s >>= 1; 其結果:1073741824, -1073741824。結果的補碼用二進制表示就是:

          u:0100 0000 0000 0000 0000 0000 0000 0000

          s:1100 0000 0000 0000 0000 0000 0000 0000

          上面的右移 1 位的操作,相當於在計算:2147483648 / 2 = 1073741824;  - 2147483648 / 2 = - 1073741824。

          對於移位操作,左移 n 位相當於乘以 2^n,右移 n 位相當於除以 2^n。

 

          前面廢話那麼多就是想說明邏輯移位和算術移位是有區別嘀。對於大整數,算術移位操作不需要用什麼補碼,因為 dp 指向的內存保存的是大整數的絕對值,符號是用 sign 來跟蹤的。另外和C裡面不同的是,如果左移後位數不夠,大整數的精度會增加,而不像C那樣丟棄移出來的位。

 

         ★ 左移和右移 b 個數位

          簡單來說就是乘以或除以 2 ^ (biL *b),移位操作是以整個數位為單元進行的。

          左移 b 個數位:

int bn_lshd(bignum *x, size_t b)
{
    int ret;
    size_t i;
    bn_digit *top, *bottom;

    x->used += b;
    BN_CHECK(bn_grow(x, x->used));

    top = x->dp + x->used - 1;
    bottom = x->dp + x->used - b - 1;

    for(i = x->used; i > b; i--)
        *top-- = *bottom--;

    top = x->dp;

    for(i = 0; i < b; i++)
        *top++ = 0;

clean:

    return ret;
}

          左移 b 個數位後,數位增加 b 位,所以 used 值要增加 b。使用 bn_grow 函數增加 bignum 的精度。然後用 top 指針指向 bignum 左移後的最高位,bottom 指針指向 bignum 當前的最高位,然後循環 used - b 次把每一個數位往左移動 b 個數位。操作結束後,讓 top 指針指向最低位,循環 b 次把最低的 b 個數位置 0,完成移位操作。

 

            右移 b 個數位:

int bn_rshd(bignum *x, size_t b)
{
    int ret = 0;
    size_t i;
    bn_digit *top, *bottom;

    if(x->used <= b)
    {
        BN_CHECK(bn_set_word(x, 0));
        return ret;
    }

    bottom = x->dp;
    top = x->dp + b;

    for(i = 0; i < x->used - b; i++)
        *bottom++ = *top++;

    for(; i < x->used; i++)
        *bottom++ = 0;

    x->used -= b;

clean:

    return ret;
}

          和左移不同的是,右移精度不會增加,但如果 used 的值小於等於 b,則 bignum 的最高位都會從右邊被移出去,結果為 0。如果 used > b,讓 bottom指向 bignum 的最低數位,top 指針指向第 b + 1 位,然後循環 used - b 次將每個數位往右挪動 b 個數位,最後將左邊剩余的 b 位取 0,完成右移操作。

 

         ★ 左移和右移 1 個比特位

         很好理解,就是乘以 2 或者除以 2。下面的算法完成後會把 x 的值賦值給 y。

         左移 1 位:

int bn_lshift_1(bignum *y, const bignum *x)
{
    int ret;
    size_t i, olduse, shift;
    bn_digit r, rr, *px, *py;

    BN_CHECK(bn_grow(y, x->used + 1));

    olduse = y->used;
    y->used = x->used;

    px = x->dp;
    py = y->dp;

    r = 0;
    shift = (size_t)(biL - 1);

    for(i = 0; i < x->used; i++)
    {
        rr = *px >> shift;
        *py++ = (*px++ << 1) | r;
        r = rr;
    }

    if(r != 0)
    {
        *py = 1;
        y->used++;
    }

    py = y->dp + y->used;

    for(i = y->used; i < olduse; i++)
        *py++ = 0;

    y->sign = x->sign;

clean:

    return ret;
}

         首先算法默認將 y 的精度增加到 x->used + 1 個數位,之所以要加 1,因為有可能剛好把最高位移到下一個數位中去。olduse 記錄當前 y 的數位,然後將 y 的數位增加到 x 的數位,如果最終還有進位,y->used 還要加一。shift 變量表明每個數位要往右移動的比特位數,例如在 32 位系統下,shift = 31,64 位系統下 shift = 63。變量 r 存儲上一個數位最左邊的比特位,變量 rr 存儲當前數位最左邊的比特位。在循環當中,先將當前數位右移 shift 位來獲得最左邊的比特位,然後再將這個數位左移 1 位並且與右邊數位的最左邊比特位做 OR 操作,這樣當前數位中的每一比特位就往左邊移動了 1 位,並且右邊數位的最左邊比特位也移動到當前數位的最右位置。循環結束後,如果 r 不為 0,表明原來 bignum 最左邊數位的最左邊比特位為 1,在左移中被移到了新的數位中,所以 used 加一,新的數位值為 1。最後將 y 的高位設置為 0,把 x 的符號給 y 完成所有操作。

 

            右移 1 位:

int bn_rshift_1(bignum *y, const bignum *x)
{
    int ret;
    size_t i, olduse, shift;
    bn_digit r, rr, *px, *py;

    BN_CHECK(bn_grow(y, x->used));

    olduse = y->used;
    y->used = x->used;

    px = x->dp + y->used - 1;
    py = y->dp + y->used - 1;

    r = 0;
    shift = (size_t)(biL - 1);

    for(i = y->used; i > 0; i--)
    {
        rr = *px & 1;
        *py-- = (*px-- >> 1) | (r << shift);
        r = rr;
    }

    py = y->dp + y->used;

    for(i = y->used; i < olduse; i++)
        *py++ = 0;

    y->sign = x->sign;

    bn_clamp(y);

clean:

    return ret;
}

          右移 1 位精度不會增加,不過仍然要調用 bn_grow 函數增加精度,因為一開始 y 的精度可能不夠。右移 1 位的操作跟左移 1 位的操作比較類似,只是方向相反。在第一個循環當中,先獲取當前數位的最右比特位存放到變量 rr 中,然後當前數位右移 1 位,接著將左邊數位的最右比特位左移 shift 位後與當前數位做 OR 操作,最後將 rr 的值存放到變量 r 中,這樣當前位的所有比特都往右移動了 1 位,並且左邊數位的最右邊比特也移動到當前數位的最左邊。完成循環後將高位設置為 0,然後設置符號位,最後壓縮多余位完成操作。

 

         ★ 左移和右移 n 個比特位

          如果說前面的移位操作都帶有特殊性,那麼下面這兩個操作就是將其一般化而已。左移或右移 n 位相當於乘以或除以 2^n。

          左移 n 位:

int bn_lshift(bignum *y, const bignum *x, size_t count)
{
    int ret;
    size_t i, d, shift;
    bn_digit r, rr, *py;

    if(y != x)
        BN_CHECK(bn_copy(y, x));

    BN_CHECK(bn_grow(y, y->used + count / biL + 1));

    if(count >= biL)
        BN_CHECK(bn_lshd(y, count / biL));

    d = count & (biL - 1);

    if(d != 0)
    {
        py = y->dp;
        shift = (size_t)(biL - d);

        r = 0;

        for(i = 0; i < y->used; i++)
        {
            rr = (*py >> shift);
            *py = (*py << d) | r;
            py++;
            r = rr;
        }

        if(r != 0)
            y->dp[y->used++] = r;
    }

clean:

    return ret;
}

         首先算法檢查並增加 y 的精度,接著如果左移的位數 count 大於或等於一個數位的比特數,調用 bn_lshd 函數將 x 左移 count / biL 個數位,然後檢查是否有剩余的比特位:d = count & (biL - 1),如果 d 不為 0,表明還有剩余位,按照左移 1 位的原理提取剩余位向左移動完成左移 n 位的操作。

 

           右移 n 位:

int bn_rshift(bignum *y, const bignum *x, size_t count)
{
    int ret = 0;
    size_t i, d, shift;
    bn_digit r, rr, *py;

    if(y != x)
        BN_CHECK(bn_copy(y, x));

    if(count >= biL) BN_CHECK(bn_rshd(y, count / biL));

    d = count & (biL - 1);

    if(d != 0)
    {
        py = y->dp + y->used - 1;
        shift = (size_t)(biL - d);

        r = 0;

        for(i = y->used; i > 0; i--)
        {
            rr = *py;
            *py = (*py >> d) | (r << shift);
            py--;
            r = rr;
        }
    }

    bn_clamp(y);

clean:

    return ret;
}

        和左移 n 位一樣,先做整個數位的右移,然後再按照右移 1 位的原理往右移動剩余的比特位。要注意的是,右移之後,需要壓縮多余位來更新 used 的值。

 

         ★ 時間復雜度分析

         本文所講到的移位操作,都是在一重循環內完成的,算法的復雜度與 bignum 的精度和大小有關,時間復雜度大致為 O(n)。

 

         ★ 總結

         移位操作對於某些特殊的計算是十分有效的,比如乘以或除以 2,因此碰到這類計算,最好使用移位操作而不是乘法或除法。講完了大整數的位操作,接下來就要開始講講四則運算了。下一篇文章將介紹絕對值加法。

 

     【回到本系列目錄】 

 

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

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