2018年9月30日 星期日

C++ 左值參照(&)及右值參照(&&)

左值參照(lvalue reference  &)可以當成變數的別名, 右值參照(rvalue reference &&)一般用在記憶暫存值, 兩者都需事先指定好記憶空間才能參照, 參照一詞隱函了指標的語意, 參考下面程式碼:
// a.cpp
#include <stdio.h>
int main( ) {
       int a = 3;     // normal variable
       int &b = a;  // lvalue reference of a
       int &&c = a + 1; // rvalue reference of a+1
       printf("a=%d b=%d c=%d\n", a, b, c);
       b = 1;   
       printf("a=%d b=%d c=%d\n", a, b, c);
       c = 6;
       printf("a=%d b=%d c=%d\n", a, b, c);
}
執行 g++   a.cpp && ./a.out
a=3 b=3 c=4
a=1 b=1 c=4
a= 1 b=1 c=6
初始化變數 a 等於 3, b 是 a 的左值參照, 因此 b 是 a 的別名, 其值等於 a, 而 c 是 a+1 的右值參照, 記憶空間位在暫存區, 其值等於 4 ,  a 及 b 參考到相同位置, 因此改變 b 相當於改變 a 的值, 但 c 參照的是 a + 1 的暫存空間, 雖然 a 已被改變, 但不影響到 c 的初始值 4,  而且 c 還可以利用 a + 1 的暫存空間直接改變值成為 6, 省下重新分配記憶空間的程序, 同時也不影響到 a 或 b 的值, 這是右值參照特殊的地方. 一個物件可以經由標準程式庫的 move( ) 程序(move semantics), 將物件強制轉化為右值參照的型別, 也就是轉化物件讓右值參照也變成別名使用
// b.cpp
#include <stdio.h>
#include <iostream>
using namespace std;
int main( ) {
    int a = 3;
    int &b = a;
    int &&c = move(a);
    printf("a=%d b=%d c=%d\n", a, b, c);
    b = 1;       
    printf("a=%d b=%d c=%d\n", a, b, c);
    c = 6;
    printf("a= %d b=%d c=%d\n",a,b,c);
}
執行 g++  b.cpp && ./a.out
a=3 b=3 c=3
a=1 b=1 c=1
a= 6 b=6 c=6
上述可以看到不管是改變 b 或 c, 同時 a 也跟著改變, 通常 move semantics 是用在函式輸入參數不想複製整個物件而宣告成右值參照時, 因右值參照(視為暫存)宣告導致傳進左值參照會不合法, 於是可以利用 move semantics 將物件轉化成右值參照再傳給函式, 兼顧了無需複製的效能也完成了函式該有的功能
// c.cpp
#include <stdio.h>
#include <iostream>
using namespace std;
void ss(int &&temp) { // using rvalue reference
    temp = temp*2;
}
int main() {
    int a = 3;
    int &b = a;
    int &&c = move(a);
    int d   = move(a);
    printf("a= %d b=%d c=%d d=%d\n",a,b,c, d);
    b = 1;      
    printf("a= %d b=%d c=%d d=%d\n",a,b,c, d);
    c = 6;
    printf("a= %d b=%d c=%d d=%d\n",a,b,c, d);
    ss(move(c));
    printf("a= %d b=%d c=%d d=%d\n",a,b,c, d);
}執行 g++  c.cpp && ./a.out
a= 3 b=3 c=3 d=3
a= 1 b=1 c=1 d=3
a= 6 b=6 c=6 d=3
a= 12 b=12 c=12 d=3
上述使用 move semantics 強制將 c 轉化為右值參照的型別傳給函式, 在函式內部改變了右值參照的內容, 因為是指標的關係, 因此它同時也更動到主體(a, b, c)的內容, 而 d 的值不受影響, 是因為宣告成一般的變數, 這是需要特別注意的地方.

2018年9月28日 星期五

計算 IRR 的 Javascript 程式範例

投資專案初期投入資本 12369 圓, 之後持續兩期再投入相同資金 12369 圓, 最後一期將專案賣掉後, 收回資金 37739 圓, 而投資期間回收的各期利潤分別是:  194, 391, 594, 606, 618, 1378, 1379 圓, 試計算該專案的 IRR
// irr.js
maxrate = 100/100 //折扣率上限 100%
minrate = 0.0           //折扣率下限 
C0 = 12369     //初期成本
CT = [-C0, 194-C0, 391-C0, 594, 606, 618, 1378, 1379 + 37739] //現金流量矩陣(CFM)
k = 1000          //防止無限回圈計數器
while (k-- > 0) {
  IRR = (maxrate + minrate) / 2   //取折扣率上下限的中間值估算 IRR
  NPV = 0.0 //因 NPV 與 r 成反比關係, 為了讓 NPV 下降需提高 r, 反之使 NPV 上升則降低 r
  for (i in CT) NPV = NPV + CT[i]/((1+IRR)**i)  // 藉由累加 CFM, 計算 NPV
  if ( Math.abs(NPV) < 1e-6 || (maxrate - minrate) < 1e-6)  break;//上下限逼近時離開
  if( NPV > 0 ) minrate = IRR //為了讓 NPV 下降為零, 修正下限以提升 IRR 估算
  else  maxrate = IRR //為了讓 NPV 上升為零, 修正上限以降低 IRR 估算
}
console.log("投資成本=" + C0*3 + "\tNPV=" + NPV + "\tIRR: " + IRR*100 + "%");
執行 js irr.js 看輸出結果:
投資成本=37107    NPV=-0.08050183637533337    IRR:2.547025680541992%
上述 NPV 計算結果雖然不等於零, 但很接近零, 因此說該折扣率(discount rate)非常近似 IRR

2018年9月26日 星期三

投資上的專有名詞 NPV 與 IRR

NPV (net present value), 字面上翻譯成淨現值, 會讓不少人誤解或摸不著頭緒, 如果翻成結算出清價值應該很容易理解, 以最通俗的成語來說就是獲利了結. 投資非一朝一夕可看出成果的, 將投資期間分成 n 個階段, 每個階段必須檢討成果, 清算現金流量 CTn(投資有賺有賠, CTn 有可能是負值), 而每期折扣率(discount rate) r 例如像是賴以為生的機器設備折舊率或是其他無形的資產消耗費率, 都要把它納入計算, 可以理解為實際上獲利一定比看得到的數字稍微要打個折, 也就是說賺的話應該會少賺一點, 如果虧也應該少賠一點, 當投資期 n 愈往後, 會看到該期折扣攤提比率必定跟著拉高, 愈後期的折扣率變高後獲利幅度就隨之縮小, 每個階段 n 代表著時間的流逝, 因此每期的實際淨所得 Cn 必定小於或等於 CTn, 最後將各階段投資的淨所得加總再減去當初投入的資金, 這個獲利了結的數值就是 NPV:
               CnCTn / (1 + r)n
               NPV = Σ Cn - C0
