contributed by < sammer1107
>
進階電腦系統理論與實作
int asr_i(signed int m, unsigned int n)
{
const int logical = (((int) -1) OP1) > 0;
unsigned int fixu = -(logical & (OP2));
int fix = *(int *) &fixu;
return (m >> n) | (fix ^ (fix >> n));
}
logical
: 首先 -1
對應到 0xFFFFFFFF
也就是二進位的全 1。如果我們將他右移,可作為判斷右移實作的根據。
0xFFFFFFFF
=-1
0x7FFFFFFF
,為正的最大值所以透過判斷唯一之後是否大於 0,就知道編譯器的實作種類。根據上面推論,OP1 為 >> 1
,且 logical 為 1
(需要把高位改成1)或 0
(不需要修正)。
fixu
: 除了判斷編譯器實作,我們也需要判斷 m
是否為負,因為只有負數會因為 shift 的實作會有不同結果。故合理的答案為 OP2
= m < 0
,觀察這兩種情況:
m >= 0
: 故 -(logical & m<0)
得到 0m < 0
: 故 -(logical & m<0)
得到
logical == 1
: 得到二進位的全 1 。logical == 0
: 得到 0 。m
為負,且實作需要修正我們會得到全 1 ,其他都是 0 代表不需修正。-1
轉成 unsigned,根據 c99 - 6.3.1.3 Signed and unsigned integers 是會得到全 1 沒錯。
Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type.
fix
: 只是把 fixu 再轉成有號數,值為 -1,不過為什麼還要 fixu 不直接使用 fix 就好了? 這我想不到
假設現在需要修正,所以 fix
為 0xFFFFFFFF
則
fix
長這樣(簡化長度為 8)
fix >> n
長這樣(假設 n = 2 )
fix ^ (fix >> n)
為
m
假如也右移 2 ,則會空出兩個 0 在左邊,所以 m >> 2 | fix ^ (fix >> n)
就是正確結果了。bool isPowerOfFour(int num)
{
return num > 0 && (num & (num - 1))==0 &&
!(__builtin_ctz(num) OPQ);
}
num > 0
,因為要是 num = 0
,後面都會成立,但 0 不是 power-of-four(num & (num - 1))==0
: 這個可用來確定是否是 2 的冪次方。
(num & (num - 1))
一定不等於 0。 故 (num & (num - 1))==0
時一定為二的次方。(num & (num - 1))==0
num
為二的次方,故 num
的二元表示,只有一個 1 。觀察四的次方,可以發現低位的 0 數量一定是偶數:
num | Binary |
---|---|
00000100 | |
00010000 | |
01000000 |
__builtin_ctz(num) & 0x1
可以判斷低位 0 的個數是否為奇數(奇數得到 1),在加上 NOT 就是判斷是否為四的次方了。bool isPowerOfFour(int num){
return (__builtin_popcount(num) == 1) &&
!(__builtin_ctz(num) & 0x1);
}
(__builtin_popcount(num) == 1)
來取代前兩個判斷。TODO:
x-compressor 是個以 Golomb-Rice coding 為基礎的資料壓縮器,實作中也用到 clz/ctz 指令,可參見 Selecting the Golomb Parameter in Rice Coding。
int numberOfSteps (int num)
{
return num ? AAA + 31 - BBB : 0;
}
根據題目,我們照著規則走即可找出通式,從最低位開始:
令 1 的個數為
對應到答案 __builtin_popcount(num) + 31 - __builtin_clz(num)
#include <stdint.h>
uint64_t gcd64(uint64_t u, uint64_t v) {
if (!u || !v) return u | v;
int shift;
for (shift = 0; !((u | v) & 1); shift++) {
u /= 2, v /= 2;
}
while (!(u & 1))
u /= 2;
do {
while (!(v & 1))
v /= 2;
if (u < v) {
v -= u;
} else {
uint64_t t = u - v;
u = v;
v = t;
}
} while (XXX);
return YYY;
}
第3行: 首先檢查有沒有數字為0,如果有,就回傳不是0的那一個(兩個都0回傳0)
4~7行:
!((u | v) & 1)
的作用在於檢查是不是兩個都是偶數shift
變數中。8~9行: 這裡判斷如果 u 是偶數,就把他所有的質因數 2 去掉。
10~21行: 這裡透過不斷的做減法與除以2,來將 u 與 v 變得愈來愈小,直到最後一個為 0 gcd 的結果就顯而易見了。
XXX
=v
,最後YYY
必須回傳 u << shift
不能用
XXX
=u-v
: 雖然當 v
會被除為 u
,所以迴圈結束時 v=0
,u
不變,但這時 u-v != 0
,所以會陷入無窮迴圈。
程式碼
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
uint64_t gcd64_ctz(uint64_t u, uint64_t v) {
if (!u || !v) return u | v;
int shift = __builtin_ctz(u | v);
u >>= shift;
v >>= shift;
u >>= __builtin_ctz(u);
do {
v >>= __builtin_ctz(v);
if (u < v) {
v -= u;
} else {
uint64_t t = u - v;
u = v;
v = t;
}
} while (v);
return u << shift;
}
uint64_t gcd64(uint64_t u, uint64_t v) {
if (!u || !v) return u | v;
int shift;
for (shift = 0; !((u | v) & 1); shift++) {
u /= 2, v /= 2;
}
while (!(u & 1))
u /= 2;
do {
while (!(v & 1))
v /= 2;
if (u < v) {
v -= u;
} else {
uint64_t t = u - v;
u = v;
v = t;
}
} while (v);
return u << shift;
}
int main(void) {
printf("i, no ctz, ctz\n");
for (size_t i=10000; i<50000; i=i+100){
clock_t start, end;
uint32_t time=0, time_ctz=0;
uint64_t gcd, gcd_ctz;
for (size_t j=1; j<i; ++j) {
start = clock();
gcd_ctz = gcd64_ctz(i,j);
end = clock();
time_ctz += (double)(end-start);
start = clock();
gcd = gcd64(i,j);
end = clock();
time += (double)(end-start);
if (gcd != gcd_ctz) {
printf("error: gcd(%lu,%lu)\n", i, j);
break;
}
}
printf("%lu, %f, %f\n", i, (double)time / CLOCKS_PER_SEC, (double)time_ctz / CLOCKS_PER_SEC);
}
return 0;
}
環境
$ uname -a
Linux Aspire-V5-591G 4.15.0-118-generic #119~16.04.1-Ubuntu SMP Tue Sep 8 14:54:40 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
$ gcc --version
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.12) 5.4.0 20160609
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ cat /proc/cpuinfo | grep name
model name : Intel(R) Core(TM) i5-6300HQ CPU @ 2.30GHz
model name : Intel(R) Core(TM) i5-6300HQ CPU @ 2.30GHz
model name : Intel(R) Core(TM) i5-6300HQ CPU @ 2.30GHz
model name : Intel(R) Core(TM) i5-6300HQ CPU @ 2.30GHz
#include <stddef.h>
size_t improved(uint64_t *bitmap, size_t bitmapsize, uint32_t *out)
{
size_t pos = 0;
uint64_t bitset;
for (size_t k = 0; k < bitmapsize; ++k) {
bitset = bitmap[k];
while (bitset != 0) {
uint64_t t = KKK;
int r = __builtin_ctzll(bitset);
out[pos++] = k * 64 + r;
bitset ^= t;
}
}
return pos;
}
out
這個 array 中。bitmapsize
個 bitmap ,每一個 bitmap 有 64 bit 。因此總共會有 bitmapsize*64
bit 。bitmap[0]
開始,bitmap[0]
的 LSB 為 0, MSB 為 63。 bitmap[1]
的 LSB 為 64, MSB 為 127。依此類推
int r = __builtin_ctzll(bitset);
out[pos++] = k * 64 + r;
r
),而不用逐 bit 查找。找到之後,就在 out
中紀錄下當前的 bit 的位置。KKK
應該要是 0b100
,這樣才可以透過 XOR 把當前的 bit 消掉。KKK
= bitset & -bitset
的作用。二補數中的負號相當於 NOT 後加 1 。
10101000
01010111
01011000
bitset & -bitset
得到 00001000
bitset & -bitset
會保留 bitset
最低位的 1 以下的部份(包含 1),以上則變成 0 ,因為以上的半段只翻轉一次, AND 之後一定為 0。所以最後得到的就是只有一個 set bit 的 bitmask, XOR 之後就可清除最低位的 1 。