# 2021q1 Homework3 (fibdrv) contributed by < `tigger12613` > ## 大數運算 ### 資料結構 用陣列去儲存數字的分割,因為要印出十進位的數字,所以對陣列的每一格最大數字為 $10^{19} - 1$,超出最大數字就進位,這樣才可以印出十進位的數字。 ```cpp // 1e19 - 1; the max power of 10 - 1 of 64 bit unsigned interger #define MaxDecimal 999999999999999999 // the long that bigNum can carry in decimal #define MaxbigNumlen 90 // bigNumArrSize = 5 #define bigNumArrSize MaxbigNumlen / 18 typedef struct bigNum { unsigned long long data[bigNumArrSize]; } bigNum; ``` ### 給定數字 將字串轉成數字。 ```cpp // set the number by string, if success return true, if too large return false. bool set_num(bigNum *num, char *s) { size_t slen = strlen(s); if (slen > MaxbigNumlen) { return false; } unsigned long long tmp = 0; int i = 0; int offset = 0; while (slen) { char char_value = (s[slen - 1] - '0'); if (char_value < 0 || char_value > 9) { return false; } tmp += char_value * (bigNum_pow_10(i)); i++; slen--; if (i == 18) { i = 0; num->data[bigNumArrSize - 1 - offset] = tmp; tmp = 0; offset++; } } num->data[bigNumArrSize - 1 - offset] = tmp; return true; } ``` ### 加法 相加兩個 `bigNum` ,超出最大值回傳 `false` 。 ```cpp // des = a + b // return true if success, return false if overflow // it will still add to des when overflow bool add_bignum(bigNum *des, bigNum *a, bigNum *b) { unsigned long long carry = 0; for (int i = bigNumArrSize - 1; i >= 0; i--) { unsigned long long int tmp = a->data[i] + b->data[i] + carry; if (tmp <= MaxDecimal) { des->data[i] = tmp; carry = 0; } else { des->data[i] = tmp - MaxDecimal - 1; carry = 1; } if (i == 0 && carry == 1) { return false; } } return true; } ``` ### 印出數字 印出十進位的數字。 ```cpp void printDecimal(bigNum *num) { bool is_printed = false; for (int i = 0; i < bigNumArrSize; i++) { if (is_printed) { printf("%018llu", num->data[i]); } else { if (num->data[i]) { printf("%llu", num->data[i]); is_printed = true; } } } if (is_printed == false) { printf("0"); } } ``` ### 測試大數加法 使用 `bigNum` 計算 `fibonacci serious` 在 `f(433)` 產生溢出。 ``` ... f(425) = 29529908689737440719341867053506936071765074245955118399664162441967250186097733500962925 f(426) = 47780395944676052274416277690031248729785923474969864327823043828116713025492002771430968 f(427) = 77310304634413492993758144743538184801550997720924982727487206270083963211589736272393893 f(428) = 125090700579089545268174422433569433531336921195894847055310250098200676237081739043824861 f(429) = 202401005213503038261932567177107618332887918916819829782797456368284639448671475316218754 f(430) = 327491705792592583530106989610677051864224840112714676838107706466485315685753214360043615 f(431) = 529892711006095621792039556787784670197112759029534506620905162834769955134424689676262369 f(432) = 857384416798688205322146546398461722061337599142249183459012869301255270820177904036305984 overflow: f(433) = 387277127804783827114186103186246392258450358171783690079918032136025225954602593712568353 ``` :::warning TODO: 學習 [第 6 週測驗題](https://hackmd.io/@sysprog/linux2021-quiz6) 的大數運算實作 :notes: jserv ::: ## 運用 clz/ctz 指令搭配 Fast Doubling 手法計算費氏數列 首先需要增加幾個計算 ### 大數減法 ```cpp= //des = a - b bool bigNum_minus(bigNum *des, bigNum *a, bigNum *b){ init_bigNum(des); int carry = 0; for (int i = bigNumArrSize - 1 ; i >=0; i--){ if(a->data[i] >= b->data[i] + carry){ des->data[i] = a->data[i] - b->data[i] - carry; carry = 0; }else{ des->data[i] = a->data[i] + (MaxDecimal - b->data[i] + 1 - carry); carry = 1; } } return true; } // a--, if <0 return false, else return true bool bigNum_minus_1(bigNum *a) { for (int i = bigNumArrSize - 1; i >= 0; i--) { if (a->data[i] > 0) { a->data[i]--; return true; } else { a->data[i] = MaxDecimal; } } return false; } ``` ### 比較兩個數字 ```cpp= // return -1 if a<b, 0 if a=b, 1 if a>b. int bigNum_cmp(bigNum *a, bigNum *b) { for (int i = bigNumArrSize - 1; i >= 0; i--) { if (a->data[i] < b->data[i]) { return -1; } else if (a->data[i] > b->data[i]) { return 1; } } return 0; } ``` ### 複製一個變數的數值到另一個變數上 ```cpp= // copy value from src to des void bigNum_copy(bigNum *des, bigNum *src) { for (int i = bigNumArrSize - 1; i >= 0; i--) { des->data[i] = src->data[i]; } } ``` ### 乘法 因為 `bigNum` 的陣列已經用了 64bit ,所以無法用一般乘法去實作大數乘法,只能用加法實作,這將導致運行速度緩慢。 ```cpp= // des = a * b bool bigNum_mul(bigNum *des, bigNum *a, bigNum *b) { bigNum d; bigNum m; init_bigNum(des); bigNum_set_num(des, "0"); if (bigNum_cmp(a, b)) { bigNum_copy(&d, b); bigNum_copy(&m, a); } else { bigNum_copy(&d, a); bigNum_copy(&m, b); } bigNum zero; bigNum_set_num(&zero, "0"); while (bigNum_cmp(&d, &zero)) { if (!bigNum_add(des, des, &m)) { return false; } bigNum_minus_1(&d); } return true; } ``` ### Fast Doubling 對照虛擬碼實作 ```cpp= Fast_Fib(n) a = 0; b = 1; // m = 0 for i = (number of binary digit in n) to 1 t1 = a*(2*b - a); t2 = b^2 + a^2; a = t1; b = t2; // m *= 2 if (current binary digit == 1) t1 = a + b; // m++ a = b; b = t1; return a; ``` 實作 ```cpp= bigNum fib_sequence(int k) { bigNum a,b,t1,t2,two,tmp,tmp2,tmp3; init_bigNum(&tmp); init_bigNum(&tmp2); init_bigNum(&tmp3); bigNum_set_num(&two,"2"); bigNum_set_num(&a,"0"); bigNum_set_num(&b,"1"); for (int i = 32 - __builtin_clz(k)-1; i >= 0 ; i--){ //t1 = a*(2*b - a) bigNum_mul(&tmp,&two,&b); bigNum_minus(&tmp2,&tmp,&a); bigNum_mul(&t1,&a,&tmp2); //t2 = b^2 + a^2 bigNum_mul(&tmp,&b,&b); bigNum_mul(&tmp2,&a,&a); bigNum_add(&t2,&tmp,&tmp2); //a=t1;b=t2 bigNum_copy(&a,&t1); bigNum_copy(&b,&t2); if((k>>i)&1){ bigNum_add(&t1,&a,&b); bigNum_copy(&a,&b); bigNum_copy(&b,&t1); } } return a; } ``` ### 測試輸出 與預期相符,但是效率非常糟糕。 ```shell= ... F(90): 2880067194370816120 F(91): 4660046610375530309 F(92): 7540113804746346429 F(93): 12200160415121876738 F(94): 19740274219868223167 F(95): 31940434634990099905 F(96): 51680708854858323072 F(97): 83621143489848422977 ``` ### 效率比較 待完成