如果將每期的折扣率 r 視為一個大於或等於 0 的常數, 當 NPV = 0 時折扣率 r 就是所謂的內部報酬率 IRR (internal Return Rate), 用數學表示式就是:
               Σ CTn/(1 + IRR)n - C0 = 0
當投資保單, 股票, 債券, 基金, 定存等都換算成 IRR 當作互相比較的基準時, 就可以明確分辨出投資報酬率的好與壞. 以本金 C0 存入定存利率 r 的銀行為例, 每年的配息滾入本金, n 年後將會領到 CTn 的現金, 用此來計算定存的 IRR
             0 + 0 + ... + CTn/(1 + IRR)n = C0
             IRR = (CTn / C0 )1/n  - 1
由複利計算可以知道
              CTn = C0 * (1 + r)n 帶入就可以得出:
              IRR = r
換句話說, 定存利率 r 就相當於投資定存的 IRR

2018年9月20日 星期四

C++ 的樣板(template)

    在 c++ 使用 template 接角括號 < > 緊接著類型及輸入參數, 例如:
               template <class T>  
               T functionName (T passthrough) { 
                     return passthrough; 
                }
               template <class T>
               class  className {
                        // ...
               };

當樣板作用在一般函式上時, 就是把該函式當樣本(函式樣板), 如果作用在類型身上, 等於把整個類型當樣本(類型樣板), 在樣板角括號 < > 裏面, 很像是函式小括號 ( ) 的輸入參數的宣告方式, 在程式裡引用時也跟函式 ( ) 呼叫(invoke)的方式非常類似, 只不過用角括號 < > 作出區別並傳遞參數, 因此可以把它當成樣板函式. 樣板函式的通用型輸入參數(generic type) 使用 class 或 typename 來宣告範本識別字, 例如 <class T> 其它類型(int, long, float ...)就跟變數的宣告方式沒兩樣, 例如 <long  L, float F>
     在程式碼呼叫(invoke)樣板函式: 函式名稱 < >( ) 時, 其實是由編譯器(compiler)事前將函式的輸出入類型由樣板函式的角括號 < > 內的輸入類型先加以取代, 之後才編譯程式碼. 因此有了樣板函式, 編譯器可以針對不同輸出入參數的類型自動產生程式碼(函式), 免去重複編寫的煩瑣問題.
     #include  <iostream>
     using namespace std;
     template <class T> 
// 啟用通用型樣板符號 T, 緊跟著樣板函式
     T  f(T  in){ // 把 f 函式當樣板使用, 輸入參數是 T  in, T 是 type, in 是輸入變數
          return in; // passthrough
      }
      int main( ) {
          cout << f <const char*>("Hello\n"); //呼叫樣板函式傳字串: f<type> (variable)
       cout <<   f
<float> (3.14159); //呼叫樣板函式傳浮點數:f<type> (variable)
        cout <<   f
<const char*> ("\n"); //呼叫樣板函式傳字串: f<type> (variable)
           cout <<   f
<int> (3);  // 呼叫樣板函式傳整數: f<type> (variable)
        cout <<   f
<const char*> ("\n"); // 呼叫樣板函式傳字串: f<type> (variable)
      }
使用 template < > 有幾個重要觀念要建立:
1. 他是編譯器用的程式碼範本(或樣本)
2. 樣板函式的角括號  < > 傳遞的是輸出入的類型(type)參數, 一般函式 ( ) 傳遞的參數只有輸入變數
3. 呼叫樣板函數要同時用 <type> (variable)  分別傳遞兩種參數
當使用樣板函式時, 程式碼可能會變得難以閱讀, 因此初學者應熟悉之後才運用

2018年9月18日 星期二

美元保單的眉角在哪裡?

最近身邊的債券贖回, 多了一大筆的美元, 想放定存又覺得利率太低. 理專推薦美元保單, 共兩年期的繳費, 年初繳費, 年末出利息, 次年初利息繼續滾存, 宣告利率是 3.85%, 看利率也許還不錯, 但會不會後悔呢? 先寫個  c++ 用複利試算一下:
#include  <stdio.h>
#define   var   auto
int main() {
var interest = 0.0385;  // 年利率
var x = 57775.3;          // 期本金
var sum  = 0.0;         // 本利和
var cost = 0.0;         // 成本
var rate = 0.0;         // 必須有小數點, 否則 c++ 會當成整數造成計算錯誤.
var year = 20;          // 年
var count= 2;           // 繳期
    for (var k = count; k<= year; k++) {
        sum  = 0;           // 本利和初始值
        cost = 0;           // 成本初始值
        for ( var j=0 ; j < count ; j++) {
            rate = 1;       // 本+利率初始值
            for( var i = 0 ; i< (k - j) ; i++ ) {
                rate = rate*(1 + interest);// 複利
            }
            cost = cost+ x; // 本金累加
            sum  = sum + x * rate;// 本利和累加
        }
        printf("year+2=%3d    cost=%4.1f    \tsum=%4.1f \t%4.1f\%%\n", k+2, cost, sum, 100*sum/cost);
    }
}
輸出結果:
year+2=  4    cost=115550.6        sum=122309.3     105.8%
year+2=  5    cost=115550.6        sum=127018.2     109.9%
year+2=  6    cost=115550.6        sum=131908.4     114.2%
year+2=  7    cost=115550.6        sum=136986.9     118.6%
year+2=  8    cost=115550.6        sum=142260.9     123.1%
year+2=  9    cost=115550.6        sum=147737.9     127.9%
year+2= 10    cost=115550.6        sum=153425.8     132.8%
year+2= 11    cost=115550.6        sum=159332.7     137.9%
year+2= 12    cost=115550.6        sum=165467.0     143.2%
year+2= 13    cost=115550.6        sum=171837.5     148.7%
year+2= 14    cost=115550.6        sum=178453.2     154.4%
year+2= 15    cost=115550.6        sum=185323.7     160.4%
year+2= 16    cost=115550.6        sum=192458.7     166.6%
year+2= 17    cost=115550.6        sum=199868.3     173.0%
year+2= 18    cost=115550.6        sum=207563.2     179.6%
year+2= 19    cost=115550.6        sum=215554.4     186.5%
year+2= 20    cost=115550.6        sum=223853.3     193.7%
year+2= 21    cost=115550.6        sum=232471.6     201.2%
year+2= 22    cost=115550.6        sum=241421.8     208.9%

