1.引言

2.位運算基礎

3.位運算在角色許可權設計中的應用

4.為什麼in32的範圍是-2^31 ~ 2^31-1 ?

5.同餘的概念

6.模的概念幫助理解補數和補碼。 

一、引言

這周在做一個新增角色許可權需求時,遇到下面這樣一行程式碼,這篇文章將圍繞這行程式碼展開。

user.RoleType = ~(~user.RoleType | 511) | requestDTO.Role;

二、位運算基礎

關於位運算的基礎知識參見:

百度百科:https://baike.baidu.com/item/%E4%BD%8D%E8%BF%90%E7%AE%97

維基百科:https://zh.wikipedia.org/wiki/%E4%BD%8D%E6%93%8D%E4%BD%9C

總結如下:

1.位邏輯非運算(記憶技巧: 二進位制按位取反)
位邏輯非運算按位對運算物件的值進行非運算,即:如果某一位等於0,就將其轉變為1;如果某一位等於1,就將其轉變為0。
比如,對二進位制的10010001進行位邏輯非運算,結果等於01101110,
用十進位制表示就是:~0111(十進位制7)=1000(十進位制8)
 
2.位邏輯與運算(記憶技巧: 二進位制按位 true&&true=true才為true)
位邏輯與運算將兩個運算物件按位進行與運算。與運算的規則:1與1等於1,1與0等於0,0與0等於0。
比如:10010001(二進位制)&11110000等於10010000(二進位制)
 
3.位邏輯或運算(記憶技巧: 二進位制按位 true||false=true,true||true=true)
位邏輯或運算將兩個運算物件按位進行或運算。或運算的規則是:1或1等1,1或0等於1,
0或0等於0。比如10010001(二進位制)| 11110000(二進位制)等於11110001(二進位制)
 
4.位邏輯異或運算(記憶技巧: 二進位制按位 true||false=true 不同時true)
位邏輯異或運算將兩個運算物件按位進行異或運算。異或運算的規則是:1異或1等於0,
1異或0等於1,0異或0等於0。即:相同得0,相異得1。
比如:10010001(二進位制)^11110000(二進位制)等於01100001(二進位制)
 
5.位左移運算
位左移運算將整個數按位左移若干位,左移後空出的部分0。比如:8位的byte型變數
byte a=0x65(即二進位制的01100101),將其左移3位:a<<3的結果是0x27(即二進位制的00101000)
 
6.位右移運算
 位右移運算將整個數按位右移若干位,右移後空出的部分填0。比如:8位的byte型變數
Byte a=0x65(既(二進位制的01100101))將其右移3位:a>>3的結果是0x0c(二進位制00001100)

 三、位運算在角色許可權設計中的應用(優缺點)

業務場景:有A.B.C.D四個基礎角色,現在需要新增一個複合角色(架構師),可以配置使用者。

下面是一個demo例子,位運算在角色許可權中的應用

複製程式碼
   [Flags]
    public enum RoleType
    {
        /// <summary>
        /// 無角色
        /// </summary>
        [Description("無角色")]
        None = 0,
        /// <summary>
        /// 普通使用者角色
        /// </summary>
        [Description("普通使用者")]
        A = 1,
        /// <summary>
        /// 初級開發
        /// </summary>
        [Description("初級開發")]
        B = 2,
        /// <summary>
        /// 中級開發
        /// </summary>
        [Description("中級開發")]
        C = 4,
        /// <summary>
        /// 高階開發
        /// </summary>
        [Description("高階開發")]
        D = 8,
        /// <summary>
        /// 架構師
        /// </summary>
        [Description("架構師")]
        E = 16   //糾正原文錯誤
    }
    public class UnitTest1
    {
        public static void Test1()
        {
            var a = RoleType.A | RoleType.B;  //變數a為 A | B
            var b = RoleType.B | RoleType.D;  //變數b為 B | D
            var aa = a.ToString();//變數aa為 "A,B" 

            var bb = a & (~RoleType.A);//從組合狀態中去掉一個元素A ,結果為 列舉 B
            var bb1 = ~(~a | RoleType.A); //bb結果等價於bb1
            var cc = (b & RoleType.B) != 0;//檢查組合狀態是否包含列舉 B  

            var dd = RoleType.A | RoleType.B | RoleType.B | RoleType.B; //變數dd為 A | B
        }
    }
複製程式碼

分析:

1.為什麼列舉角色數都是2的倍數?

十進位制  二進位制

1    01

2    10

4    100

8    1000

。。。。。。

我們發現在各個位上值都是唯一的,所以做位或運算時,不同值的運算結果是唯一的;反過來,我們也可以根據結果值推算出來包含的列舉(即業務中的角色)

