前言
(1)今天看到一個有意思的問題,如何判斷一個數字是否為2的若干次冪。這個問題并不難,但是對于我們的C語言功底還是有一點點的考驗的。
(2)希望各位可以先自行思考,實在想不出來再看后面的講解。提示,C語言的位運算是一個好東西。
解析
2的若干次冪數所存在的特征點
(1)首先,我們需要知道2的若干次冪所存在的特征點。當我們知道了這個特征點之后,就可以將這個特征點與其他數進行分離了。
(2)我們都知道,計算機是2進制系統。如果讓一個數字乘以2,我們是不是可理解為,讓這個二進制數右移動一位呢?
(3)既然我們知道了,在計算機中乘以2就是進行一次右移操作。那么2的n次冪,是不是就是二進制數1進行右移n次呢?例如,數字2換成二進制是10,而十進制的數字2恰好就是2的1次冪,因而二進制的1進行一次右移操作。
(4)好,我們再進行擴展,既然2的n次冪就是數字1進行右移n次。那么2的n次冪在二進制中是不是有一個特點,那就是只有一個位為1!(不理解的同學可以看一下下面這幾個例子,我們發現2的n次冪的數8和4只有一個位為1)
如何提取這個特征點
(1)現在我們知道了2的若干次冪在二進制中,只會有一個位是1,其他位是數字0。因此,我們是不是得想個辦法,判斷一個二進制數只存在一個1。
(2)于是,我們可以想到"&"運算。他的特點在于,有0出0。假設我們的二進制數是100,那么讓100減去1變成011。這樣原來那個存放唯一數字1的地方就會變成0了。
(3)然后100&011,就會變成0。最后邏輯取反即可。
!((x)&(x-1))
(4)但是肯定有朋友會想舉其他例子嘗試反駁,很不幸的是你找不到的。
(5)為什么這么說呢?因為,我們要抓住這個方法的巧妙之處,我們是將唯一的存放1的位變成0。
(6)但是,我們假設有一個可以反駁的數1010。(注意,這里是假設)1010-1=1001,有沒有發現一個問題,這里的最高位的數字1永遠無法被消除。因此,進行如上操作之后,最終還是可以分辨出這不是2的n次冪。
上述代碼存在bug?
bug1 — 不承認1為2的若干次冪
(1)看到上述代碼,有一些東西肯定想到了一個刁鉆的角度。數字1經過上述代碼,最終輸出的也是1啊。所以你這個代碼有問題!這個時候我建議還是重新學一下小學數學啊(苦笑)。1就是2的0次冪呀。
(2)但是有一些朋友就是不承認1怎么辦呢?也很簡單,判斷這個數是不是1唄。
x == 1 ? 0 : !((x)&(x-1))
bug2 — 數字0所導致的問題
(1)數字1導致的問題解決了,我們角度再刁鉆一點,假設這個數字是0呢?真的可以嗎?
(2)我們在Linux中執行如下代碼。
#include <stdio.h>
#include <stdlib.h>#define if_2_equation(x) !((x)&(x-1))int main(int argc,char** argv)
{char i;//如果輸入參數小于2個,打印本程序使用方法if(argc < 2){printf("Usage: \r\n");printf("%s <string> \r\n",argv[0]);return -1;}i = strtol(argv[1], NULL, 0);printf("number = %d \r\n",i);if(if_2_equation(i)){printf("yes\r\n");}else{printf("no\r\n");}return 0;
}
(3)執行發現,數字0也被當成了2的若干次冪,這個從數學的角度上來看,怎么也說不清楚啊。為什么會發生這種情況呢?很簡單,0&所有的數,都是0。因此,這里就存在bug。
(4)怎么處理呢?很簡單,進行一次判斷唄。
#define if_2_equation(x) x == 0 ? 0 : !((x)&(x-1))
(5)但是有些騷年會想,我1和0這兩個數都不打算當成2的若干次冪來判斷,這個怎么處理呢?也很簡單,進行兩次判斷唄。
#define if_2_equation(x) x == 0 ? 0 : (x == 1 ? 0 : !((x)&(x-1)))