將理專給的解約金拿出來比對一下, 會發現整個保險金的價值大概都往後順延了 1 年, 第 3 年初才開始有些許的利息出來, 到第 4 年初開始有 3.85% 的利息出現, 換句話說, 先將資金借給銀行無息 3 年, 這3年內不得動用資金否則必定虧本. 當經歷 10 年過後(第 11年初)可能會有 33.22% 的利息, 與計算出來(+2年) 的結果 32.8% 也許相去不遠. 除了把它當成一筆不想動用的資金外, 這例保單還有個壞處是不得請領每年的利息, 必須等到夠本解約後, 本金連同可能的利息才拿得回來, 這解約金裡面其中的回饋金最大的問題可能在宣告利率, 它並非一般銀行的定存利率, 而是保險公司將保險金拿去投資, 當有收益時才分配盈餘生出回饋金, 投資有賺有賠, 收益率自然也有可能是負的, 未來當收益率不如預期恐怕後悔莫及. 而銀行拿本金去運用, 將收益分配給投資者, 天經地義, 只要他們的現金流量足以支付利息就不成問題. 值不值得呢? 考慮清楚, 保單簽收後還有 10 天鑑賞期.
後記:
1. 所繳保險費扣掉一些必要管理費費後多出來的資金是保單價值準備金的基礎, 保單的預定利率是固定不變的, 保單價值金因時間所累積預定利率的增加而跟著上升, 保險價值準備金減去保險管理費才是該筆保險解約金, 每年的解約金管理手續費所佔的比率, 第一年甚至可能高達  10% 以上, 但隨時間增加而遞減, 大部份到第 7 年初就會趨近於零, 這 6 年中間解約的話, 所損失的解約金可是相當高的(保守估計大都高於 1%), 賣保單的保險員通常不會強調解約費率, 他巴不得你早點解約, 讓保險公司因此獲得高額解約金, 因此保單價值準備金愈高以及保險管理費率低才代表著解約金高, 預定利率愈高僅代表著固定收益, 再次重申最重要的一點: 保完險後必須度過零解約費時機才不會虧本.
2. 保單號稱的回饋金是宣告利率先減去預定利率後才乘上保單價值準備金, 這也是美元保單收益率可能大於定存利率的主要因素, 當宣告利率大於預定利率才有可能多出回饋金,務必注意當宣告利率小於預定利率時是否強調回饋金為零(不得為負值), 才不致侵蝕到固定收益.
3. 網路上文章以 IRR (Internal Return Rate) 作判斷保單好壞的指標, 把保單收益率換算成年化報酬率就很容易理解與比較. 當一筆資金 x , 以每年收益率 r (也就是定存利率)增長時, 經過 n 年後, 回收總資產是 y, 其中 r 就是年化報酬率(IRR), 以複利公式來看:
           x * (1 + r)n = y
           r = (y / x)1/n - 1
因此把投資期間所有收入來源相加後除以當初投入的資金, 把它開 n 次方, 最後減去 1 , 當成年化報酬率, 雖不正確但與計算出來的 IRR 相去不遠. 可以作為判斷的指標, 當年化報酬率大於定存利率時才是值得投入的保單.

2018年9月14日 星期五

生活化的程式語言: C++ 基礎摘要

物件導向的程式語言(Object Oriented Program 簡稱 OOP), 端看字面意義就覺得博大精深讓人忘而怯步, 如果改翻譯成"活化的程式語言", 或者更貼近一點用"生活化的程式語言"也許能令人眼睛為之一亮, 有興趣學習. C++ 蘊藏著 OOP 的特性, 經過編譯後本質上仍是 C 語言, 它執行速度之快是最讓人激賞的地方. 以下列出 C++ 常用的關鍵字:
1. 數學四則運算元符號:  +, -, *, ÷, %
2. 邏輯運算元符號: !, &&, ||
3. 比較運算元符號:  ==, !=, >=, <=, >, <
4. 位元運算符號: ~, &, |, ^, > >, < <
5. 指定(assign)運算符號:  =, ++, --, +=, -=, *=, /=, %=, &=, |=, ^=,  >>=, <<=
6. 繼承單冒號  :
7. 類型或整體區域引用雙冒號  ::
8. 優先或函式引用小括號  ( )
9. 陣列或向量引用中括號 [ ]
10. 區塊敘述大括號 { }
11. 物件引用句號  .
12. 指標引用箭號 ->
13. 區塊陳述與指令: if( )  { } else { }, switch( ) { case: }, while( ) { }, do{ } while( ), for( ){ }
14. 流程或迴圈控制: break, continue
15. 條件判斷式  ( ) ? true-expression : false-expression
16. 所有變數名稱都必須宣告資料型態(type), 包含函數(函式名稱也是變數之一), 如果變數使用 = 初始化時, 通常資料型態可以根據上下文推斷出來, 因此可以宣告成 auto 資料型態讓編譯器代理. C++ 內建數值型資料型態有 unsigned, char, string, int, long, long long, float, double, long doublet, bool
17. 新資料類型使用"class 新名稱"來宣告, 它是類型化身(instantiate)的來源, 類型新名稱之後可以用冒號 : 繼承父類型, 如要開放給繼承者使用父類型的方法, 需使用公開繼承關鍵字 : public 這樣化身之後才能使用所繼承的方法. 在類型區塊內所定義的變數名稱(variable)即為化身後的物件成員, 而定義的從屬函式(member function)特稱方法(method), 與類型同名稱的函式稱為建構法, 若同類型名稱前面再加上漂浮線 ~ 的函式則稱為解除法, 執行建構法將會產生物件的分身. 新類型經由建構法將成員組成新物件, 化身分身 (instance), 因此一旦宣告新類型名稱, 它就成為新的資料型態(type).
18. class 新類型名稱 { } 區塊內敘述摘要:
       成員宣告   變數型態  變數名稱 ;       
                          函數型態  方法名稱 (輸入資料型態列表, ... ) { }
       友類認同列表   frind class
       私密區域   private:
       保護區域   protected:
       公開區域   public:
       建構法       新類型名稱( ) { }
       解除法       ~新類型名稱( ) { }