ok,到這裡我們再看開頭引言中的那行程式碼,可以寫為

user.RoleType = (user.RoleType & ~511) | requestDTO.Role;

抽象為x=(x&~y)|z,就是去除x中的y角色,再與z做位或組合。

想下,這個在儲存使用者角色的時候會很巧妙,就是去除使用者 x(原有角色)中的 y(基礎角色),再和z(要儲存的角色),做位或運算組合 得出一個新的要儲存角色。

優點:一個roletype欄位可以儲存使用者的所有角色資訊

缺點:當已經有31個角色,當需要再新增角色的時候,就變的尷尬了(超出了int32位)

解決辦法

1.將roletype欄位擴充套件為64位,但在系統的後期迭代階段影響範圍頗大,還是存在用完的時候

2.新增一張表,將複合角色與基礎角色 這兩個拆分位兩個欄位,單獨儲存兩者之間關係

四、為什麼in32的範圍是-2^31 ~ 2^31-1 ?

為什麼會介紹這個問題,因為當新增角色時,2^32超出了int32的範圍,但是為什麼int32範圍是-2^31 ~ 2^31-1 ?本著對刨根問底的態度,便追尋了下去。

我們可以先研究下8位二進位制的標識範圍為什麼是-2^7~2^7-1

這裡要說下 原碼,反碼,補碼的概念。

原碼

正數的原碼就是它的本身

假設使用一個位元組儲存整數,整數10的原碼是:0000 1010

負數用最高位是1表示負數

假設使用一個位元組儲存整數,整數-10的原碼是:1000 1010

反碼

正數的反碼跟原碼一樣

假設使用一個位元組儲存整數,整數10的反碼是:0000 1010

負數的反碼是負數的原碼按位取反(0變1,1變0),符號位(首位)不變

假設使用一個位元組儲存整數,整數-10的反碼是:1111 0101

補碼

正數的補碼和原碼一樣

假設使用一個位元組儲存整數,整數10的補碼是:0000 1010(這一串是10這個整數在計算機中儲存形式)

負數的補碼是負數的反碼加1

假設使用一個位元組儲存整數,整數-10的補碼是:1111 0110(這一串是-10這個整數在計算機中儲存形式)

 

在計算機中,為什麼不用原碼和反碼,而是用補碼呢?

使用原碼計算10-10

         0000 1010  (10的原碼)

    +        1000 1010   (-10的原碼)

------------------------------------------------------------

         1001 0100  (結果為:-20,很顯然按照原碼計算答案是否定的。)

 

分析:正常的加法規則不適用於正數與負數的加法,因此必須制定兩套運算規則,一套用於正數加正數,還有一套用於正數加負數。從電路上說,就是必須為加法運算做兩種電路

 

使用反碼計算10-10

      0000 1010  (10的反碼)

    +   1111 0101  (-10的反碼)

------------------------------------------------------------

      1111 1111  (計算的結果為反碼,我們轉換為原碼的結果為:1000 0000,最終的結果為:-0,很顯然按照反碼計算答案也是否定的。)

使用補碼計算10-10

      0000 1010  (10的補碼)

   +   1111  0110  (-10的補碼)

------------------------------------------------------------

      1 0000 0000  (由於我們這裡使用了的1個位元組儲存,因此只能儲存8位,最高位(第九位)那個1沒有地方存,就被丟棄了。因此,結果為:0)

 

分析:補碼錶示法可以將加法運算規則,擴充套件到整個整數集,從而用一套電路就可以實現全部整數的加法。補碼是計算機中儲存整數的形式。

 

八位二進位制正數的補碼範圍是0000 0000 ~ 0111 1111 即0 ~ 127

負數的補碼範圍是正數的原碼0000 0000 ~ 0111 1111 取反加一(也可以理解為負數1000 0000 ~ 1111 1111化為反碼末尾再加一)。 所以得到 1 0000 0000 ~ 1000 0001

1000 0001作為補碼,其反碼是1000 0000,其原碼是1111 1111(-127)

依次往前推,可得到1111 1111作為補碼,其反碼1111 1110,原碼1000 0001(-1)

那麼補碼0000 0000(1被捨去)的原碼是1000 0000符號位同時也可以看做數字位即表示-128(-2^7)

類推:in32的範圍便是-2^31 ~ 2^31-1 

 五、同餘的概念

兩個整數a,b,若它們除以整數m所得的餘數相等,則稱a,b對於模m同餘

記作 a ≡ b (mod m)

讀作 a 與 b 關於模 m 同餘。

六、模的概念

時間不早了,模的概念可以幫助理解補數和補碼,下篇部落格中提到吧。。。

 

原文地址:https://www.cnblogs.com/jdzhang/p/9034171.html