19. 在類型(class)的區塊 { } 裏面, 若無特別指定, 內定(default)就是私密區域(private:), 它會被隱藏起來, 只有該類型本身可以直接存取, 但友類(friend class)或友方(friend function)經由 friend 的宣告獲取私密區域成員的存取權. 而繼承類型會繼承保護區域的成員, 但不包含私密區域的成員, 並且要用關鍵字公開繼承(:pupblic)後, 類型繼承者的分身才見的到所繼承的方法. 公開區域則是開放分身在類型的區域外部存取成員, 因此透過公開方法間接在類型內部進行私密區域成員的存取是合乎規定的.
20. 新類型建構法所回傳的是物件分身(instance), 若使用前置關鍵字 new 的話則只傳物件分身的指標(pointer of instance), 這樣可減少大量的物件複製的動作. 物件使用句點 . 引用成員, 但指標必須用箭頭符號 -> 來指定成員,  new 的作用在於動態配置一塊記憶體作為分身的棲息地, 因此之後要使用 delete 將它移除才可回收再利用. 否則會造成記憶體缺憾(memory leak).
21. 在類型的方法裏面, 可以用關鍵字 this, 它是一個指標(pointer), 若區域內的變數名稱與類型的成員名稱相同時, 該變數在此區域內只能用 this 指標加上鍵號 -> 來加以區別. 當方法用  return  this; 時就等同本身的指標, 分身透過本身的方法回傳本身指標時, 後續可以用號鍵 -> 來鏈接其它方法接序執行. 如果分身的方法執行到最後用類型建構法傳物件, 等同另起爐灶複製(clone)出該類型的分身. 雖然會有大量的複製動作, 但後續也可以用句號 . 方便鏈接其它方法接序執行.
22. 函式摘要
        函式透過函式宣告:   輸出型態 名稱(輸入型態列表, ... ) {  } ,之後引用 名稱(參數) 觸發執行
        函式執行到最後的傳回值(return)稱為函數
        如果要重新指定運算元符號的函式, 可以使用前置  operator 宣告新的運算(函)式
        函式針對不同的資料輸入型態,使用相同的名稱重複宣告,這個動作稱為加載(overload),
        新類型的建構函式(方法)也可根據輸入的參數不同,用相同名稱 overload 方式分別處理
23. 所有敘述或宣告結束後要使用分號 ; 作結尾 , 包含新類型 class 宣告的大括號結尾 { };
24. 單行註解   //
25. 多行註解  /*            */
26. 如何了解 C++ 四種變數宣告的不同用義: 變數, *指標, &別名, &&右值參照, 指標/別名/右值等變數用於大量記憶體傳遞時無需複製內容, 只用指標互相傳遞. &別名/&&右值參照都會指向特定目標,右值指向暫存區的內容, 當輸入或輸出參數佔用記憶體資料量龐大時, 用指標( *, &, &&)來傳遞資料因無需複製內容,因此變得簡潔又有效率.
      變數: variable name
      指標: variable pointer  *
      別名: alias name   &
      右值參照: rvalue reference &&
後記:
1.  virtual function 空殼函式, 宣告在類型內的函式, 可以讓繼承者自行改寫(override)的函式.
2. 抽象(abstract)類型是內含一個空殼函式指定為 0 的類型, 用於繼承但不得做為物件化身
    // abstract class
    class Abstract {  virtual  void _( ) =0;    }
3. 定義新類型除了用 class 外, 也可以用 struct, 差別在於 class 內定是私密區域(private:), 但 struct 內定是公開區域(public:)
4.  c++ 依照將中小大括號  [ ] ( ) { } 順序組合起來就是 lambda function(希臘字母 λ 函式), 中括號 [ ] 用於捕捉函式區塊內的變數, 將它們塞進 λ 函式加以運用. λ 函式 ( ) { } 就像一般的函式宣告, 把輸入參數放在小括號裏面. 大括號 { } 內容就是 λ 函式所封裝的程式碼簡稱 closure. 如果在 c++ 程式開頭處將 var 定義(#define)成 auto, 再將 function 定義(#define)成 [ ], 就可以讓它長的像是 Javascript, 這種定義(即使合法也)應儘量避免放在函式的輸入變數或輸出函數上, 以免造成誤解, 用 c++ 寫一個 λ 函式接收函數右值的程式範例:
    #define   var  auto        // typedef  auto  alternative
    #define   function  [ ]   // capture nothing to act as a normal JS function
    #include <stdio.h>
    float  f(float  &&temp )  { //   C(celsius) to F(fahrenheit)
           var   lambda = function (var  x) { return x*9.0/5+32; } ; // define  λ(x) = 9x/5 +32
           return  lambda(temp); // call  λ(x)  to transform  x value, then return
    }
    float   c( )  {   return 30;  } // return temperature in C
    int main( )    {
          printf("f( a( ) ) =  %f\n", f( c( )  ) );
    }

2018年9月8日 星期六

用 Kotlin 寫一個簡單的橢圓曲線密碼 ECC

來看一個橢圓曲線(elliptic curve)與直線(line)的聯立方程式,已知兩個交點分別是 P(xp, yp)及 Q(xq, yq),直線斜率是 s
            y2 = x3 + ax2 + bx    --- (1)
            y   = s(x -xp) + yp     --- (2)
先假設 P 與 Q 是兩個不同點,通過這兩點的直線斜率 s 可以很容易計算出來:
            s  = (yp - yq) / (xp - xq)    --- (3)
將第(2)式代入第(1)式得到
          (s(x - xp) + yp)2 = x3 + ax2 + bx     --- (4)
我們已知上述聯立方程式應該有 3 個解,我們目的就是要解出最後的第 3 交點(解),假設它是 R(xr, yr), 這 3 個交點的 x 座標分別是 xp, xq, xr, 正好是方程式的解,換句話說這 3 個解一定是上述聯立方程式的根,因此(4)式就可以寫成:
          (x - xp)(x - xq)(x - xr) = 0   --- (5)
將第(4)式與第(5)式分別展開,但只需關注 x2 項的係數就可,無需全部展開:
            (a - s2)  x 2       =        - (xp +  xq  + xr)  x2        --- (6)
比較係數得出 xr 的關係式,加上第(2)與(3)式,就可以定義出一個 ECC 的加法運算法則(因為 P + Q = - R),經此推導.可知第 3 交點位置在 R(xr, yr)
           s  = (yp - yq) / (xp - xq) --- (7)
          xr = s2 - xp - xq - a           --- (8)
        -yr = s(xp - xr) - yp            --- (9)
可是當  P 與 Q 重疊時(dual root), 導致第(7)式的斜率無法運算,兩個點重疊(double)的斜率其實就是橢圓曲線的切線斜率,因此將式(1)分別對 x 作微分並移項得到
           dy/dx  = (3 x2 + 2 a x + b) / 2y  --- (10)
將切點(xp, yp)代入公式就可算出切點的斜率,因為此時 P 與 Q 點重疊(xr, xq) = (yr, yq),我們可以定義出所謂的加倍運算法則(因為 P + P=2P = -R' 就是加倍的意思),只要修正用橢圓曲線的切點斜率(等於定義了兩個交點在同一位置)去取代直線斜率,這樣就可計算出另外第 3 交點的位置在 R'(xr, yr)
             s = (3 x2 + 2a x + b) / 2y      where  (x, y)=(xp, yp)   -- (11)
           xr = s2 - 2xp - a              --- (12)
          -yr = s(xp - xr) - yp        --- (13)
綜合上述,我們在橢圓曲線與直線交點的基礎規範下,進行 P + Q = -R 的運算,可以用加法程序(在第 7, 8, 9 式)或加倍程序(在第 11 ,12, 13 式)來計算出第 3 交點 R(xr, yr).其中加法程序是用在兩個不同點的相加, 而加倍程序則用在相同點的累加,只要知道一個交點,我們稱為之基準點(G),先透過加倍程序,找出其 2 倍(2P)座標點(也就是2G),利用加法程序將 2 倍座標點(2G)與基準座標點(G)相加(add)算出 3 倍座標點(3G),再利用兩倍的兩倍,累加計算出 4 倍座標點(4G),  ... 以此類推得到 n 倍基準座標點(nG).這個 n(稱為 scale) 倍數的座標點,利用的是倍加原理(double and add)的疊代運算逐漸產生新的座標點,但有一點要注意的是當兩個座標點累加去計算出另一座標點的時候(不管是 P + Q = -R 或是 P + P =2P),我們不是要曲線與直線的交點座標 R, 而是要它鏡像點的座標 -R,上面聯立方程式推導的(xr, yr)是交點的座標,因為在 y 軸上,交點與其鏡像點座標是互為反數的關係,因此我們只要將上述推導出的 y 軸座標值轉成負數就可,這就是上述式子中用 -yr 的原因,因為它代表了座標點的位置. 用 Kotlin 語法加上它有 operator overloading 的特性,可以寫出很容易明瞭的程式:


import java.math.BigInteger // 大數運算先要載入 java.math.BigInteger 的模組:
class Curve383187 (key: BigInteger = BigInteger.ONE) { // Curve383187 :: y2=x3 + ax2 +bx    mod   p
    private val p = BigInteger("2").pow(383).subtract( BigInteger("187") ) //  p = 2383 - 187
    private val a = BigInteger("229969") // a = 229969
    private val b = BigInteger("1")   // b=1
    private var x = BigInteger("5") 
    private var y = BigInteger("4759238150142744228328102229734187233490253962521130945928672202662038422584867624507245060283757321006861735839455")
    init {       //       key * G(x,y)
            if (key != BigInteger.ONE)    scale(key, x, y)
    }  
    private infix fun BigInteger.mod(another: BigInteger)   : BigInteger { return rem(another)            }   // %
    private operator fun BigInteger.plus(another: BigInteger)  : BigInteger { return add(another) mod p      }// +
    private operator fun BigInteger.minus(another: BigInteger) : BigInteger { return subtract(another) mod p }// -
    private operator fun BigInteger.times(another: BigInteger) : BigInteger { return multiply(another) mod p }// *   
    private operator fun BigInteger.div(another: BigInteger)   : BigInteger { return multiply(another.modInverse(p)) mod p } // ÷
    fun isPoint() : Boolean {    return    y * y == x * x * x + a * x * x + b * x  } // y2 = x3 + ax2 +bx
    operator fun times(n: BigInteger) : Curve383187 {
        var c = Curve383187( )    // new object
        return   c.scale(n, this.x, this.y)   // return c object
    } 
    private fun scale(n: BigInteger, Gx: BigInteger, Gy: BigInteger ) : Curve383187 {
       var k = 1024               // number of bits
       val num = n  mod  p  // Double And Add algorithm to scale G(x,y)  
       if (num == BigInteger.ZERO) { println("Error, scale num is 0");   return this }
       val big3=BigInteger("3")
       val big2=BigInteger("2")
       while( k-- > 0 )     if ( num.testBit(k) )  break               
       x = Gx;  y = Gy            // initialize P(x, y) = G(x,y)
       while( k-- > 0 ){   // Doubler slope = (3x2 + 2ax + b) / 2y 
          var xp = x                  // store  x  to caculate  2P(x, y) = -R'(x, y)    
          var s = (big3 * x * x + big2 * a * x + b) / (big2 * y)
          x = s * s - big2 * xp - a  //    R'.x = s2 - 2xp - a
          y = s * (xp - x) - y             //  - R'.y = s(xp - x) - yp
          if ( num.testBit(k) ) {    //   P(x, y) + G(x,y) = - R(x, y)
               s = (y - Gy )/( x - Gx)   //   Adder slope = (y - G.y) / (x - G.x)
               x = s * s - x - Gx - a      //   R.x = s2 - x - G.x - a
               y = s * (Gx - x) - Gy      //  -R.y = s(G.x - x) - G.y
           }  
        }       
        if (x < BigInteger.ZERO) x = x + p mod p
        if (y < BigInteger.ZERO) y = y + p mod p
        return this // return self object
    } // end of scale
    fun listpoint() {
        println("\n\tx="+x.toString(16)+"\n\ty="+y.toString(16))
    }
}// End of class Curve383187
fun main(args:Array < String > ) {  
   var keyA = BigInteger("9").pow(87).subtract(BigInteger("65") ) // Alice pickup a key
    var keyB = BigInteger("4").pow(32).subtract(BigInteger("10") ) // Bob pickup a key
    var key  =  keyA*keyB         // Nobody knowns this share key
    var pubA =  Curve383187(keyA) // Alice send public key to Bob
    var pubB =  Curve383187(keyB) // Bob send public key to Alice
    var keyECC=  Curve383187(key) // Caculate the ECC of the secret to double check
    var Alice= pubB * keyA        // Alice use self key to decode pubB sent from Bob
    var Bob  = pubA * keyB        //  Bob use self key to decode pubA sent from Alice
    pubA.listpoint()  ; println("Public@ECC(x,y) from Alice key:\n\t"+keyA.toString())
    pubB.listpoint()  ; println("Public@ECC(x,y) from Bob key:\n\t"  +keyB.toString())
    keyECC.listpoint(); println("Share @ECC(x,y) from Secret key:\n\t"+key.toString())     
    Alice.listpoint() ; println("Alice key decode:")
    Bob.listpoint()   ; println("Bob key decode:")                               
    println("\nAlice on curve ? "+Alice.isPoint()+ " Bob on curve ? "  +Bob.isPoint())
}
輸出結果:
    x=2a4c00dc2e8acb66b527ae0cddaeb9b373dfe0e7b3ef9b86e38b7b39d55e3f24f25cc5608a45e66acc96eddd40578daf
    y=5c18f88677e650fa6cad33a14a961adc0db96739fcfb39c7b600a3e106ed0aafe705271408a522dd582ff6ce9d8fe37f
Public@ECC(x,y) from Alice key:
    104495676331778315966103878903450701989608781073244439950619431748912396904023371704

    x=2989c08b718a4fc3bfe8cc3da96e5d7d975f6c58802700eb2f5ca018e70bbdb3d5c03671053eed8ee94adaed23dfcac0
    y=37eb8fa79a5ee1caa076518b1abf3249e5fd560a95acf3ad6e22a27055d51c9dcb974988c9a2f99b6f68afcece7c7570
Public@ECC(x,y) from Bob key:
    18446744073709551606

    x=381dcd3a1bdea34760212d276c7d21940d315a4d547af3c520aba80a2ff4e38e0e548dd9d0038c1ce5c3eb36acf6960b
    y=3491be994e90f4e9b28352b5c0495c5f395dba99fe2fa870553351be77e363c67392e3c3feb2fed711b5d187c63afbbb
Share @ECC(x,y) from Secret key:
    1927604998101503106558917489994263425013278054390180757461516411290410360277764861810797121646108156624

    x=381dcd3a1bdea34760212d276c7d21940d315a4d547af3c520aba80a2ff4e38e0e548dd9d0038c1ce5c3eb36acf6960b
    y=3491be994e90f4e9b28352b5c0495c5f395dba99fe2fa870553351be77e363c67392e3c3feb2fed711b5d187c63afbbb
Alice key decode:

    x=381dcd3a1bdea34760212d276c7d21940d315a4d547af3c520aba80a2ff4e38e0e548dd9d0038c1ce5c3eb36acf6960b
    y=3491be994e90f4e9b28352b5c0495c5f395dba99fe2fa870553351be77e363c67392e3c3feb2fed711b5d187c63afbbb
Bob key decode:

Alice on curve ? true Bob on curve ? true

2018年9月6日 星期四

曲線與直線方程式的聯立方程式解


假設有一組聯立方程式,  由一個個曲線(立方)方程式, 及直線(一次方)方程式組成:
                  y = g( x ) =  x^3  + a *x + b            --- ( 1 )
                  y = h( x )  =  c*x   + d                      --- ( 2 )
將(2)式代入(1)式 , 就可以算出聯立方程式的解, 解出的 x (根)應該有 3 個,將上面兩式在 x, y 平面作圖. 其中的交點假設為 P', Q', R' 共 3 個點, 也就是說曲線與與平面上任何一條直線的交點應該都會有3個, 現在將第 1 式修正改成 y^2 ,那與直線關係式又會如何呢? 也就是:
                  y^2 = g( x ) =  x^3  + a *x + b       --- ( 3 )
                      y = f( x )  =  e*x   + f                    ---( 4 )
將(4)式代入(3)式,可見仍是一個 3 次方程式, 因此還是會解出 x 另外 3 個不同的根,而且因為
(-y )^2 = y^2  的關係, 解出的 3 個根,假設是 (x1,y1),(x2,y2),(x3,y3),一定可以發現 (x1, -y1), (x2, -y2), (x3, -y3) 必定是曲線方程式(3)的解,但不一定滿足方程式(4), 如果我們定義上述3個交點分別稱為 P, Q,R, 那其相對的鏡像點(因為 y 座標軸的值是互相對稱的關係),因此(x1,-y1)定義為 -P, 同理將 (x2,-y2) 叫作 -Q.此外定義一個零點叫作 O,它目的是當本身與其鏡像點相加時會造成消失(y=0)的狀況
                     P + (-P ) = O          --- (5)
                     Q + (-Q) = O          --- (6)
另外將 P + Q 定義成等於 -R , 目的是在於將曲線上兩點相加必須在同一曲線上(的場域), -R 的鏡像點就是 R, R 與 -R 都是曲線中的一員, 且 R 又剛好是與直線的第3個交點(x3,y3),因此:
                     P + Q    = -R           --- (7)
                     R + (-R) = O           --- (8)
將 (7) 與 (8) 相加
                     P + Q + R + (-R ) = -R + O
兩邊同時消去  -R 就可以推導出
                     P + Q + R = O        --- (9)
也就是說當曲線與任一條直線相交的3個點,將它累加在一起時就會等於是零點.

而 O 是什麼呢? 當我們將曲線與直線個交點之一拿來與其鏡像點畫一條直線時,剛好就是平行 y 軸的直線, 我們說直線與曲線會有 3 個交點,顯然這種直線找不到第3個交點,但我們強制將它定義成零點,也就是說這類型直線的第 3 交點就是零點,而且 -O = O 必需符合零的運算法則,且必須滿足是曲線中的一員,應該說它沒有位置點,只是將它稱為零點,規則之一(7)式說當兩點相加時等於第3點的鏡像點, 令 O = 0 ,式(9)就可以很容易推導出這3個交點之間的交換的關係:
                     P + Q = -R
                     Q + R = -P
                     R + P = -Q
接著來看當兩個相加的點是同一點時,也就是說曲線有兩個根在同一點,其實就是該曲線的切點(因為當 Q 逼近 P 時,兩個點(根)就匯聚成一點, 等於是方程式的一次微分點). 該條切線方程式擔任了產生器的角色(因為切點 G, 其兩倍數 G + G = 2G),再由上面推導得知這條切線與曲線第3個交點之鏡像點正是這2倍數(double)點的位置所在地,有點像是時空轉換,將一點自我累加就會轉換到另一交點的鏡像位置.於是一旦知道有一點 G 滿足曲線方程式, 透過該點的切線方程式就可以找到 2 倍點, 也就是 2G(因為 G + G = 2G), 那 3 倍點 = 2G + G,  ... 以此類推就可得出共多點的位置,而 O 零點也就是 0G 加上 1G, 2G, 3G, 4G, 5G ...  就可以產生一條秩序,從而建構出橢圓曲線密碼學(ECC)重要的運算規則


用 Kotlin 將一小段文字使用 RSA 加/解密

不囉唆, 直接看原始碼:
import java.math.BigInteger
fun main(args: Array < String > )  {
  fun String.toStringHEX() = this.toByteArray(Charsets.UTF_8).map( { String.format("%02X", (it.toInt() and 0xFF)) }).joinToString(separator="")
  fun String.fmStringHEX() = ByteArray(this.length/2, { this.substring(it * 2, it * 2 + 2).toInt(16).toByte()     }).toString(Charsets.UTF_8)

  var sd="7E134469882EC2FA67B3142B96240043BDDCB946956773301736A81E45B5378CB6C11506C6BD2B4367E200B5D31C9F05C81220C43E9E7F405A3FB64C951F559B00F7F1586AE251E1EFE6793B43FBF2A283883C4843FF593ADD0273ED2786545AE42539911A9415AB41686E367A838E4CD4F6C6A2AC5B41B821651DD857DB29110DDAF7C2044B1227696E5F1EEC6FCAECD021DC4AD318937DAF58130B5594308D8EE7F6CCBB6CEE7B9B71CC2002FE342609E0717EB4FA67B61D55D11DFBABC6933FDA203400405E71CE87450968216A8C883187B562207216F213B12931F8D8AD4DF2361BC3FC404C8B088F33C0F66B7CAD2EDD1D796182DDC51153B34EBE7335"
  var sn="00CCCC68E135CDAD3A1BA300BBE2AEA65BD45F73EAD963A4C087E1E4CF60050F0E9077C59F62A98BB1DF20B371756C0DF310056FD678FF11011EB486C683B6F1F5C66A93C21F532F5E7C6EF2AABBCD681F5C013E1437C28268CCCEF01D83C4DC7E462AB06D103EBE5DDDB88982000D0D113F8520AB16679B2834517E6D1605733B6E7486E9629F9DD8E366D16A1E7D6AC836795C1889B74319816550ADB4EB8E5EAE805217E28E8FCA61496AE4DC7FB5E5C2A03E77FEAFCCFEFFB1A6C9EE96F8E239F2912665BEC90E3D960A997172E0C30C12ABEA96BBEAD59DD65506619099AA1829AB3494B6A8FE694296CB5AB08961DD11FAC39A80026CB39E6E287D1ABBC1"
  var se="10001"
  val 明文 = "This is a chinese 這是中文"
  var sx=明文.toStringHEX()
  println("原文: "+明文);
  println("轉成暗碼: "+sx);
  var x=BigInteger(sx,16)
  var n=BigInteger(sn,16)
  var d=BigInteger(sd,16)
  var e=BigInteger(se,16)
  var m=x.modPow(d,n); // d, n private key to encode
  var z=m.modPow(e,n); // e, n public key to decode
  println("\n加密暗碼: "+m.toString(16));
  println("\n暗碼解密: "+z.toString(16));
  println("還原明文: "+z.toString(16).fmStringHEX() )
}
輸出結果:
原文: This is a chinese 這是中文
轉成暗碼: 546869732069732061206368696E65736520E98099E698AFE4B8ADE69687

加密暗碼: bb3f1f7d5b45233c4b01ee9fdc378ead99da02b99cd4a8c1196403808ac37588468d4885c49a3a4b1f6bb71cec259dce3a4bfcbc4ae8023f59434f3f6a393670a13d914cdbfd8eea28c4e49f2653b99b22a4156e0a9891ec33e887b4a6e45e053ee91da29f7ea9d53941d00be51c613b72aa38820879ba256d13d33e08942d7abda10e322452543c62667b404589480b745cdcb4bde34e54eb09f61fc71d3d20c1edeec2cee6e7515d7802f9236b6af48ea25edf0de3de7edb960d2d05f1bbb322c820c1fa3db84c7cb2b8b4a3e079a82b2479a23d0e184bb26618a4daba83370f96c78d4aa4ff9785f2bb884b54e4f59f33942ba042f3b1827682d6c235cfb9

暗碼解密: 546869732069732061206368696e65736520e98099e698afe4b8ade69687
還原明文: This is a chinese 這是中文

2018年9月3日 星期一

簡單的 Javascript ( async 與 await)

寫一個回傳 Promise  物件的 Javascript function
// a.js  a Promise function example 
const childprocess = require("child_process");     //  i.e.   /usr/bin/bash
function nextTask(msg) {   console.log(msg+"--- Ok ---")            }
function errTask(msg)    {   console.log(msg+"--- Warring! ---")  }
function bash(cmd) {
    console.log("bash "+cmd)
    return new  Promise(  (ok,ng) => {
          childprocess.exec( cmd, (err, stdout, stderr) =>  err ?  ng(stderr) : ok(stdout)    );
    })
}
async function cmd(opt ) {
    console.log("副程式開始")
    var shell= bash(opt)   // return a Promise
    await shell.then(nextTask).catch(errTask);
    console.log("副程式結束")
}
console.log("主程式開始")
cmd("ls -al").then(  console.log("主程式結束") )
// end of file

執行 js  a.js 看輸出結果:
主程式開始
副程式開始
bash ls  -al
主程式結束
total 12
drwxrwxr-x 2 mint mint 4096  9月  4 14:04 .
drwxrwxr-x 4 mint mint 4096  9月  4 13:05 ..
-rw-rw-r-- 1 mint mint  686  9月  4 14:04 a.js
--- Ok ---
副程式結束

如果將上述 Javascript 內容(a.js)做一些修改, 移除  await 之後,再執行一遍,整個過程變成:
主程式開始
副程式開始
bash ls  -al
副程式結束
主程式結束
total 12
drwxrwxr-x 2 mint mint 4096  9月  4 14:05 .
drwxrwxr-x 4 mint mint 4096  9月  4 13:05 ..
-rw-rw-r-- 1 mint mint  680  9月  4 14:05 a.js
--- Ok ---

可以看到整個非同步(不定時)運作的過程,由此可以了解到 async/await 運作方式, async function 一定回傳一個稱為 Promise 的物件, 而 then( ) 是它的一個從屬函式(member function), await 就只能放在 async function 裏面, 用來等待,作為同步之用, 而 node js 的 child_process.exec 是非同步在運作(也就是說其 callback function 是隨後才會執行)的.要運用 async/await 就不得不瞭解 Promise, 因為 await 就是為了等待特定 promise 完成,才會繼續下一個動作, 但在主程序中的 async function 僅傳回 Promise 就會繼續下一步動作,並不會因 async function 內的 await 就被暫停而隔離(block),簡單就字面意義來說 async function 就是非同步的功能函式,如果要一起執行多個非同步函式又要等待某個非同步函式的完成,應該使用 Promise.all( [  ,   ] ) 平行處理非同步函式,避免在 await 的過程執中阻擋(block)了其它非同步函式的運作,將上述 async function 稍作一點修改:
      async function multicmd(  ) {
           console.log("副程式開始")
           var  shell1= bash("ls -al")  // return a Promise
           var  shell2= bash("pwd")   // return another Promise
           await shell1.then(nextTask).catch(errTask);
           await shell2.then(nextTask).catch(errTask);           
           console.log("副程式結束")
       }
       console.log("主程式開始")
       multicmd( ).then(  console.log("主程式結束")  )

這是輸出結果:
主程式開始
副程式開始
bash ls -al
bash pwd
主程式結束
total 12
drwxrwxr-x 2 mint mint 4096  9月  5 09:19 .
drwxrwxr-x 4 mint mint 4096  9月  4 13:05 ..
-rw-rw-r-- 1 mint mint  782  9月  5 09:19 a.js
--- Ok ---
/home/mint/work/kotlin
--- Ok ---
副程式結束

最後使用 async/await Promise.all 的完整範例:
const childprocess = require("child_process");
function nextTask(msg) {   console.log(msg+"--- Ok ---")            }
function errTask(msg)    {   console.log(msg+"--- Warring! ---")  }
function bash(cmd) {
    console.log("sh "+cmd)
    return new  Promise(  (resolv,reject) => {
          childprocess.exec( cmd, (err, stdout, stderr) =>  err ?  reject(stderr) : resolv(stdout)    ); // pipe stderr and stdout
    })
}
async function multicmd( ) {
    console.log("非同步副程式開始")
    var promise1= bash("ls -al").then(nextTask).catch(errTask)
    console.log("--- Promise1 ---")
    var promise2= bash("pwd").then(nextTask).catch(errTask)
    console.log("--- Promise2 .. ---")
    var promise3= bash("unknow").then(nextTask).catch(errTask);  
    console.log("--- Promise3 .. ---")
    await Promise.all([ promise1, promise2, promise3 ])
    console.log("非同步副程式結束")
}
console.log("主程式開始")
multicmd( ).then(console.log("主程式結束"))

上面 promise1, promise2, promise3 不一定會依照順序輸出結果, async 用意就是將程序包裝成 Promise, 而裡面的程序若有  await 就會暫停等待結果, 一旦結果完成(呼叫 callback function 中的 reject 或 resolve )接著就會繼續往下執行直到非同步程序整個完成才會脫離 :
主程式開始
非同步副程式開始
sh ls -al
--- Promise1 ---
sh pwd
--- Promise2 .. ---
sh unknow
--- Promise3 .. ---
主程式結束
/bin/sh: 1: unknow: not found
--- Warring! ---
/home/mint/work/kotlin
--- Ok ---
total 12
drwxrwxr-x 2 mint mint 4096  9月  6 09:23 .
drwxrwxr-x 4 mint mint 4096  9月  4 13:05 ..
-rw-rw-r-- 1 mint mint  957  9月  6 09:23 a.js
--- Ok ---
非同步副程式結束




修正 /etc/rc.local 內容

最近改用  Linux mint 19 , 發現 /etc/rc.local 開機時沒有執行,改一下內容,在開頭地方加入一行   #!/bin/bash  並將它設成可執行檔, 就解決了:
sudo chmod +x /etc/rc.local 

2018年9月1日 星期六

使用 SDK tools安裝 Android SDK

1. 到官方網站  https://developer.android.com/studio 拉到最下方下載 Command line tools
 (SDK  tools package 內含 sdkmanager), 我下載 linux 版本 sdk-tools-linux-4333796.zip
(大約 150MB 的檔案). 將它解壓縮後,把 tools/bin 目錄加到環境變數 PATH
      downloadFile=$HOME/Downloads/sdk-tools-linux-4333796.zip
      targetDirectory=$HOME/sdk   
      mkdir    $targetDirectory   &&   cd    $targetDirectory
      unzip    $downloadFile   &&   PATH=$(pwd)/tools/bin:$PATH  &&  echo $PATH
      sdkmanager --list > package.all

2. 查看上述產生的檔案(cat  package.all | more) , 記下所需安裝套件的名稱, 用 "" 括起來傳給 sdkmanager 去下載, 如果要編譯 24.0.0 至少要下載3個套件,安裝完後將會多出 3 個目錄, 將 build-tools/bin 目錄加到環境變數 PATH
      sdkmanager  "platforms;android-24"
      sdkmanager  "extras;google;m2repository"
      sdkmanager  "build-tools;24.0.0" 
      PATH=$(pwd)/build-tools/bin:$PATH

3. 下載 JDK8 可以到官方網站:
http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html
下載完解壓縮後, 我把它把它放在 Android sdk 目錄之下,便於管理(例如  $HOME/sdk/jdk1.8.0_92 )

4. 寫一個 bash script 設定環境變數,將它存檔(sdkenv,)方便日後使用:
      #!/bin/bash
      #sdkenv       
      export  ANDROID_HOME=$HOME/sdk
      export JAVA_HOME=$ANDROID_HOME/jdk1.8.0_92
      export JRE_HOME=$ANDROID_HOME/jdk1.8.0_92/jre
      export PATH=$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$ANDROID_HOME/build-tools/bin:$ANDROID_HOME/tools/bin:$PATH
      export CLASSPATH=$CLASSPATH:$JAVA_HOME/lib:$JAVA_HOME/jre/lib

5. 先執行環境設定 (sdkenv) 再更改預設的 JDK 位置:
.   sdkenv
sudo update-alternatives --install /usr/bin/java java $ANDROID_HOME/jdk1.8.0_92/bin/java 300
sudo update-alternatives --install /usr/bin/javac javac $ANDROID_HOME/jdk1.8.0_92/bin/javac 300

6.將以前寫的 Android 程式解壓縮,進入專案的根目錄查看,如果有 local.properties 檔案, 直接將它刪除.執行環境設定 (sdkenv) 後就可執行  gradlew 去編譯 Android 程式:
       ./gradlew clean
       ./gradlew build
如果無法編譯完成,出現錯誤訊息,可能是 build-tools 沒下載, 查看專案底下 app/build.gradle 檔案敘述內容,用上述步驟 2 利用 sdkmanager 下載相應版本的工具程式