ポコム メモ

目次

Windows11 のレガシー・コンソールでの文字化けを防ぐ

pocom ver.8.17 以前では、Windows11 のレガシー・コンソールで UTF8 文字列を 出力すると文字化けを起こしていました。
原因はUTF8 全角文字列を1バイトずつfputc(ch,fp);の ように書き出す場合があるからです。
さらに TDM-GCC-64 のバージョン10以降の場合、 fprintf("%s\n","utf8日本語");のようにしても文字化けを起こしていました。
文字化けを防ぐにはfputs("utf8日本語",fp);のように書けばよいことが分かりました。

乱数初期化の改良

2017年2月に totoBIG にて別々に購入した5口分が完全に一致する事件が起きました。
https://twitter.com/4DOqRqe5bPw4tUh/status/831900589705433089
『BIGなおとり「宝くじはランダム」は本当か?』
https://business.nikkei.com/atcl/report/16/030300120/030300002/
別々に購入した5口分が完全に一致する確率は約25「溝」分の1です。(25溝=25億×兆×兆)
宇宙誕生よりも稀な現象が起きたから totoBIG に不正がっあったと言われてしまいました。
私はこの事件は疑似乱数のプログラムに仕様上のバグがあったと認識しています。

Version 8.15 までのポコムでは乱数初期化の種は32ビット整数のみなので、
約43億分の1で事件のように出力が完全に一致する可能性があります。
さらに Seed 関数を引数なしで呼び出した場合、time(0) を種にするので、
別々のマシンで同時にプログラムを実行すると事件のように出力が完全に一致する危険があります。

そこで Version 8.16 に以下の機能を加えました。

乱数の内部状態の取りうる種類は 2¹²⁷-1 (≒170澗=170兆×兆×兆)あるので、
マクロ RANDOMIZE を定義すれば事件のように出力が完全に一致する危険はほとんどありません。

Progress( ) と Progress2( ) と OpenMP

ポコム Version 8.16 以降では Progress( ), Progress2( ) は
ウィンドウズモードでもクリティカルセクションで囲めば、並列リージョン内でも
正常に動作します。理由は、マスタースレッド以外ではメッセージポンプや
タイトルの書き換えを呼び出さなくなったからです。
ただし、ウィンドウズモードで OpenMP の場合は旧形式表示の指示をする
マクロ OLD_PROGRESS は無効にされます。

ポコム Version 8.15 以前の場合、ウィンドウズモードで並列リージョンの中で
クリティカルセクションで囲っても Progress( )、Progress2( ) を呼び出すと
マスタースレッド以外からメッセージポンプやタイトルの書き換えをするので
クラッシュしていました。

並列リージョンの中で Progress( ) をクリティカルセクションで囲む例を示します。
例えば、以前のバージョンでは以下のように Progress( ) を並列リージョンの
外に書いていましたが、

 void F87Mth1(void) {
    Int i,z;
    Copy();
    for (z=0;z<Z;z++) {
        #pragma omp parallel for
        for (i=0;i<N;i++) {
            Int j;
            for (j=0;j<N;j++) F87(i,j);
        }
        Progress("#F87Mth(1) ",z,Z);
    }
    PrintSum("Normal multi  thread(1)");
 }

は次のように書き換えます。

 void F87Mth2(void) {
    Int z,n=0;
    Copy();
    #pragma omp parallel for
    for (z=0;z<Z;z++) {
        Int i,j;
        for (i=0;i<N;i++) for (j=0;j<N;j++) F87(i,j);
        #pragma omp critical
        Progress("#F87Mth(2) ",n++,Z);
    }
    PrintSum("Normal multi  thread(2)");
 }

この例の場合、後者の方が速くなります。

もし for (i=0;i<N;i++) for (j=0;j<N;j++) F87(i,j); の実行時間が長くて、
応答と表示が遅くなる場合は Progress2( ) を使います。以下に例を示します。

void F87Mth3(void) {
    Int z,n=0,m=0;
    Copy();
    #pragma omp parallel for
    for (z=0;z<Z;z++) {
        Int i,j;
        for (i=0;i<N;i++) {
            for (j=0;j<N;j++) F87(i,j);
            #pragma omp critical
            Progress2("#F87Mth(3) ",n,Z,m++,N);
        }
        #pragma omp atomic
        n++;
        m=0;
    }
    PrintSum("Normal multi  thread(3)");
 }

AddTo の間違ったプログラム例

以下に AddToIvec() の間違った例を示します。
#define FILE_NAME "Add1.c"
/* AddToIvec() の間違った例 */
#if defined(PDEF_H)
 INTF(N,2,5,"Size of X")
 IVEC(X,2,N,"Integer vector")
 PROC(Test,"Test")
 AUTO("Test")
#else
 #define NO_LIST
 #define NO_OPEN
 #define STDERR_STDOUT
 #include "../pocom.h"
 CHECK_FILE_NAME
 void Test(void) {
     Int i;
     for (i=0;i<N;i++) AddToIvec(X,i);
     Print("X");
 }
#endif
結果:
At AUTO(2), Error occurred!
Range N == 5
Test
Print("X")
       ^
X's size N:==5 Range violation
意図した結果にならないのは AddToIvec() が変数 N を変更するからです。 意図した結果を得るには以下のように for 文が変数 N を参照しないようにします。
#define FILE_NAME "Add2.c"
/* AddToIvec() の正しい例 */
#if defined(PDEF_H)
 INTF(N,2,5,"Size of X")
 IVEC(X,2,N,"Integer vector")
 PROC(Test,"Test")
 AUTO("Test")
#else
 #define NO_LIST
 #define NO_OPEN
 #define STDERR_STDOUT
 #include "../pocom.h"
 CHECK_FILE_NAME
 void Test(void) {
     Int i,n=N;
     for (i=0;i<n;i++) AddToIvec(X,i);
     Print("X");
 }
#endif
結果:
 X
  0  1  2  3  4
以下の二つプログラムのように N をマクロにしても意図した結果を得られます。
#define FILE_NAME "Add3.c"
/* AddToIvec() の正しい例 */
#if defined(PDEF_H)
 #define N 5
 INTF(SizeOfX,2,N,"Size of X")
 IVEC(X,2,SizeOfX,"Integer vector")
 PROC(Test,"Test")
 AUTO("Test")
#else
 #define NO_LIST
 #define NO_OPEN
 #define STDERR_STDOUT
 #include "../pocom.h"
 CHECK_FILE_NAME
 void Test(void) {
     Int i;
     for (i=0;i<N;i++) AddToIvec(X,i);
     Print("X");
 }
#endif
#define FILE_NAME "Add4.c"
/* AddToIvec() の正しい例 */
#if defined(PDEF_H)
 #define N 5
 FIX(N)
 IVEC(X,2,FIXN,"Integer vector")
 PROC(Test,"Test")
 AUTO("Test")
#else
 #define NO_LIST
 #define NO_OPEN
 #define STDERR_STDOUT
 #include "../pocom.h"
 CHECK_FILE_NAME
 void Test(void) {
     Int i;
     for (i=0;i<N;i++) AddToIvec(X,i);
     Print("X");
 }
#endif
ただし、Add4.c は LSI-C や TurboC 1.5 や TurboC 2.0 のように C++に対応していない古いCコンパイラではコンパイルできません。 FIX(N) でコンパイルが失敗します。

乱数ジャンプの例

乱数のジャンプを行う Jump() 関数の例を示します。
Unif(), BackUnif() を使用しているので、UNIF_STEP も使用しています。
TINYMT64, X86_INTRIN や FAST_UNIF を定義すると結果が変わります。
#define FILE_NAME "rand.c"

#if defined(PDEF_H)

 INTF(N,           3,10,"比較する乱数ベクトルの大きさ")    
 RVEC(UnifF,     305,N, "高速乱数")
 REAL(UnifF7,    305,   "高速乱数")
 RVEC(Forward,   305,N, "最初の N 個の乱数値")
 RVEC(Reverse,   305,N, "逆順の N 個の乱数値")
 RVEC(AfterJump, 305,N, "1000兆ジャンプして戻った後の乱数値")
 REAL(AfterUnif,1719,   "ジャンプを繰り返した後の乱数値")
 PROC(RandTest,         "乱数のテスト")
 AUTO("RandTest")

#else

 #define X86_INTRIN   /* ハードウェア乱数を有効にする */
 #define TIMEk
 #define DBG_SRAND
 #define FAST_UNIFk
 #define SRAND_BYSTR 1 /* 16bit コンパイラで SrandByStr() を有効にする */
 #define JUMP_RAND     /* 16bit コンパイラで乱数のジャンプを有効にする */
 #define BACK_RAND     /* 16bit コンパイラで逆順乱数を有効にする */
 #define NO_OPEN       /* ファイルを開くダイアログを無効にする */
 #define NO_LIST       /* 変数リストの表示を無効にする */
 #define FAST_RAND 2   /* Unif_m(), Rand_m() を有効にする */
 #if defined(LSI_C)
  #define NO_ADD
  #define NO_SORT
 #endif
 #if defined(OLD_UNIF53)
  #if __POCOM__ < 816
  /* Earlier than pocom.h version 8.16(2023-10-01) OLD_UNIF53 has bug. */
  /* 2023-05-13(version 8.12) ... 2023-09-17 */
   #define OLD_UNIF52
  #endif
 #endif
 #include "../pocom.h"
 CHECK_FILE_NAME
 #define JUMP_SEC 1.0
 #define ITER 10000 /* まとめて発生させる繰り返し数 */
 #define K 8 /* 最大値最小値を求める時の繰り返し数 */

const char*Nano(double sec) {
    static char buf[4][256]; char*p; static int sw;
    sw=(sw+1)&3; p=buf[sw];
    sprintf(p,"%7.1f nsec",sec*1000000000.0);
    return p;
}

 volatile double Sum;
 volatile Uint32_t SumU;

 #if defined(TIME)
  #define BENCH(Name,Func,Str,SumB) \
void Name(void) {\
int i,j,k; double min_=1000,max_=0,lap;\
fprintf(fp,Str); SetInterval(0);\
for (k=0;k<K;k++) {\
 ResetLap();\
 for (i=0;GetInterval()==0&&i<INT_MAX;i++)\
  for (j=0;j<ITER;j++) SumB+=Func;\
 lap=LapSec()/i;\
 if (lap<min_) min_=lap;\
 if (k&&lap>max_) max_=lap;\
 fprintf(Stderr,"%2d,",IntervalBits); FlushFp();}\
fprintf(fp," %s %s\n",Nano(min_/ITER),Nano(max_/ITER)); }

BENCH(UnifBench,  Unif(),    "Unif()    ",Sum);
BENCH(Unif_fBench,Unif_f(),  "Unif_f()  ",Sum);
BENCH(RandBench,  Rand(),    "Rand()    ",SumU);
BENCH(Rand_fBench,Rand_f()  ,"Rand_f()  ",SumU);

  #if defined(X86_INTRIN)
BENCH(Rand_hBench,Rand_h()  ,"Rand_h()  ",SumU);
   #if defined(Rand_s)
BENCH(Rand_sBench,Rand_s()  ,"Rand_s()  ",SumU);
   #endif
  #endif
  #undef BENCH
 #endif

 void RandTest(void) {
    int i; double jump_lap=0;
 #if SRAND_BYSTR
  #if defined(OLD_BYSTR)
    SrandByStr("10036"); /*10036,10077,10176,10178,10265,10293,10301,10308 */
  #else
    SrandByStr("123456789012345678901");
  #endif
 #else
    SrandByU32(102); /* 102,103,189,232 Srand()直後と53/52で異なる */
 #endif
    Alloc("UnifF Forward Reverse AfterJump");

 #if HALF_REAL || FLOAT_REAL || defined(FAST_UNIF)
    Echo("#0.777 0.395 0.656 0.455 0.167 0.765 0.998 0.857 0.549 0.509");
 #else
    Echo("#0.777 0.656 0.167 0.998 0.549 0.176 0.300 0.669 0.025 0.121");
 #endif
    for (i=0;i<N;i++) UnifF[i]=Unif_f();
    Print("UnifF");

 #if SRAND_BYSTR && defined(TINYMT64) && defined(OLD_BYSTR)
    Echo("#0.693 0.939 0.793 0.411 0.103 0.204 0.054 0.606 0.146 0.445");
 #elif SRAND_BYSTR && defined(TINYMT64)
    Echo("#0.288 0.662 0.678 0.545 0.049 0.371 0.493 0.699 0.062 0.946");
 #elif SRAND_BYSTR && UNIF_STEP == 1 && defined(OLD_BYSTR)
    Echo("#0.889 0.445 0.007 0.851 0.684 0.295 0.030 0.219 0.439 0.915");
 #elif SRAND_BYSTR && UNIF_STEP == 1
    Echo("#0.281 0.415 0.206 0.931 0.293 0.009 0.933 0.441 0.818 0.375");
 #elif SRAND_BYSTR && UNIF_STEP == 2 && defined(OLD_BYSTR)
    Echo("#0.889 0.007 0.684 0.030 0.439 0.865 0.233 0.936 0.344 0.845");
 #elif SRAND_BYSTR && UNIF_STEP == 2
    Echo("#0.281 0.206 0.293 0.933 0.818 0.968 0.896 0.768 0.905 0.856");
 #elif defined(TINYMT64)
    Echo("#0.828 0.493 0.208 0.234 0.776 0.403 0.693 0.651 0.615 0.614");
 #elif UNIF_STEP == 1
    Echo("#0.522 0.907 0.315 0.976 0.861 0.239 0.610 0.655 0.492 0.750");
 #elif UNIF_STEP == 2
    Echo("#0.522 0.315 0.861 0.610 0.492 0.628 0.155 0.733 0.906 0.358");
 #endif
    for (i=0;i<N;i++) Forward[i]=Unif();
    Print("Forward");
    for (i=N;i--;) Reverse[i]=BackUnif();
    Print("Reverse");

    /* 1000兆先にジャンプした後、(1000兆-N)だけ前に戻る */
    Jump(PETA*UNIF_STEP); Jump(-(PETA-N)*UNIF_STEP);
    for (i=N;i--;) AfterJump[i]=BackUnif();
    /* この時点で乱数は初期状態に戻る */
    Print("AfterJump");

    /* 前後に平均10億回離れたジャンプを JUMP_ITER 回行う時間を計る */
    ResetLap();
    for (i=0;;i++) {
        double x=Rand_f()-2147483648.0;
        Sum+=x; Jump(x);
        if (i>=INT_MAX-1) break;
        if (Progress("# Jump ",i,0)&&(jump_lap=LapSec())>JUMP_SEC) break;
    }

    Jump(-Sum);
    /* この時点で乱数は初期状態に戻る */
    AfterUnif=Unif(); UnifF7=UnifF[7];
 #if SRAND_BYSTR
  #if    defined(TINYMT64) &&  defined(FAST_UNIF) && defined(OLD_BYSTR)
    Echo("#0x3fe62ecc0d200000 0x3feb6cc7e7000000 TM64 STR FAST_UNIF");
  #elif  defined(TINYMT64) &&  defined(FAST_UNIF)
    Echo("#0x3fd27138b6400000 0x3feb6cc7e7000000 TM64 STR FAST_UNIF");
  #elif  defined(TINYMT64) && defined(UNIF52_UNION) && defined(OLD_UNIF53)
    Echo("#0x3fe62ecc0c6bc1f2 0x3fe565fbc134f166 TM64 STR UNIF52 OLD53");
  #elif  defined(TINYMT64) && defined(UNIF52_UNION)
    Echo("#0x3fe62ecc0d235e0e 0x3fe565fbc169a78a TM64 STR UNIF52");
  #elif  defined(TINYMT64) && defined(OLD_UNIF53)
    Echo("#0x3fe62ecc0c6bc1f3 0x3fe565fbc134f167 TM64 STR UNIF53 OLD53");
  #elif  defined(TINYMT64)
    Echo("#0x3fd27138b64b1250 0x3fe565fbc169a78b TM64 STR UNIF53");
  #elif  defined(FAST_UNIF) && defined(OLD_BYSTR)
    Echo("#0x3fec6eec15600000 0x3feb6cc7e7000000 TM32 STR FAST_UNIF");
  #elif  defined(FAST_UNIF)
    Echo("#0x3fd2020ab2000000 0x3feb6cc7e7000000 TM32 STR FAST_UNIF");
  #elif  defined(UNIF52_UNION) && defined(OLD_UNIF53)
    Echo("#0x3fec6eec15c76070 0x3fe565fbc134f166 TM32 STR UNIF52 OLD53");
  #elif  defined(UNIF52_UNION)
    Echo("#0x3fec6eec156e3b02 0x3fe565fbc169a78a TM32 STR UNIF52");
  #elif  defined(OLD_UNIF53)
    Echo("#0x3fec6eec15c76071 0x3fe565fbc134f167 TM32 STR UNIF53 OLD53");
  #elif defined(OLD_BYSTR)
    Echo("#0x3fec6eec156e3b03 0x3fe565fbc169a78b TM32 STR UNIF53");
  #else
    Echo("#0x3fd2020ab21a9124 0x3fe565fbc169a78b TM32 STR UNIF53");
  #endif
 #else
  #if    defined(TINYMT64) &&  defined(FAST_UNIF)
    Echo("#0x3fea7ea3b2600000 0x3feb6cc7e7000000 TM64 NUM FAST_UNIF");
  #elif  defined(TINYMT64) && defined(UNIF52_UNION) && defined(OLD_UNIF53)
    Echo("#0x3fea7ea3b083d72c 0x3fe565fbc134f166 TM64 NUM UNIF52 OLD53");
  #elif  defined(TINYMT64) && defined(UNIF52_UNION)
    Echo("#0x3fea7ea3b2641eb8 0x3fe565fbc169a78a TM64 NUM UNIF52");
  #elif  defined(TINYMT64) && defined(OLD_UNIF53)
    Echo("#0x3fea7ea3b083d72d 0x3fe565fbc134f167 TM64 NUM UNIF53 OLD53");
  #elif  defined(TINYMT64)
    Echo("#0x3fea7ea3b2641eb9 0x3fe565fbc169a78b TM64 NUM UNIF53");
  #elif  defined(FAST_UNIF)
    Echo("#0x3fe0b7ce36400000 0x3feb6cc7e7000000 TM32 NUM FAST_UNIF");
  #elif  defined(UNIF52_UNION) && defined(OLD_UNIF53)
    Echo("#0x3fe0b7ce37a0dab8 0x3fe565fbc134f166 TM32 NUM UNIF52 OLD53");
  #elif  defined(UNIF52_UNION)
    Echo("#0x3fe0b7ce365d06d4 0x3fe565fbc169a78a TM32 NUM UNIF52");
  #elif  defined(OLD_UNIF53)
    Echo("#0x3fe0b7ce37a0dab9 0x3fe565fbc134f167 TM32 NUM UNIF53 OLD53");
  #else
    Echo("#0x3fe0b7ce365d06d5 0x3fe565fbc169a78b TM32 NUM UNIF53");
  #endif
 #endif
    Print("AfterUnif;; UnifF7;;");
    fprintf(fp,"Jump() %s(%d)\n",Nano(jump_lap/(i+1)),i+1);
 #if defined(TIME)
    SetInterval(1000); /* ベンチマーク1回の時間を 1.0 秒にする */
    SetInterval(-4);   /* 1.0 秒の間に時計を見る回数を8~15回にする。*/
    UnifBench();
    Unif_fBench();
    RandBench();
    Rand_fBench();
  #if defined(X86_INTRIN)
    if (HaveRdrand()) Rand_hBench();
   #if defined(Rand_s)
    if (HaveRdseed()) Rand_sBench();
   #endif
  #endif
 #endif
 }
#endif

複数のポコムプログラムを連携する例

あらかじめ、以下の Tra.c から実行ファイル Tra.exe または Tra と Example1.c から実行ファイル Example1.exe または Example1 を作成しておきます。
Print N A;SrcM;
の最初の ; は変数の名前を変更し、 二つ目の ; は16進数で書き出す指令です。 16進数で書き出すと浮動小数点数の値を正確に伝えることができます。
Tra.c の内容:

   #define FILE_NAME "Tra.c"

   #if defined(PDEF_H)

    INT(N,3,          EJ("Size of matrix",  "行列の大きさ"              ))
    RMAT(SrcM,307,N,N,EJ("Input matrix",    "入力となる行列"            ))
    RMAT(DstM,307,N,N,EJ("Output matrix",   "転置した結果を記録する行列"))
    PROC(Transpose,   EJ("Transpose matrix","転置行列を作る計算ルーチン"))

   #else

    #include "../pocom.h"
    CHECK_FILE_NAME

    void Transpose(void) {
        Int i,j;
        Check("SrcM"); Alloc("DstM");
        for (i=0;i<N;i++) for (j=0;j<N;j++) DstM[j][i]=SrcM[i][j];
    }

   #endif
test4.txt の内容:
 Echo # Example1 から Tra を起動して行列 A を転置し計算する例
 A                         B
   2.000   5.000   7.000 ;  23.000
   4.000  13.000  20.000 ;  58.000
   8.000  29.000  50.000 ; 132.000

 Solve
 Print N A X B
 Output tra-in.txt
 Print N A;SrcM;
 Echo Transpose
 Echo Output tra-out.txt
 Echo Print N DstM;A;
 Echo Quit
 Output -
 System Tra tra-in.txt
 Load tra-out.txt
 Delete tra-in.txt
 Delete tra-out.txt
 Solve
 Print N A X B
Example1 test4.txt の出力内容:
# Example1 から Tra を起動して行列 A を転置し計算する例
 N   A                         X       B
   3   2.000   5.000   7.000 ;   3.000  23.000
       4.000  13.000  20.000 ;   2.000  58.000
       8.000  29.000  50.000 ;   1.000 132.000

 N   A                         X       B
   3   2.000   4.000   8.000 ;  36.417  23.000
       5.000  13.000  29.000 ; -37.708  58.000
       7.000  20.000  50.000 ;  12.625 132.000


PrintCSV の文字列出力(引用符で囲むか囲まないかの違い)

csv.txt の内容

 Limit 4

 Title
 2147483647         2147483648         -2147483648        -2147483649
 ----------         +++++++++++++      ==========         [010]
 '2019/03/16 09:03' 平成19年02月28日   09:03:01           '=IF(A1="Title",1,2)'
 ----------A        +++++++++++++A     010                10.0
 'Title'            +AAAA              A1                 23時03分04秒      
 '\-012,023'        '-\012,023'        '$-012,023'        '-$012,023'       
 '-123,567,890'     h30-02/28          '-100,200'         -00009
 0                  -22222             ''                 'aa bb'

 Format Title 18
 Print Title
 PrintCSV Title
 Output csv.csv
 PrintCSV Title
 PrintCSV Title;;

PrintCSV で文字列変数の Format が正の場合は、文字列として出力します。 Format が負の場合や 文字列変数名の後に ;; を加えた場合はそのまま書き出します。 文字列が値に変換される場合もあります。

cl.exe /D JPN /Tp Mva.c または g++ -DJPN Mva.c のようにして Mva.exe を作成します。 そして

 C:\...\...> Mva.exe csv.txt

として書き出された csv.csv をエクセルで開いて列幅を調整したときの画面

csv

ubuntu で soffice で開いたときの画面

csv-u

間違ったプログラムでの警告

以下にバグのあるプログラムとその実行結果を示します。
err.c
#define FILE_NAME "err.c"

#if defined(PDEF_H)

 INT(N,3,     EJ("Size of matrix","行列の大きさ"))
 SMAT(A,3,N,N,EJ("Matrix of string","文字列行列"))
 PROC(Test,   EJ("Test","テスト"))
 AUTO("Test")

#else

 #define JPN
 #define STDERR_STDOUT /* エラーメッセージもリダイレクトできるようにする */
 #define CHECK_MEM   /* 16ビットコンパイラではデフォルトで無効のため */
 #define NO_OPEN     /* ファイル選択ダイアログを出さない     */
 #define NO_LIST     /* 変数リストを表示しない     */
 #define REPORT_MEM  /* メモリの使用状況を表示する */
 #include "../pocom.h"
 CHECK_FILE_NAME

 void Test(void) {
    Int i,j;
    N=3;
    FreeMem(&N);          /* ①GetMem() の戻り値でないアドレスを解放する */
    Alloc("A");
    A[0][0][1]=(char)'v'; /* ②A[0][0] は初期 ZeroStr を指す。それを破壊 */
    for (i=0;i<N;i++) for (j=0;j<N;j++) SetSmat(A,i,j,"abc");
    GetMem(1,1);          /* ③解放されなメモリを確保する(メモリリーク)  */
    Print("A");
 }

#endif
bcc32 err.c で err.exe > err.txt の結果:(32ビットコンパイラ)
# 不正な FreeMem(&N)
#  -> err.c(24)
# 警告:ZeroStr に代入された
 A
 abc abc abc ;
 abc abc abc ;
 abc abc abc ;

# メモリリーク(size:20)GetMem(1,1)
#  -> err.c(28)
# Memory Error:3(NumOfBlock__:1)
# Malloc:6(vmj:0,str:0,fmj:1)avg:846.7
# GetVmj:1 max:1vmjs/size:254x16bytes
# StrMgr:9 FmjMgr:3 MaxCdef:0 MaxMem:6
# Max:5080bytes MemLimit:1G(0.000508%)
tcc err.c で err.exe > err.txt の結果(16ビット・スモールモデル)
# 不正な FreeMem(&N)
#  -> err.c(24)
# 警告:ZeroStr に代入された
 A
 abc abc abc ;
 abc abc abc ;
 abc abc abc ;

# メモリリーク(size:10)GetMem(1,1)
#  -> err.c(28)
# Memory Error:3(NumOfBlock__:1)
# Malloc:8(vmj:0,str:0)avg:112.2
# GetVmj:1 max:1vmjs/size:16x10bytes
# StrMgr:9 MaxCdef:0 MaxMem:8
# Max:898bytes
# TotalSpace 53648 Max:1502 diff:604 Available:52146(2.799732%)
エラー① → # 不正な FreeMem(&N)
            #  -> err.c(24)
エラー② → # 警告:ZeroStr に代入された
エラー③ → # メモリリーク(size:10)GetMem(1,1)
            #  -> err.c(28)
出力の # Memory Error:3 以降は REPORT_MEM が出力した結果です。 順に数字の説明をします。 3 はエラーの回数。
# Malloc:6times(vmj:0,str:0,fmj:1)max:6 avg:846.7
# Malloc:8times(vmj:0,str:0)avg:112.2
6times は Malloc__ が呼ばれた回数。その内訳は、 avg:846.7bytes は1回の Malloc__ が確保した平均の大きさ。 (16ビットコンパイラの場合行列Aの行ベクトルの確保で3回 Malloc__ が呼ばれるので合計8回になる。FAST_MATJAG を定義すると 合計6回になる。)
# GetVmj:1times max:1vmjs/size:254x16bytes
# GetVmj:1times max:1vmjs/size:16x10bytes
# StrMgr:9times
・文字列高速確保を呼び出した回数が9回(err.c の場合は SetSmat の呼出回数)。
# FmjMgr:3times
・行列高速確保の呼び出し回数
MaxCdef:0
・CDEF の最大登録数
MaxMem:6
・Malloc__ が確保したメモリブロック数の最大値
# Max:5080bytes MemLimit:1G(0.000508%)
# Max:898bytes
# TotalSpace 53648 Max:1502 diff:604 Available:52146(2.799732%)

下三角行列の作り方

以下に4×4のランダムな下三角行列を表示するプログラムを示します。

ltm.c
#define FILE_NAME "ltm.c"
#if defined(PDEF_H)
 FIX(4)
 IVEC(L,1,FIX4,"列幅")
 RJAG(Ltm,307,L,"下三角行列")
 PROC(GenLtm,"行列の作成")
 AUTO("GenLtm")
 AUTO("Print Ltm")
#else
 #include "../pocom.h"
 CHECK_FILE_NAME

 void GenLtm(void) {
     Int i,j;
     NoList=1; Alloc("L"); SizeChangedBy(L,GenLtm);
     for (i=0;i<4;i++) L[i]=i+1;
     Alloc("Ltm");
     for (i=0;i<4;i++) for (j=0;j<L[i];j++) Ltm[i][j]=Unif();
 }
#endif
結果
 Ltm
   0.593 ;
   0.836   0.890 ;
   0.648   0.178   0.150 ;
   0.205   0.980   0.859   0.754 ;

古い pocom 形式での書き方

上のプログラムを古い pocom 形式での書き方を示します。 データ定義を pdef.h に書き ltm.c には計算ルーチンを書きます。

pdef.h
 FIX(4)
 IVEC(L,1,FIX4,"列幅")
 RJAG(Ltm,307,L,"下三角行列")
 PROC(GenLtm,"行列の作成")
 AUTO("GenLtm")
 AUTO("Print Ltm")
ltm.c
 #include "../pocom.h"
 void GenLtm(void) {
     Int i,j;
     NoList=1; Alloc("L"); SizeChangedBy(L,GenLtm);
     for (i=0;i<4;i++) L[i]=i+1;
     Alloc("Ltm");
     for (i=0;i<4;i++) for (j=0;j<L[i];j++) Ltm[i][j]=Unif();
 }
ltm.c の #include "../pocom.h" の前に、#define PDEF_H "pdef2.h" と書けば、 pdef.h の代わりにデータ定義を pdef2.h に書くことが出来ます。

正方行列について

以下のように通常の行列と正方行列を定義したプログラム(square.c)を例に、 通常の行列と正方行列の読込時の違いを説明します。

#define FILE_NAME "square.c"

#if defined(PDEF_H)

 INT(N,3,"Size N")
 INT(M,3,"Size M")
 RMAT(RM,406,N,M,"Real Matrix")
 RMAT(RQ,406,N,N,"Real Square Matrix")

#else

 #include "../pocom.h"
 CHECK_FILE_NAME

#endif
入力ファイル square.txt は以下の通りです。
 RM

 Print N M RM

 RM
 ;

 Print N M RM

 RQ

 Print N RQ

 RQ
 ;

 Print N RQ
実行結果は以下の通りです。
 N   M   RM
   0   0

 N   M   RM
   1   0   ;

 N   RQ
   0

square.txt(16)でエラーが発生!
 ;
 ^
RQ:決定された要素数を超えている

正方行列RQが上の読込でエラーになる原因は ; を読んだ時点で 列数としてNが0になるのに、 最終的に列数が0の1行目があるため、行数としてNが1になり、矛盾するからです。

Add 関数群の使い方の例

Add 関数群を使用すると、読み込んだデータに新しい要素を追加することができます。
以下に一回実行するたびに乱数ベクトルと文字列ベクトルを1個ずつ成長させてく例を示します。

#define FILE_NAME "grow.c"

#if defined(PDEF_H)

 INT(N,5,"Size")
 RVEC(RV,406,N,"Real Vector")
 SVEC(SV,  8,N,"Str Vector")
 PROC(Grow,"Grow")
 AUTO("Grow")

#else

 #define HIT_ENTER
 #include "../pocom.h"
 CHECK_FILE_NAME

 void Grow(void) {
    Int i; char buf[32]; real next;
    NoList=1;  /* 変数リストの表示を抑止する */
    Seed("");  /* 乱数を現在時刻で初期化する */
 
 /* grow.txt が存在しなければ空の grow.txt を作成する */
    Append("grow.txt"); Output("-");
    Load("grow.txt");
    Print("N SV RV"); /* Print がチェックするので Check("RV SV"); は不要 */
    next=Unif(); sprintf(buf,"(%6.4f)",next);
    AddToRvec(RV,next); AddToSvec(SV,buf);
    Print("N SV RV");
    Append("grow.txt");
    Print("N SV RV");
 }

#endif

grow.c をコンパイルした grow.exe を 実行するたびに grow.txt に乱数と文字列が1個ずつ加えられます。

コード型の使い方の例(TotoBig)

以下に示すように TotoBig の結果を確かめるプログラムも CDEF と CVEC を使えば簡単に書くことができます。

toto.c
#define FILE_NAME "toto.c"
#if defined(PDEF_H)
 INT( N,     2,         "購入口数")
 FIX(14)
 FIX(15)
 CDEF(Rank,  3,FIX15,   "等級")
 IVEC(Lotos, 1,FIX14,   "試合結果")
 IMAT(Buy,   1,FIX14,N, "購入数字")
 CVEC(Result,3,N,  Rank,"結果")
 AUTO("Rank")
 AUTO("1等 2等 3等 4等 5等 6等 8 7 6 5 4 3 2 1 0 $")
 AUTO("Range Lotos >= 0 <= 2")
 AUTO("Range Buy   >= 0 <= 2")
 PROC(TotoBig,"TotoBig")
#else
 #define HIT_ENTER 1
 #include "../pocom.h"
 CHECK_FILE_NAME

 void TotoBig(void) {
     Int i,j;
     Check("Rank Lotos Buy"); Alloc("Result");
     for (i=0;i<N;i++) for (j=0;j<14;j++)
     if (Buy[j][i]!=Lotos[j]) Result[i]++;
 }
#endif
toto.txt
 Buy
 2 2 2 0 1 2 2 0 0 1 ;
 2 0 0 2 0 2 0 2 0 2 ;
 0 1 0 0 1 2 0 1 2 0 ;
 0 0 1 1 0 0 0 1 1 0 ;
 0 1 0 0 1 0 0 1 0 1 ;
 1 2 1 1 0 0 0 0 2 1 ;
 1 1 0 0 0 2 2 2 2 1 ;
 1 2 0 2 0 1 1 1 2 1 ;
 2 1 2 1 0 2 2 1 2 1 ;
 0 1 2 2 2 0 2 0 0 1 ;
 0 0 0 1 0 0 0 0 0 1 ;
 0 0 1 0 0 2 2 1 2 0 ;
 1 1 1 2 0 0 0 2 1 2 ;
 2 1 1 2 0 1 2 1 1 2 ;

 Lotos
 2 1 1 1 1 0 2 2 1 0 2 0 2 1

 TotoBig
 Print Result
結果
 Result
 3   7   3   5   4   5   3   6等 5   4

3次元配列

ポコムには3次元配列は用意されていません。 しかし、値域が狭ければ、 文字列行列 SMAT や SJAG で代用することができます。 例えば'0'と'1'のみのN×N×N行列の'1'の出現数を求める場合を示します。

cube.c
#define FILE_NAME "cube.c"

#if defined(PDEF_H)

  INT(N,4,     "大きさ")
  SMAT(A,4,N,N,"N×N×N行列")
  INT(Sum,3,   "総和の結果")
  PROC(Count,  "総和を求める")

#else

 #include "../pocom.h"
 CHECK_FILE_NAME

 void Count(void) {
     Int i,j,k;
     Check("A"); Sum=0;
     for (i=0;i<N;i++) for (j=0;j<N;j++) {
         if ((Int)strlen(A[i][j])!=N) Error("A","幅が不正");
         for (k=0;k<N;k++) if (A[i][j][k]=='1') Sum++;
     }
 }
#endif
sum.txt
 A
 100 001 101 ;
 010 010 010 ;
 001 100 101 ;

 Count
 print Sum

ローカル変数による高速化

ポコムで宣言される変数は全てグローバル変数になります。 これは、数値計算のとき大変便利ですが、古いコンパイラの場合、ローカル変数に比べ、 わずかに遅くなるのが欠点です。 最近のコンパイラは大域的な最適化を行うと、 グローバル変数もレジスタに割り当てる場合があります。

参考にのため「3次元配列」 の例題をローカル変数を使って高速化したものを示します。 プログラムが汚くなるので、あまりお勧めはできませんが、 古いコンパイラで、わずかでも高速化したいときは、このようにします。

 void Count(void) {
     Int i,j,k,n,sum=0; smat a; str s;
     Check("A"); n=N; a=A;
     for (i=0;i<n;i++) for (j=0;j<n;j++) {
         s=a[i][j];
         if (strlen(s)!=n) Error("A","幅が不正");
         for (k=0;k<N;k++) if (s[k]=='1') sum++;
     }
     Sum=sum;
 }

関数の引数を読み込む例

この例の Fill 関数は行列 A の大きさと値を引数にとって、行列をその値で埋めます。 引数の範囲チェックをしない場合は、ReadInt( ) 、 ReadReal( ) を代わりに使ってください。

fill.c
#define FILE_NAME "fill.c"

#if defined(PDEF_H)
 INT(N,4,     "大きさ")
 RMAT(A,4,N,N,"N×N行列")
 FUNC(Fill,   "設定する")
 PROC(Test,   "Fill を呼び出す")
 AUTO("Range N > 0")
 AUTO("Range A >= 0 <= 100")
 AUTO("Fill 4 50")
 AUTO("Print N A")
 AUTO("Test")

#else

 #include "../pocom.h"
 CHECK_FILE_NAME

 void Fill(const char*argument) {
     Int i,j; real a;
     CopyArg("Fill",argument); NoList=1;
     N=ReadIntRange(N); a=ReadRealRange(A);
     SizeChangedBy(N,Fill); EndArg(); Alloc("A");
     for (i=0;i<N;i++) for (j=0;j<N;j++) A[i][j]=a;
 }

 void Test(void) { Fill("3 25"); Print("N A"); }

#endif
結果:
 N    A
    4   50   50   50   50 ;
        50   50   50   50 ;
        50   50   50   50 ;
        50   50   50   50 ;

 N    A
    3   25   25   25 ;
        25   25   25 ;
        25   25   25 ;

UpdateCvar(CVAR) の使い方

UpdateCvar(CVAR) は以下のような場合で使用します。

#define FILE_NAME "cdef.c"

#if defined(PDEF_H)

 INT(DN,3,"")
 CDEF(Def,7,DN,"")
 CODE(Val,3,Def,"")
 FUNC(Test,"")
 AUTO("Def")
 AUTO("(A) (B) (C)")
 AUTO("")
 AUTO("Test (C)")
 AUTO("Print Val")
 AUTO("Def")
 AUTO("(A) (B)")
 AUTO("")
 AUTO("Print Val")

#else

 #include "../pocom.h"
 CHECK_FILE_NAME

 void Test(const char*argument) {
     CopyArg("Test",argument);
     Val=ReadCode(Def);
     EndArg();
     NoList=1;
     UpdateCvar(Val);
 }

#endif
とすると以下のように最後の Print でエラーを検出してプログラムが停止します。
 Val
 (C)

AUTO(9)でエラーが発生!
Print Val
      ^
Val:の定義'Def'が空、またはサイズが縮小した
のどれかに当てはまる場合は以下のように出力され、エラーとはなりません。
 Val
 (C)

 Val
 [2]

MulAdd の使い方

pocom.html で配列の大きさを計算ルーチンで指定する例があります。 そこでは NP1=N+1; のようにしています。これは NP1 がオーバーフローする危険はありません。 しかし、NP1=N*10-1; のような場合、整数のオーバーフローが発生する可能性があります。 そこでバージョン 5.02 から用意したのは MulAdd マクロです。

NP1=MulAdd(N,10,-1);
のように書くと安心です。 これは整数のオーバーフローが起こるとメッセージを表示してプログラムを中断します。 以前の CheckIntOverflow() も引き続き使用できます。

高速乱数の使い方

数値計算用に通常使う Unif() 乱数は周期が長く高品質でジャンプやバックもできます。 しかし、少し複雑です。 例えばクイックソートのピボット選択や treap という2分探索木で使う乱数は、乱数の品質は多少低くても、 高速であることが重要なので Unif() は向いていません。
そこで、Unif_f() をはじめとする高速乱数を用意しました。 高速乱数には必要ないので種を与える機能も組み込んでいません。周期も2の32乗引く1で長くありません。
高速乱数を無効にするにはマクロ FAST_RAND を0に定義します。 この場合、プログラムがほんの少し小さくなります。
Rand_f() は0以外の32ビット符号なし整数乱数を返します。 Unif_f() は0より大きく1より小さい浮動小数点数乱数を返します。
マクロ FAST_RAND で乱数を何系列使うかを指定します。 デフォルトは1系列です。
FAST_RAND を2以上にすると、マルチスレッドなどで必要な複数系列乱数を 発生させることができます。 FAST_RAND に指定できる最大値はメモリの制約がなければ2の32乗引く1です。
Rand_m(N) は0以外の32ビット符号なし整数乱数を返します。 Unif_m(N) は0より大きく1より小さい浮動小数点数乱数を返します。 N は系列の番号(0以上 FAST_RAND 未満)を指定します。
Rand_f() は Rand_m(0) と、 Unif_f() は Unif_m(0) と同じです。
例えば0以上8未満の整数乱数を得るには i = Rand_f() % 8 のようにします。 高速乱数は32ビット XorShift を使っているので、このように下位ビットを使うことができます。
高速乱数は周期が短いのでオーバーラップが心配になります。 そこで、実際に128の系列のに場合について オーバーラップの起こりやすい組み合わせを調べてみました。
・隣り合う系列で一番近いもの → 53系列と54系列の距離778万2022
・任意の組み合わせで一番近いもの → 38系列と59系列の距離11万6842
この結果から、実用上は問題は起きないと考えられます。

構造体

ちょっとしたCのプログラムならば、たいてい構造体を使います。 しかし、ポコムのデータ型には構造体はありません。 ポコムでは代わりに複数のベクトルで表現します。 例えば、

#define N 100
struct person {
    char Name[32];
    char Phone[16];
    int  Age;
} data[N];

のようなデータは、ポコムでは、

 INT(N,4,        "人数")
 SVEC(Name,12,N, "氏名")
 SVEC(Phone,14,N,"電話番号")
 IVEC(Age,3,N,   "年齢")

のように定義します。Name と Phone と Age は N という同じサイズを指定されているので、 読み込みのときにサイズの矛盾が生じることはありません。 また、入力や出力のときは、変数行に並べることによって、まとめることができます。

THIS_IS と FILE_NAME

FILE_NAME マクロの代わりに THIS_IS マクロを使えば、 少しプログラムが小さくなります。

しかし、従来の書き方である THIS_IS マクロを使うことには推奨しません。理由は以下の通りです。

例えば Example1.c の最初が

#define THIS_IS "Example1.c"
と書かれていて、それを、を同じフォルダの Example2.c へコピーしてこれを修正し、 表示精度を小数点3桁から小数点4桁へ高めたバージョンを作る場合を考えます。
#define THIS_IS "Example1.c"

#if defined(PDEF_H)

 INT(N,4,       "行列の大きさ")
 RMAT(A,408,N,N,"係数行列")
 RVEC(B,408,N,  "右辺")
 RMAT(C,408,N,N,"作業用の行列")
 RVEC(X,408,N,  "計算結果")
 PROC(Solve,    "連立方程式を解く")

#else

 #include "../pocom.h"

 void Solve(void) {
     Int i,j,k,nn=MulAdd(N,N,0);
     Check("A B"); Alloc("C X");
     for (i=0;i<N;i++) for (j=0;j<N;j++) C[i][j]=A[i][j];
     for (i=0;i<N;i++) X[i]=B[i];
     for (k=0;k<N;k++) {
         if (C[k][k]==0) Error("Solve","分母が0");
         for (j=k+1;j<N;j++) C[k][j]/=C[k][k];
         X[k]/=C[k][k];
         for (i=0;i<N;i++) if (i!=k) {
             for (j=k+1;j<N;j++) C[i][j]-=C[i][k]*C[k][j];
             X[i]-=C[i][k]*X[k];
         }
         Progress("#Solve ",k*(2*N-k),nn);
     }
 }

#endif

この Example2.c は、コンパイルエラーも実行時エラーも起きませんが、 表示精度は3桁のままです。原因は THIS_IS マクロを Example2.c とすべきところが、 Example1.c の ままという間違いをしているからです。この場合、Example2.c をコンパイルしたときに pocom.h の中から インクルードするのが同じフォルダにある Example1.c ですから、コンパイルエラーが起きません。 このバグに気がついて修正するのは非常に難しいと私は考えます。そこで、Example2.c を

#define FILE_NAME "Example1.c"

#if defined(PDEF_H)

 INT(N,4,       "行列の大きさ")
 RMAT(A,408,N,N,"係数行列")
 RVEC(B,408,N,  "右辺")
 RMAT(C,408,N,N,"作業用の行列")
 RVEC(X,408,N,  "計算結果")
 PROC(Solve,    "連立方程式を解く")

#else

 #include "../pocom.h"
 CHECK_FILE_NAME

 void Solve(void) {
     Int i,j,k,nn=MulAdd(N,N,0);
     Check("A B"); Alloc("C X");
     for (i=0;i<N;i++) for (j=0;j<N;j++) C[i][j]=A[i][j];
     for (i=0;i<N;i++) X[i]=B[i];
     for (k=0;k<N;k++) {
         if (C[k][k]==0) Error("Solve","分母が0");
         for (j=k+1;j<N;j++) C[k][j]/=C[k][k];
         X[k]/=C[k][k];
         for (i=0;i<N;i++) if (i!=k) {
             for (j=k+1;j<N;j++) C[i][j]-=C[i][k]*C[k][j];
             X[i]-=C[i][k]*X[k];
         }
         Progress("#Solve ",k*(2*N-k),nn);
     }
 }

#endif

とすると、実行時に、"Bad FILE_NAME macro." というエラーメッセージを表示して、 バグの原因が明らかになり、このよくあるミスを防ぐことができます。

ALIGN_REAL の効果

ALIGN_REALは、16ビットコンパイラや Borland C++ 5.5.1 の ように calloc( ) や new char[] の返すアライメントが8バイトよりも小さいコンパイラの ために用意しました。その効果を測るのが以下の Example3.c です。

#define FILE_NAME "Example3.c"

#if defined(PDEF_H)

 INT(N,3,       EJ("Size of matrix",    "行列の大きさ"      ))
 RMAT(A,307,N,N,EJ("Coefficient matrix","係数行列"          ))
 RVEC(B,307,N,  EJ("Right side vector", "右辺"              ))
 RMAT(C,307,N,N,EJ("Work matrix",       "作業用の行列"      ))
 RVEC(X,307,N,  EJ("Solution vector",   "計算結果"          ))
 PROC(Bench,    EJ("Bench mark test",   "ベンチマークテスト"))
 AUTO("Bench")

#else

 /* 連立方程式の次元数 */
 #define SZ   50

 /* ポコムバージョン 8.01 以降で静的配列モードのために定義する */
 #define VROW SZ
 #define MROW SZ
 #define MCOL SZ

 /*
 pocom.h, Example3.c のファイル名の長さが変わっても
 実行ファイルの大きさを変えない。(CHECK_MEM と MulAdd を無効化する。)
 ベンチマークの結果がファイル名が変わっても影響されなくなる */
 #define FIX_EXE_SIZE
 
 #include "../pocom.h"
 CHECK_FILE_NAME

 /* msdos.exe を利用する場合は T の 32767 を 10 程度にすること */
 #if defined(MSDOS_EXE)
  #define T 10
 #else
  #define T 32767
 #endif

 #define AND7(X) ((int)((size_t)(X)&7))
 /*
 以前は以下のように定義していた。
 #define AND7(X) (((int)(size_t)(X))&7)
 この場合、Visual C++ 64ビットコンパイラで /O2 か /O1 を指定すると、
 fatal error C1001: でコンパイルができなかった。非常に不思議である。
 */

 void Bench(void) {
     Int i,j,k,t;
     NoList=1;
     N=SZ;
 #if !defined(__BORLANDC__)
     Alloc("A B C X");
 #else
     /* A を確保してそのアライメントによって他の配列を確保する順番を変える */
     /* C と X のアライメントが8ではないようになる */
     Alloc("A");
     if (AND7(A[0])) Alloc("B X C"); else Alloc("X C B");
 #endif
     fprintf(fp,"A[0]=%d B=%d X=%d C[0]=%d C[1]=%d C[2]=%d C[3]=%d\n",
         AND7(A[0]),AND7(B),AND7(X),
         AND7(C[0]),AND7(C[1]),AND7(C[2]),AND7(C[3]));
     for (i=0;i<N;i++) {
         for (j=0;j<N;j++) A[i][j]=Unif();
         B[i]=Unif();
     }
     for (t=0;t<T;t++) {
         for (i=0;i<N;i++) for (j=0;j<N;j++) C[i][j]=A[i][j];
         for (i=0;i<N;i++) X[i]=B[i];
         for (k=0;k<N;k++) {
             if (C[k][k]==0)
                 Error("Solve", EJ("Denominator is zero","分母がゼロ"));
             for (j=k+1;j<N;j++) C[k][j]/=C[k][k];
             X[k]/=C[k][k];
             for (i=0;i<N;i++) if (i!=k) {
                 for (j=k+1;j<N;j++) C[i][j]-=C[i][k]*C[k][j];
                 X[i]-=C[i][k]*X[k];
             }
         }
 #if INT_MAX > 32767 || defined(MSDOS_EXE) || __POCOM__ >= 805
         Progress(EJ("#Bench ","#進捗率 "),t,T);
 #endif
     }
     Clock(ZeroStr);
 }

#endif

これを Windows 10 Core i5-10210U 1.60GHz 2.10GHz でテストしました。
Borland C++ 5.5.1 で bcc32 -O2 Example3.c として実行すると、 3.652 秒となりました。
そして bcc32 -O2 -DALIGN_REAL Example3.c として実行すると、 3.520 秒となりました。
このように ALIGN_REAL は実行速度に関して効果があります。 しかし、プログラムサイズと消費メモリは大きくなります。
16ビットコンパイラと Borland C++ 以外のコンパイラでは効果はありません。
例えば Visual C++ や g++ では効果はありません。

メモリ節約データファイルの効果

以下の様な相関係数を計算するプログラムを例に、 メモリを節約するデータファイルの効果について説明します。

#define FILE_NAME "CorrCoef.c"

#if defined(PDEF_H)
 INT(N,4,      "大きさ")
 RVEC(X,307,N, "X")
 RVEC(Y,307,N, "Y")
 REAL(EX,307,  "平均X")
 REAL(EY,307,  "平均Y")
 REAL(SX,307,  "標準偏差X")
 REAL(SY,307,  "標準偏差Y")
 REAL(RXY,307, "相関係数")
 PROC(CorrCoef,"分析")
#else
 #include "../pocom.h"
 CHECK_FILE_NAME

 void CorrCoef(void) {
     Int i; real dx,dy;
     Check("X Y"); EX=EY=SX=SY=RXY=0;
     for (i=0;i<N;i++) { EX+=X[i]; EY+=Y[i]; }
     EX/=N; EY/=N;
     for (i=0;i<N;i++) {
         dx=X[i]-EX; dy=Y[i]-EY;
         SX+=dx*dx; SY+=dy*dy; RXY+=dx*dy;
     }
     SX=sqrt(SX/(N-1)); SY=sqrt(SY/(N-1)); RXY/=(N-1)*SX*SY;
 }
#endif

このプログラムを TurboC 1.5 のスモールモデルでコンパイルします。 ( tcc -DJPN CorrCoef.c ) この場合、Windows10+msdos.exe にて下のようなデータファイルで N が 1513 までは実行できますが 1514 だと、メモリが足りなくなります。
(ちなみに tcc -DJPN -DALIGN_REAL CorrCoef.c とすると、 N が 1434 までしか実行できません。)

 X   Y
 1.0 1.5
 2.0 1.0
  ... 途中省略 ...
 3.3 2.1

 CorrCoef
 Print N EX EY SX SY RXY

しかし、データファイルを

 N    X   Y
 3470 1.0 1.5
      2.0 1.0
  ... 途中省略 ...
      3.3 2.1

 CorrCoef
 Print N EX EY SX SY RXY

のように N を X と Y と同時に指定すれば N が 3470 まで実行できるようになります。

なお、-DNO_SAVE とすると節約をする仕組みを組み込まなくなります。この場合プログラムサイズが 小さくなりますが、節約するようにデータファイルを書いても、効果が現れません。

以下の方法も効果があります。

 Reset X Y
 Add X 1.0 Y 1.5
 Add X 2.0 Y 1.0
 Add X 1.0 Y 1.7
  ... 途中省略 ...
 Add X 3.3 Y 2.1

 CorrCoef
 Print N EX EY SX SY RXY

この場合 N は 2048 まで大きくすることができます。

行列の const について(SwapRow)

例えば浮動小数点数行列の場合は、

typedef real*const RESTRICT*RESTRICT rmat;
のように宣言されます。これにより、不用意にポインタを書き換えるプログラムを コンパイル時にチェックできます。しかし、以下のようにポインタを使って二つの行を交換する プログラムもコンパイルエラーになってしまいます。
{ rvec t=A[i]; A[i]=A[j]; A[j]=t; }

そこで、行を交換するマクロを用意しました。上の例の場合は以下のように書き換えます。

SwapRow(A,i,j);

この SwapRow マクロは rmat の他に imat、cmat、smat、ivec、cvec、rvec、svec でも使うことができます。 可変列行列や cdef でもこのマクロで交換するプログラムをコンパイル出来ますが、 たいへん危険ですので、可変列行列や cdef に対してこのマクロは絶対使わないでください。
マクロ SwapRow() の実装は以下のようになっています。
前者が静的配列モード向けです。後者は通常のものです。

#if defined(STATIC) || defined(SEMISTATIC)
 #define SwapRow(A,I,J) \
 do{\
    struct t__{char d__[sizeof*(A)];}*a__=(struct t__*)(A);\
    char*i__=a__[I].d__,*j__=a__[J].d__,c__;size_t k__=sizeof*a__;\
    do{c__=i__[--k__];i__[k__]=j__[k__];j__[k__]=c__;}while(k__);\
 }while(0)
#else
 #define SwapRow(A,I,J) \
 do{\
    size_t i__=(size_t)(I),j__=(size_t)(J);\
    struct t__{char d__[sizeof*(A)];}\
    *a__=(struct t__*)(A),s__=a__[i__];\
    a__[i__]=a__[j__];a__[j__]=s__;\
 }while(0)
#endif

SwapRow() の引数 A が2箇所展開されており、一見すると SwapRow(Z[k++],i,j); のように使うと、副作用(++)が2回行われるように見えますが、sizeof*(A) は、 型の大きさを計算するだけで副作用は行われないので安全です。
静的配列モードで後者の SwapRow のままにすると問題が起きます。 例えば MCOL が 4096 で RMAT なら struct t__ の大きさが32キロバイトになります。 巨大な配列をスタックに確保するのは危険なので、 静的配列モードでは効率を犠牲にして前者の SwapRow にしています。

条件分岐や繰り返しがしたい場合

ポコムのデータファイルには条件分岐や繰り返しを記述できません。 しかし、これは致命的な欠陥ではありません。必要ならば、 C言語で関数を記述して登録し、それを呼び出せば解決できるからです。

ポコムでは、Print( )、Echo( )、Output( )、Load( ) 等、データファイルからでも、 C言語からでも呼び出せる便利な関数が用意されています。

連想配列の機能を実装する例

この章は長くなります。
CdefIndex(K,S) は CDEF:K に文字列:S が 既に登録されているなら、 その位置を返し、登録されてないなら -1 を返します。 まず、この CdefIndex(K,S) と AddTo???? を使用例を示します。
これは重複のある文字列ベクトルを入力とし、どの文字列が何回出現したか数え上げます。

#define FILE_NAME "Map.c"

#if defined(PDEF_H)

 INT(N,     3,   EJ("Size of data","入力データの大きさ"))
 SVEC(Data, 3,N, EJ("Input data",  "入力データ"))
 INT(MN,    3,   EJ("Size of Map", "連想配列の大きさ"))
 CDEF(Key,  3,MN,EJ("Key of Map",  "連想配列のキー"))
 IVEC(Value,3,MN,EJ("Value of Map","連想配列の値"))
 PROC(MapTest,   EJ("Test of Map", "連想配列テストの実行"))
 AUTO("Data")
 AUTO("Mon Mon Tue Wed Thu Fri Fri")
 AUTO("")
 AUTO("MapTest")

#else

 #include "../pocom.h"
 CHECK_FILE_NAME

 void MapTest(void) {
     Int i;
     NoList=1;
     Check("Data"); Reset("Key Value");
     for (i=0;i<N;i++) {
         Int idx=CdefIndex(Key,Data[i]);
         if (idx==-1) {
             idx=MN;
             AddToCdef(Key,Data[i]);
             AddToIvec(Value,0);
         }
         Value[idx]++;
     }
     Print("Key;曜日 Value;出現回数");
 }

#endif
/*
------- 出力結果 -----
 曜日 出現回数
 Mon         2
 Tue         1
 Wed         1
 Thu         1
 Fri         2
*/

上の MapTest 関数は、以下のように書き換えることができます。
CdefIndex(K,S) マクロの代わりに MapIndex(K,V,S) マクロを使って簡略化しています。
上のプログラムと同じ動作をします。

 void MapTest(void) {
     Int i;
     Check("Data"); Reset("Key Value");
     for (i=0;i<N;i++) {
         Int idx=MapIndex(Key,Value,Data[i]);
         Value[idx]++;
     }
     Print("Key;曜日 Value;出現回数");
 }

注意すべきこととして

Int idx=MapIndex(Key,Value,Data[i]);
Value[idx]++;

としているのを

Value[MapIndex(Key,Value,Data[i])]++; /* Error! */

のように書いてはいけません。 これは Visual C++ や Borland C++ では問題ありませんが、 gcc だとクラッシュします。
Visual C++ の場合、先に MapIndex(K,V,S) マクロが実行され、 新しい Value ポインターでアドレスが計算されるため意図した通り動作します。
しかし gcc の場合、 まず、初期の Value ポインター(null)がレジスタにロードされ、 その後、MapIndex(K,V,S) マクロが実行されます。
MapIndex(K,V,S) マクロが更新した後の正しい Value ポインタは無視されます。
結果、レジスタの null ポインターが指す場所をインクリメントしようとし、クラッシュします。

この問題を回避するために MapValue(K,V,S) マクロを用意しました。上の例の

Value[MapIndex(Key,Value,Data[i])]++; /* Error! */

は gcc でクラッシュしましたが、

MapValue(Key,Value,Data[i])++;

に書き換えれば gcc でもクラッシュせずに意図した通り動作します。
これは以下のように定義されています。

#define MapValue(K,V,S) (*(MapIndex(K,V,S),V+LastIndex))

LastIndex は 最後に実行した MapIndex(K,V,S) の返り値を保持したものです。

MapIndex(K,V,S)、MapValue(K,V,S) の K は、連想配列のキーを保持する文字列配列で CDEF 型でなくてはなりません。
V は連想配列の値で K と同じ列数変数を指定された
IVEC, CVEC, BVEC, TVEC, NVEC, LVEC, HVEC, FVEC, RVEC, DVEC, SVEC 変数の
いずれかではなくてはいけません。
S は連想配列から検索したり追加するキーで、必ず文字列でなくてはいけません。
連想配列の新しいキーで MapIndex(K,V,S) と MapValue(K,V,S) により V に追加される値の初期値は、
数値ベクトルの場合は0、文字列ベクトルの場合は長さ0の文字列(ZeroStr) です。
値 V が SVEC の場合、マップの値を直ちに新しい文字列で上書するのに便利なのが MapSetSvec(K,V,S,X) マクロです。
これは以下のように定義されています。

#define MapSetSvec(K,V,S,X) SetSvec(V,MapIndex(K,V,S),X)

以下に例を示します。 ※JP("日本語")の JP はUTF8モード・古いコンパイラで、
プログラムにシフトJIS文字列が埋め込まれた場合に、UTF8に変換するためにあります。
fprintf 関数はC言語の生の関数なので、JP がないと自動変換する機会が無くそのまま出力されてしまいます。
変数定義のコメントや MapValue(K,V,S) や MapSetSvec(K,V,S,X) の中の日本語は pocom.h がUTF8に変換します。
※ PRI32 は32ビット整数の表示するために、
16ビットコンパイラでは "l" で32/64ビットコンパイラでは空文字列となっています。

#define FILE_NAME "Map2.c"

#if defined(PDEF_H)

 INT(MN,      6,   "連想配列の大きさ")
 CDEF(Key,    6,MN,"連想配列のキー")
 NVEC(ValueNV,6,MN,"連想配列の値1")
 SVEC(ValueSV,6,MN,"連想配列の値2")
 PROC(MapTest,     "連想配列テストの実行")
 AUTO("MapTest")

#else

 #include "../pocom.h"
 CHECK_FILE_NAME

 void MapTest(void) {
     NoList=1;
     Reset("Key ValueNV");
     MapValue(Key,ValueNV,"abc")=1;
     MapValue(Key,ValueNV,"def")=2;
     MapValue(Key,ValueNV,"ghi")=3;
     fprintf(fp,JP(" #'ghi'の値は %" PRI32 "d です。\n"),
         MapValue(Key,ValueNV,"ghi"));
     fprintf(fp,JP(" #'jkl'の値は %" PRI32 "d です。\n"),
         MapValue(Key,ValueNV,"jkl"));
     Print("Key ValueNV");
     Reset("Key ValueSV");
     MapSetSvec(Key,ValueSV,"イロハ","いろは");
     MapSetSvec(Key,ValueSV,"ニホヘ","にほへ");
     MapSetSvec(Key,ValueSV,"トチリ","とちり");
     fprintf(fp,JP(" #'トチリ'の値は '%s' です。\n"),
         MapValue(Key,ValueSV,"トチリ"));
     fprintf(fp,JP(" #'ヌルヲ'の値は '%s' です。\n"),
         MapValue(Key,ValueSV,"ヌルヲ"));
     Print("Key ValueSV");
 }

#endif
/*
------------ 出力結果 -------------
 #'ghi'の値は 3 です。
 #'jkl'の値は 0 です。
 Key    ValueNV
 abc          1
 def          2
 ghi          3
 jkl          0

 #'トチリ'の値は 'とちり' です。
 #'ヌルヲ'の値は '' です。
 Key    ValueSV
 イロハ いろは 
 ニホヘ にほへ 
 トチリ とちり 
 ヌルヲ ''     

 */
MapIndex(K,V,S)、MapValue(K,V,S) の追加するキー S に数値を指定したい場合は、浮動小数点数なら RealStr(R) マクロ、
整数なら IntStr(I) マクロを使って文字列に変換する必要があります。
RealStr マクロなら引数として real, half, float, double を受け付けます。
IntStr マクロなら引数として
Int, char, unsigned char, short, unsigned short, int, unsigned, long, __int64, long long
を受け付けます。
コード型ならば、CodeStr, CvecStr, CmatStr, CjagStr マクロを使って文字列に変換する必要があります。
以下に例を示します。
#define FILE_NAME "Map3.c"

#if defined(PDEF_H)

 INT(N,     3,   EJ("Size of data","入力データの大きさ"))
 INT(MN,    3,   EJ("Size of Map", "連想配列の大きさ"))
 CDEF(Key,  3,MN,EJ("Key of Map",  "連想配列のキー"))

 SVEC(DataSV, 3,N,EJ("String data","文字列型データ"))
 AUTO("DataSV")
 AUTO("Mon Mon Tue Wed Thu Fri Fri")
 AUTO("")
 NVEC(ValueNV,3,MN,EJ("Value of Map","連想配列の値"))
 PROC(SvecTest,EJ("Test of Str","文字列型テスト"))
 AUTO("SvecTest")

 IVEC(DataIV, 3,N, EJ("Int data","整数型データ"))
 AUTO("DataIV")
 AUTO("100 100 200 300 400 500 500")
 AUTO("")
 IVEC(ValueIV,3,MN,EJ("Value of Map","連想配列の値"))
 PROC(IvecTest,EJ("Test of Int","整数型テスト"))
 AUTO("IvecTest")

 RVEC(DataRV, 103,N, EJ("Real data","浮動小数点型データ"))
 AUTO("DataRV")
 AUTO("1.1  1.1  2.2  3.3  4.4  5.5  5.5")
 AUTO("")
 RVEC(ValueRV,103,MN,EJ("Value of Map","連想配列の値"))
 PROC(RvecTest,EJ("Test of Real","浮動小数点型テスト"))
 AUTO("RvecTest")

 FIX(7)
 CDEF(Week, 3,FIX7,EJ("Week name","曜日の定義"))
 AUTO("Week")
 AUTO("MON TUE WED THU FRI SAT SUN")
 AUTO("")
 CVEC(DataCV,3,N,Week,EJ("Code data","コード型データ"))
 AUTO("DataCV")
 AUTO("MON MON TUE WED THU FRI FRI")
 AUTO("")
 SVEC(ValueSV,3,MN,EJ("Value of Map","連想配列の値"))
 PROC(CvecTest,EJ("Test of Code","コード型テスト"))
 AUTO("CvecTest")

#else

 #include "../pocom.h"
 CHECK_FILE_NAME

 void SvecTest(void) {
     Int i;
     NoList=1;
     Check("DataSV"); Reset("Key ValueNV");
     for (i=0;i<N;i++) MapValue(Key,ValueNV,DataSV[i])++;
     Print("Key");
     Print("ValueNV");
 }

 void IvecTest(void) {
     Int i;
     Check("DataIV"); Reset("Key ValueIV");
     for (i=0;i<N;i++) MapValue(Key,ValueIV,IntStr(DataIV[i]))++;
     Print("Key");
     Print("ValueIV");
 }

 void RvecTest(void) {
     Int i;
     Check("DataRV"); Reset("Key ValueRV");
     for (i=0;i<N;i++) MapValue(Key,ValueRV,RealStr(DataRV[i]))++;
     Print("Key");
     Print("ValueRV");
 }

 void CvecTest(void) {
     Int i;
     Check("DataCV"); Reset("Key ValueSV");
     for (i=0;i<N;i++) {
         char buf[100];
         Int idx=MapIndex(Key,ValueSV,CvecStr(DataCV,i));
         if (strlen(strcpy(buf,ValueSV[idx]))<sizeof(buf)-1)
            SetSvec(ValueSV,idx,strcat(buf,"*"));
     }
     Print("Key");
     Print("ValueSV");
 }

#endif
/*
----------- 出力結果 -------------
 Key
 Mon Tue Wed Thu Fri

 ValueNV
   2   1   1   1   2

 Key
 100 200 300 400 500

 ValueIV
   2   1   1   1   2

 Key
 1.1 2.2 3.3 4.4 5.5

 ValueRV
 2.0 1.0 1.0 1.0 2.0

 Key
 MON TUE WED THU FRI

 ValueSV
 **  *   *   *   ** 

*/
MapIndex(K,V,S) と MapValue(K,V,S) マクロは連想配列の値として一つだけのベクトル型しか指定できません。
二つの以上の配列を連想配列の値としたり、固定列・可変列行列型を連想配列の値としたい場合は、 手間がかかりますが、以下の例のように CdefIndex(K,S) マクロを使えば実装できます。
以下の連想配列の例は IVEC:ValueIV と SMAT:ValueSM の二つの値を持ちます。
#define FILE_NAME "Map4.c"

#if defined(PDEF_H)

 INT(N,     3,  EJ("Size of data","入力データの大きさ"))
 SVEC(Data1,3,N,EJ("Input data 1","入力データ1"))
 AUTO("Data1")
 AUTO("Mon Mon Tue Wed Thu Fri Fri")
 AUTO("")
 SVEC(Data2,3,N,EJ("Input data 2","入力データ2"))
 AUTO("Data2")
 AUTO("(1) (2) (3) (4) (5) (6) (7)")
 AUTO("")
 INT(MN,      3,     EJ("Size of Map",    "連想配列の大きさ"))
 CDEF(Key,    3,MN,  EJ("Key of Map",     "連想配列のキー"))
 IVEC(ValueIV,3,MN,  EJ("Value1 of Map",  "連想配列の値1"))
 INTF(W,      3,10,  EJ("Width of matrix","連想配列の値2の列数"))
 SMAT(ValueSM,3,MN,W,EJ("Value2 of Map",  "連想配列の値2"))
 SVEC(InitSV, 3,W,   EJ("Initial ValueSM","連想配列の値2の初期値"))
 PROC(MapTest,       EJ("Test of Map",    "テストの実行"))
 AUTO("MapTest")

#else

 #include "../pocom.h"
 CHECK_FILE_NAME

 void MapTest(void) {
     Int i;
     NoList=1;
     Check("Data1"); Reset("Key ValueIV ValueSM"); Alloc("InitSV");
     for (i=0;i<W;i++) SetSvec(InitSV,i,"---");
     for (i=0;i<N;i++) {
         Int idx=CdefIndex(Key,Data1[i]);
         if (idx==-1) {
             idx=MN;
             AddToCdef(Key,Data1[i]);
             AddToIvec(ValueIV,0);
             AddToSmat(ValueSM,W,InitSV);
         }
         if (ValueIV[idx]<W) SetSmat(ValueSM,idx,ValueIV[idx],Data2[i]);
         ValueIV[idx]++;
     }
     Print("Key ValueIV ValueSM");
 }

#endif
/*
--------- 出力結果 ----------
 Key ValueIV ValueSM
 Mon       2 (1) (2) --- --- --- --- --- --- --- --- ;
 Tue       1 (3) --- --- --- --- --- --- --- --- --- ;
 Wed       1 (4) --- --- --- --- --- --- --- --- --- ;
 Thu       1 (5) --- --- --- --- --- --- --- --- --- ;
 Fri       2 (6) (7) --- --- --- --- --- --- --- --- ;

 */
ポコムバージョン 7.03 にマップ関係で

を追加しました。CdefIndex2(P,S) は CDEF 型である P から文字列 S を検索し、 そのインデックスを返します。 文字列 S が見つからなかった場合 CdefIndex(P,S) が -1 を返すのに対し、 CdefIndex2(P,S) はエラーメッセージを出力してプログラムを終了します。
同様に MapValue2(K,V,S) は 文字列 S が K に見つからなければ エラーメッセージを出力してプログラムを終了します。
MapAdd(K,V,S,X) と MapAddSvec(K,V,S,X) は S がすでに K に登録されていると、 エラーメッセージを出力してプログラムを終了します。
以下に例を示します。

#define FILE_NAME "Map5.c"

#if defined(PDEF_H)

 INT(MN,      6,   "連想配列の大きさ")
 CDEF(Key,    6,MN,"連想配列のキー")
 NVEC(ValueNV,6,MN,"連想配列の値1")
 SVEC(ValueSV,6,MN,"連想配列の値2")
 PROC(MapTest,     "連想配列テストの実行")
 AUTO("MapTest")

#else

 #include "../pocom.h"
 CHECK_FILE_NAME

 void MapTest(void) {
     NoList=1;
     Reset("Key ValueNV");
     MapAdd(Key,ValueNV,"abc",1);
     MapAdd(Key,ValueNV,"def",2);
     MapAdd(Key,ValueNV,"ghi",3);
     /*
     MapAdd(Key,ValueNV,"def",4);
         AUTO(1)でエラーが発生!
         MapTest
         def:多重定義
     */
     fprintf(fp,JP(" #'ghi'の値は %" PRI32 "d です。\n"),
         MapValue2(Key,ValueNV,"ghi"));
     fprintf(fp,JP(" #'def'の値は %" PRI32 "d です。\n"),
         MapValue2(Key,ValueNV,"def"));
     Print("Key ValueNV");
     Reset("Key ValueSV");
     MapAddSvec(Key,ValueSV,"イロハ","いろは");
     MapAddSvec(Key,ValueSV,"ニホヘ","にほへ");
     MapAddSvec(Key,ValueSV,"トチリ","とちり");
     fprintf(fp,JP(" #'トチリ'の値は '%s' です。\n"),
         MapValue2(Key,ValueSV,"トチリ"));

     /*
     fprintf(fp,JP(" #'ヌルヲ'の値は '%s' です。\n"),
               MapValue2(Key,ValueSV,"ヌルヲ"));
         AUTO(1)でエラーが発生!
         MapTest
         ヌルヲ:定義'Key'にないコード名
     */

     Print("Key ValueSV");
 }

#endif
/*
------------ 出力結果 -------------
 #'ghi'の値は 3 です。
 #'def'の値は 2 です。
 Key    ValueNV
 abc          1
 def          2
 ghi          3

 #'トチリ'の値は 'とちり' です。
 Key    ValueSV
 イロハ いろは 
 ニホヘ にほへ 
 トチリ とちり 

 */

Free の使い道

例えば以下のようにベクトルの内積を求めるプログラムを考えます。 ただし、ベクトルYが読み込まれていない場合は単純にXの総和を求めるものとします。

#define FILE_NAME "ipro.c"

#if defined(PDEF_H)

 INT(N,4,     "ベクトルの大きさ")
 RVEC(X,408,N,"変数1")
 RVEC(Y,408,N,"変数2")
 REAL(Sum,408,"内積")
 PROC(ipro,   "内積を求める")

#else

 #include "../pocom.h"
 CHECK_FILE_NAME

 void ipro(void) {
     Int i;
     Check("X"); Sum=0;
     if (RowSize(Y)) {
         Check("Y");
         for (i=0;i<N;i++) Sum+=X[i]*Y[i];
     } else {
         for (i=0;i<N;i++) Sum+=X[i];
     }
 }

#endif

データファイルとして以下の場合を考えます。実はこのファイルの後半は失敗します。

 X Y
 1 3
 2 2
 3 1

 ipro

 Print Sum

 Y     # Xの総和を求めるためにYをクリアする

 ipro

 Print Sum

結果は以下のようになります。

 Sum
  10.0000

square.txt(12)でエラーが発生!

    ipro
Check("X")
       ^
X:'Y'によってサイズ'N'が 3 から 0 へ変化した

そこで以下のように Free を使って変数Yをクリアします。

 X Y
 1 3
 2 2
 3 1

 ipro

 Print Sum

 Free Y     # Xの総和を求めるためにYをクリアする

 ipro

 Print Sum

結果は以下のようになります。

 Sum
  10.0000

 Sum
   6.0000

Free はこのようなときに使います。
Free は指定した変数のメモリの解放を行い、RowSize(Y) を0にします。
この時、ベクトルのサイズ N を0に戻しません。

バージョン 7.06 以前のポコムでは Free によりポインタ Y は必ず0になりました。
バージョン 8.01 以降のポコムでは Y が0にならない場合があります。
STATIC、SEMISTATIC のいずれかを定義してコンパイルすると0になりません。
STATIC、SEMISTATIC を定義すると静的配列モードか準静的配列モートになり、 Y はプログラムの開始時から終了時まで同じ領域を指すからです。

Sort の使用例

Yamate.c:
#define FILE_NAME "Yamate.c"
#if defined(PDEF_H)
 FIX(9)
 FIX(23)
 CDEF(KU,12,FIX23,  EJ("Ku",            "区"))
 IVEC(NO, 2,FIX9,   EJ("Serial No.",    "番号"))
 CVEC(CV,12,FIX9,KU,EJ("Ku with",       "存在区"))
 IVEC(L,  3,FIX9,   EJ("Num",           "数"))
 SJAG(SJ, 8,L,      EJ("Stations of Ku","区別駅"))
 AUTO("Load Yamate.txt")
#else
 #define NO_OPEN
 #define NO_LIST
 #define NO_SYSTEM
 #include "../pocom.h"
 CHECK_FILE_NAME
#endif
Yamate.txt
 KU
 ちよだく   ちゅうおうく みなとく   しんじゅくく ぶんきょうく たいとうく
 すみだく   こうとうく   しながわく めぐろく     おおたく     せたがやく
 しぶやく   なかのく     すぎなみく としまく     きたく       あらかわく
 いたばしく ねりまく     あだちく   かつしかく   えどがわく


 NO  CV           SJ
   1 ちよだく     有楽町   東京             神田   秋葉原 ;
   2 たいとうく   御徒町   上野             鶯谷 ;
   3 あらかわく   日暮里   西日暮里 ;
   4 きたく       田端 ;
   5 としまく     駒込     巣鴨             大塚   池袋   目白 ;
   6 しんじゅくく 高田馬場 新大久保         新宿 ;
   7 しぶやく     代々木   原宿             渋谷   恵比寿 ;
   8 しながわく   目黒     五反田           大崎 ;
   9 みなとく     品川     高輪ゲートウェイ 田町     浜松町 新橋 ;

 Echo # Sort -a CV NO SJ 区の番号の順番で表示(千代田区~江戸川区の順)
 Sort -a CV NO SJ
 Print NO CV L SJ

 Echo # Sort -A CV NO SJ 区名のひらがなの順番で表示(あだちく~めぐろくの順)
 Sort -A CV NO SJ
 Print NO CV L SJ

 Echo # Sort -d L CV NO SJ 駅数の多い区から順に表示
 Sort -d L NO CV SJ
 Print NO CV L SJ

 Echo #  Sort NO CV SJ 元データに戻す(有楽町~新橋の駅並び順)
 Sort NO CV SJ
 Print NO CV L SJ
結果:
# Sort -a CV NO SJ 区の番号の順番で表示(千代田区~江戸川区の順)
 NO CV           L   SJ
  1 ちよだく       4 有楽町   東京     神田     秋葉原   ;
  9 みなとく       5 品川     高輪ゲートウェイ 田町     浜松町   新橋     ;
  6 しんじゅくく   3 高田馬場 新大久保 新宿     ;
  2 たいとうく     3 御徒町   上野     鶯谷     ;
  8 しながわく     3 目黒     五反田   大崎     ;
  7 しぶやく       4 代々木   原宿     渋谷     恵比寿   ;
  5 としまく       5 駒込     巣鴨     大塚     池袋     目白     ;
  4 きたく         1 田端     ;
  3 あらかわく     2 日暮里   西日暮里 ;

# Sort -A CV NO SJ 区名のひらがなの順番で表示(あだちく~めぐろくの順)
 NO CV           L   SJ
  3 あらかわく     2 日暮里   西日暮里 ;
  4 きたく         1 田端     ;
  8 しながわく     3 目黒     五反田   大崎     ;
  7 しぶやく       4 代々木   原宿     渋谷     恵比寿   ;
  6 しんじゅくく   3 高田馬場 新大久保 新宿     ;
  2 たいとうく     3 御徒町   上野     鶯谷     ;
  1 ちよだく       4 有楽町   東京     神田     秋葉原   ;
  5 としまく       5 駒込     巣鴨     大塚     池袋     目白     ;
  9 みなとく       5 品川     高輪ゲートウェイ 田町     浜松町   新橋     ;

# Sort -d L CV NO SJ 駅数の多い区から順に表示
 NO CV           L   SJ
  5 としまく       5 駒込     巣鴨     大塚     池袋     目白     ;
  9 みなとく       5 品川     高輪ゲートウェイ 田町     浜松町   新橋     ;
  7 しぶやく       4 代々木   原宿     渋谷     恵比寿   ;
  1 ちよだく       4 有楽町   東京     神田     秋葉原   ;
  2 たいとうく     3 御徒町   上野     鶯谷     ;
  8 しながわく     3 目黒     五反田   大崎     ;
  6 しんじゅくく   3 高田馬場 新大久保 新宿     ;
  3 あらかわく     2 日暮里   西日暮里 ;
  4 きたく         1 田端     ;

#  Sort NO CV SJ 元データに戻す(有楽町~新橋の駅並び順)
 NO CV           L   SJ
  1 ちよだく       4 有楽町   東京     神田     秋葉原   ;
  2 たいとうく     3 御徒町   上野     鶯谷     ;
  3 あらかわく     2 日暮里   西日暮里 ;
  4 きたく         1 田端     ;
  5 としまく       5 駒込     巣鴨     大塚     池袋     目白     ;
  6 しんじゅくく   3 高田馬場 新大久保 新宿     ;
  7 しぶやく       4 代々木   原宿     渋谷     恵比寿   ;
  8 しながわく     3 目黒     五反田   大崎     ;
  9 みなとく       5 品川     高輪ゲートウェイ 田町     浜松町   新橋     ;

half の使用例

ポコム version 4.01 からC++で半精度浮動小数点数(half)が使用可能になりました。 HALF_REAL を0以外に定義すれば REAL,RVEC,RMAT,RJAG が half になります。 また、ならば HALF,HVEC,HMAT,HJAG も使用可能です。 C++ではないC言語の場合では半精度浮動小数点数(half)は使用不可です。
例えば Turbo C++ Version 4.02 の場合 Example1.c をコンパイルするときに、
C:\... > tcc -DHALF_REAL Example1.c
とすると失敗しますが。
C:\... > tcc -DHALF_REAL -P Example1.c
とするとコンパイルができます。-P は拡張子が .c でもC++としてコンパイルする オプションです。 Visual C++ では -P オプションの代わりに /Tp オプションを指定します。 gcc の場合は gcc を使う代わりに g++ を使うと .c ファイルでもC++ でコンパイルできます。

不定文字列について

ここでは、ウィンドウズの古いC++コンパイラでUTF8モードの注意すべきことについて説明します。

シフトJIS文字列の中にはUTF8文字列としても正しいと判定されるものがあります。 以降ではこれを「不定文字列」と呼びます。
これが原因でポコムが正しく結果を表示できなくなる場合が稀にあります。
半角カナ文字列を多用するとリスクが高まくなります。
例えば マズイ 、レイ 、フォロー 、メス 、ネコ 、ナギサ などです。 以降これらを「不定カナ文字列」と呼びます。不定カナ文字列(タイプh2h1)の条件は、

h2:
ツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン゙゚
のどれかで始まり、その次に
h1:
。「」、・ヲァィゥェォャュョッーアイウエオカキクケコサシスセソ
のどれかが続く半角カナ2文字の繰り返しです。
不定カナ文字列のタイプ h2h1 の意味は 'h' は半角文字を表し、 後で説明する不定全角文字列のでの 'z' は全角文字を表します。
'1' や '2' は文字コードの範囲を表します。
0:0~127
1:128~191
2:192~223
3:224~239
4:240~247

メス と ネコ を連結して メスネコ としても不定文字列となります。 また <メス> のようにアスキー文字で囲んでも不定文字列となります。 一方 メスイヌ , メス猫 , <メス> のように不定文字列の前後に 不定文字列以外のカナ文字列や全角文字列がある場合は 非不定文字列となり、問題は起こりません。
不定文字列かどうかを確認するには「メモ帳」に調べたい文字列だけを書き、 「名前を付けて保存」で「文字コード」を「ANSI」にして保存します。 再度、その保存したファイルを「メモ帳」で開いて文字化けしていれば不定文字列です。 このように「メモ帳」も同じような不具合を持っています。

プログラムに読み込ませるデータファイルに不定文字列があっても、 それがUTF8ファイルだったり、 シフトJISの場合でもデータファイル内に不定文字列が含まれる最初の行かそれ以前の行に 非不定文字列があれば問題は起きません。 (コメントも文字コード判別の対象なので、例えば先頭行に '# あ' と書いておけば確実。) また AUTO , LAST , マクロ内のテキストや、変数のコメントの場合はどこに不定文字列があっても 問題は起きません。
しかし、古いウィンドウズのC++コンパイラ (Borland C++、2008以前の VC++、VC++ 2012 等)でUTF8モードにして コンパイルする時に、計算ルーチン内に不定文字列が埋め込まれていると、 正しく結果を表示することができなくなる場合があります。
従って、プログラムに埋め込む文字列に半角カナを多用するのは推奨しません。
例えば VC++ 2005 、UTF8モードにて、IVEC(ニセ,5,N,"あいうえお") のように宣言して、 Alloc("ニセ"); とするとエラーになります。この場合 Alloc("ニセ #あ"); のようにすると、 エラーは起こらなくなります。

どうしても、古いコンパイラを使用して不定カナ文字列を埋め込みたい場合は、 マクロ PRIOR_KANA を定義してコンパイルします。
ただし、この場合2バイトUTF8文字の一部が シフトJISと誤判定されてしまう副作用があります。
1920文字ある2バイトUTF8文字のうち、930文字が副作用として シフトJISカナ文字と誤判定されます。 副作用で誤判定される2バイトUTF8文字930文字のうち、 シフトJIS文字になる文字は以下の61文字です。

§¨°±´¶÷ΡΣΤΥΦΧΨΩαβγδεζηθικλμνξοСТ
УФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмноп

古いコンパイラで WINUTF8 と PRIOR_KANA を定義してこの文字がプログラムに埋め込まれた場合、 以下の表の対応する不定カナ文字列に変換される可能性があります。

§ ツァ   ¨ ツィ   ° ツー   ± ツア   ´ ツエ   ¶ ツカ   ÷ テキ   Ρ ホ。   Σ ホ」   
Τ ホ、   Υ ホ・   Φ ホヲ   Χ ホァ   Ψ ホィ   Ω ホゥ   α ホア   β ホイ   γ ホウ   
δ ホエ   ε ホオ   ζ ホカ   η ホキ   θ ホク   ι ホケ   κ ホコ   λ ホサ   μ ホシ   
ν ホス   ξ ホセ   ο ホソ   С ミ。   Т ミ「   У ミ」   Ф ミ、   Х ミ・   Ц ミヲ   
Ч ミァ   Ш ミィ   Щ ミゥ   Ъ ミェ   Ы ミォ   Ь ミャ   Э ミュ   Ю ミョ   Я ミッ   
а ミー   б ミア   в ミイ   г ミウ   д ミエ   е ミオ   ж ミカ   з ミキ   и ミク   
й ミケ   к ミコ   л ミサ   м ミシ   н ミス   о ミセ   п ミソ   

従って古いコンパイラを使用してUTF8モードで埋め込む文字に、 不定カナ文字列と上の61文字は共存はできません。

不定カナ文字列と似たようなものに「不定全角文字列」もあります。
例えば「骰子」(さいころ)です。 これも不定カナ文字列と同様に古いコンパイラで不具合を起こします。
不定全角文字列(タイプz31z10)の条件は、

z31:
燿爍爐爛爨爭爬爰爲爻爼爿牀牆牋牘牴牾犂犁犇犒犖犢犧犹犲狃狆狄狎狒痼瘁痰痺痲痳
瘋瘍瘉瘟瘧瘠瘡瘢瘤瘴瘰瘻癇癈癆癜癘癡癢癨癩癪癧癬癰癲癶癸發皀皃皈皋皎皖皓皙皚
皰皴皸皹皺盂盍盖盒盞盡盥盧盪蘯盻眈眇眄眩窶竅竄窿邃竇竊竍竏竕竓站竚竝竡竢竦竭
竰笂笏笊笆笳笘笙笞笵笨笶筐筺笄筍笋筌筅筵筥筴筧筰筱筬筮箝箘箟箍箜箚箋箒箏筝箙
篋篁篌篏箴篆篝篩縲縺繧繝繖繞繙繚繹繪繩繼繻纃緕繽辮繿纈纉續纒纐纓纔纖纎纛纜缸
缺罅罌罍罎罐网罕罔罘罟罠罨罩罧罸羂羆羃羈羇羌羔羞羝羚羣羯羲羹羮羶羸譱艢艨艪艫
舮艱艷艸艾芍芒芫芟芻芬苡苣苟苒苴苳苺莓范苻苹苞茆苜茉苙茵茴茖茲茱荀茹荐荅茯茫
茗茘莅莚莪莟莢莖茣莎莇莊荼莵荳荵莠莉莨菴萓菫蛟蛛蛯蜒蜆蜈蜀蜃蛻蜑蜉蜍蛹蜊蜴蜿
蜷蜻蜥蜩蜚蝠蝟蝸蝌蝎蝴蝗蝨蝮蝙蝓蝣蝪蠅螢螟螂螯蟋螽蟀蟐雖螫蟄螳蟇蟆螻蟯蟲蟠蠏
蠍蟾蟶蟷蠎蟒蠑蠖蠕蠢諤諱謔諠諢諷諞諛謌謇謚諡謖謐謗謠謳鞫謦謫謾謨譁譌譏譎證譖
譛譚譫譟譬譯譴譽讀讌讎讒讓讖讙讚谺豁谿豈豌豎豐豕豢豬豸豺貂貉貅貊貍貎貔豼轢轣
轤辜辟辣辭辯辷迚迥迢迪迯邇迴逅迹迺逑逕逡逍逞逖逋逧逶逵逹迸遏遐遑遒逎遉逾遖遘
遞遨遯遶隨遲邂遽邁邀邊邉邏邨邯邱邵郢郤扈郛鄂鄒鄙閠閨閧閭閼閻閹閾闊濶闃闍闌闕
闔闖關闡闥闢阡阨阮阯陂陌陏陋陷陜陞陝陟陦陲陬隍隘隕隗險隧隱隲隰隴隶隸隹雎雋雉
雍襍雜霍雕雹霄霆霈霓霎霑騾驕驍驛驗驟驢驥驤驩驫驪骭骰骼髀髏髑髓體髞髟髢髣髦髯
髫髮髴髱髷髻鬆鬘鬚鬟鬢鬣鬥鬧鬨鬩鬪鬮鬯鬲魄魃魏魍魎魑魘魴鮓鮃鮑鮖鮗鮟鮠鮨鮴鯀
黴黶黷黹黻黼黽鼇鼈皷鼕鼡鼬鼾齊齒齔齣齟齠齡齦齧齬齪齷齲齶龕龜龠堯槇遙瑤凜熙

の645文字のどれかで始まり、その次に

z10:
 、。,.・:;?!゛゜´`¨^ ̄_ヽヾゝゞ〃仝々〆〇ー―‐/\~∥|…‥‘
’“”()〔〕[]{}〈〉《》「」『』【】+-±×0123456789ABC
DEFGHIJKLMNOPQRSTUVWXYZァアィイゥウェエォオカガキギク
グケゲコゴサザシジスズセゼソゾタダチヂッツヅテデトドナニヌネノハバパヒビピフ
ブプヘベペホボポマミАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪ
ЫЬЭЮЯабвгдеёжзийклмн①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱
⑲⑳ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩ㍉㌔㌢㍍㌘㌧㌃㌶㍑㍗㌍㌦㌣㌫㍊㌻㎜㎝㎞㎎㎏㏄㎡㍻院陰
隠韻吋右宇烏羽迂雨卯鵜窺丑碓臼渦嘘唄欝蔚鰻姥厩浦瓜閏噂云運雲荏餌叡営嬰影映曳
栄永泳洩瑛盈穎頴英衛詠鋭液疫益駅悦謁越閲榎厭円魁晦械海灰界皆絵芥蟹開階貝凱劾
外咳害崖慨概涯碍蓋街該鎧骸浬馨蛙垣柿蛎鈎劃嚇各廓拡撹格核殻獲確穫覚角赫較郭閣
隔革学岳楽額顎掛笠樫機帰毅気汽畿祈季稀紀徽規記貴起軌輝飢騎鬼亀偽儀妓宜戯技擬
欺犠疑祇義蟻誼議掬菊鞠吉吃喫桔橘詰砧杵黍却客脚虐逆丘久仇休及吸宮弓急救掘窟沓
靴轡窪熊隈粂栗繰桑鍬勲君薫訓群軍郡卦袈祁係傾刑兄啓圭珪型契形径恵慶慧憩掲携敬
景桂渓畦稽系経継繋罫茎荊蛍計詣警軽頚鶏芸迎鯨后喉坑垢好孔孝宏工巧巷幸広庚康弘
恒慌抗拘控攻昂晃更杭校梗構江洪浩港溝甲皇硬稿糠紅紘絞綱耕考肯肱腔膏航荒行衡講
貢購郊酵鉱砿鋼閤降察拶撮擦札殺薩雑皐鯖捌錆鮫皿晒三傘参山惨撒散桟燦珊産算纂蚕
讃賛酸餐斬暫残仕仔伺使刺司史嗣四士始姉姿子屍市師志思指支孜斯施旨枝止宗就州修
愁拾洲秀秋終繍習臭舟蒐衆襲讐蹴輯週酋酬集醜什住充十従戎柔汁渋獣縦重銃叔夙宿淑
祝縮粛塾熟出術述俊峻春瞬竣舜駿准循旬楯殉淳拭植殖燭織職色触食蝕辱尻伸信侵唇娠
寝審心慎振新晋森榛浸深申疹真神秦紳臣芯薪親診身辛進針震人仁刃塵壬尋甚尽腎訊迅
陣靭笥諏須酢図厨繊羨腺舛船薦詮賎践選遷銭銑閃鮮前善漸然全禅繕膳糎噌塑岨措曾曽
楚狙疏疎礎祖租粗素組蘇訴阻遡鼠僧創双叢倉喪壮奏爽宋層匝惣想捜掃挿掻叩但達辰奪
脱巽竪辿棚谷狸鱈樽誰丹単嘆坦担探旦歎淡湛炭短端箪綻耽胆蛋誕鍛団壇弾断暖檀段男
談値知地弛恥智池痴稚置致蜘遅馳築畜竹筑蓄邸鄭釘鼎泥摘擢敵滴的笛適鏑溺哲徹撤轍
迭鉄典填天展店添纏甜貼転顛点伝殿澱田電兎吐堵塗妬屠徒斗杜渡登菟賭途都鍍砥砺努
度土奴怒倒党冬如尿韮任妊忍認濡禰祢寧葱猫熱年念捻撚燃粘乃廼之埜嚢悩濃納能脳膿
農覗蚤巴把播覇杷波派琶破婆罵芭馬俳廃拝排敗杯盃牌背肺輩配倍培媒梅鼻柊稗匹疋髭
彦膝菱肘弼必畢筆逼桧姫媛紐百謬俵彪標氷漂瓢票表評豹廟描病秒苗錨鋲蒜蛭鰭品彬斌
浜瀕貧賓頻敏瓶不付埠夫婦富冨布府怖扶敷法泡烹砲縫胞芳萌蓬蜂褒訪豊邦鋒飽鳳鵬乏
亡傍剖坊妨帽忘忙房暴望某棒冒紡肪膨謀貌貿鉾防吠頬北僕卜墨撲朴牧睦穆釦勃没殆堀
幌奔本翻凡盆諭輸唯佑優勇友宥幽悠憂揖有柚湧涌猶猷由祐裕誘遊邑郵雄融夕予余与誉
輿預傭幼妖容庸揚揺擁曜楊様洋溶熔用窯羊耀葉蓉要謡踊遥陽養慾抑欲蓮連錬呂魯櫓炉
賂路露労婁廊弄朗楼榔浪漏牢狼篭老聾蝋郎六麓禄肋録論倭和話歪賄脇惑枠鷲亙亘鰐詫
藁蕨椀湾碗腕僉僊傳僂僖僞僥僭僣僮價僵儉儁儂儖儕儔儚儡儺儷儼儻儿兀兒兌兔兢竸兩
兪兮冀冂囘册冉冏冑冓冕冖冤冦冢冩冪冫决冱冲冰况冽凅凉凛几處凩凭咫哂咤咾咼哘哥
哦唏唔哽哮哭哺哢唹啀啣啌售啜啅啖啗唸唳啝喙喀咯喊喟啻啾喘喞單啼喃喩喇喨嗚嗅嗟
嗄嗜嗤嗔嘔嗷嘖嗾嗽嘛嗹噎噐營嘴嘶嘲嘸奸妁妝佞侫妣妲姆姨姜妍姙姚娥娟娑娜娉娚婀
婬婉娵娶婢婪媚媼媾嫋嫂媽嫣嫗嫦嫩嫖嫺嫻嬌嬋嬖嬲嫐嬪嬶嬾孃孅孀孑孕孚孛孥孩孰孳
孵學斈孺宀廖廣廝廚廛廢廡廨廩廬廱廳廰廴廸廾弃弉彝彜弋弑弖弩弭弸彁彈彌彎弯彑彖
彗彙彡彭彳彷徃徂彿徊很徑徇從徙徘徠徨徭徼忖忻忤忸忱忝悳忿怡恠戞戡截戮戰戲戳扁
扎扞扣扛扠扨扼抂抉找抒抓抖拔抃抔拗拑抻拏拿拆擔拈拜拌拊拂拇抛拉挌拮拱挧挂挈拯
拵捐挾捍搜捏掖掎掀掫捶掣掏掉掟掵捫曄暸曖曚曠昿曦曩曰曵曷朏朖朞朦朧霸朮朿朶杁
朸朷杆杞杠杙杣杤枉杰枩杼杪枌枋枦枡枅枷柯枴柬枳柩枸柤柞柝柢柮枹柎柆柧檜栞框栩
桀桍栲桎檗蘗檻櫃櫂檸檳檬櫞櫑櫟檪櫚櫪櫻欅蘖櫺欒欖鬱欟欸欷盜欹飮歇歃歉歐歙歔歛
歟歡歸歹歿殀殄殃殍殘殕殞殤殪殫殯殲殱殳殷殼毆毋毓毟毬毫毳毯

の1701文字のどれかが続く2文字の繰り返しです。
不定全角文字列(タイプz31z10)は 645×1701=1,097,145 種類あり、 そのうちUTF8とみなした結果がシフトJIS文字列になるものは 190,533 種類です。 不定全角文字列の最初の文字 z31 は、JIS第2水準漢字の後半なので通常の熟語では 滅多に現れることはありません。これらは当用漢字や常用漢字にも含まれません。 国語辞書にある z31(「燿」~「熙」)で始まる全ての熟語、 千個以上を調べたところ、以下の81個が不定全角文字列となりました。 全てがタイプ z31z10 で他のタイプはありませんでした。

爛熟 らんじゅく 爛々 らんらん   犇々 ひしひし   狒々 ひひ       痰吐 たんはき
瘡蓋 かさぶた   瘡気 かさけ     瘡掻 かさかき   瘴気 しょうき   癈人 はいじん
癩病 らいびょう 皓々 こうこう   皚々 がいがい   皴法 しゅんぽう 皺々 しわしわ
眈々 たんたん   眩惑 げんわく   笊法 ざるほう   筮竹 ぜいちく   繻子 しゅす
罨法 あんぽう   羞恥 しゅうち   羚羊 かもしか   芬々 ふんぷん   茫然 ぼうぜん
茫洋 ぼうよう   茫々 ぼうぼう   茘枝 れいし     菫色 すみれいろ 蜀黍 もろこし
蜀江 しょっこう 蜿々 えんえん   蝟集 いしゅう   蝗害 こうがい   蠅叩 はえたたき
蟇蛙 ひきがえる 蟶貝 まてがい   諤々 がくがく   諷詠 ふうえい   諷刺 ふうし
諷諭 ふうゆ     謦咳 けいがい   譬喩 ひゆ(比喩) 讒臣 ざんしん   讒訴 ざんそ
豁然 かつぜん   豎子 じゅし     豺狼 さいろう   轢殺 れきさつ   轢断 れきだん
辣韮 らっきょう 辣腕 らつわん   逍遥 しょうよう 迸出 へいしゅつ 邁進 まいしん
扈従 こじゅう   閨秀 けいしゅう 閨房 けいぼう   闊達 かったつ   陋巷 ろうこう
陋習 ろうしゅう 隘路 あいろ     隕鉄 いんてつ   雉子 きぎす     雉笛 きじぶえ
雹害 ひょうがい 騾馬 らば       驟雨 しゅうう   驢馬 ろば       骰子 さいころ
鬘師 かつらし   鬢掻 びんかき   鬢付 びんつけ   鮨詰 すしづめ   黴臭 かびくさい
鼈甲 べっこう   齣絵 こまえ     齧付 かじりつく 凜然 りんぜん   凜冽 りんれつ
凜々 りりしい

このうち、以下の17個がUTF8とみなした結果がシフトJIS文字列になります。

菫色 俐F    蜀江 再]    諷刺 慎h    讒臣 槐b    豎子 汎q    豺狼 淘T    閨房 聖[
闊達 舒B    陋巷 蛍J    隕鉄 覓S    雉子 賎q    雉笛 賓J    雹害 蹊Q    騾馬 途n
驟雨 酉J    鬘師 顎t    鮨詰 齋l

普通「羞恥」の直後には「心」が続き「羞恥心」となるので、 「羞恥」が不定全角文字列になり不具合が起こるリスクはありません。 また、「凜」は常用漢字の「凛」にすれば不具合が起こるリスクはありません。
ただし、国語辞書に載っていない人名・地名などの固有名詞には、 多数の不定全角文字列があると思われます。 この不定全角文字列を古いコンパイラを使用してUTF8モードでプログラムに埋め込む時は、 マクロ PRIOR_SJIS を定義してコンパイルします。 この場合、プログラムは不定全角文字列(タイプz31z10)をシフトJISとして処理します。 これも PRIOR_KANA と同様に副作用を伴うので推奨しません。

不定全角文字列にはタイプz31z10以外に 長さが3以上のもっと複雑な条件を持った別のタイプが存在します。 ただし、国語辞書には該当するものが無かったので、実際に目にすることはないと思われます。 以下に意図的に作り出した不定全角文字列の例を挙げます。

z31z12z10:蝓侑益  蟯紬嚇  齪嶄Н  蜉旃弖  眩再益  霈鉦慎  蜩衿⑤
z31z13z11:齦滓漫  讚区勧  苳廷刹  痲樞く  舮玖溜  笞夂Γ  站俾№ 
z31z14z11z10:逎奉ず彦  熙岐婿掘  迥烈さ雨  髏暈嗇田  皺歴跳K  艱拓∪Х
z31z14z11z13z11:箒印萩夜▼  霈候順川緒  遉列从介銚  齟汀泅堺協  莎酒亊貞亠

さらに、半角文字と全角文字を組み合わせた「不定混合文字列」もあります。

h2z10:フ鮮  ロ軍  メ價  ル紅  レв  フ波  ツ冲  ワ夕  ワ貝  ニ朞  ホ后  ヘ冏
z31h1:逹ア  豬ョ  蛹エ  蟒ェ  髟サ  黻イ  縲」  證ク  皃ョ  犖、  鮗ヲ  謳ス
h2z12z10:リ緬唹  ル淌歛  リ壥奔  ホ間コ  フ氾必  ノ観Г  テ寨塑  ニ俑薩
z31z12h1:鬆替イ  竕厮ッ  鬆敲オ  諢圃シ  諛杢ウ  霈祭ヲ  遶嵎「  皈詳オ

乱数

C/C++や JAVA で用意されている乱数には、 問題があることがわかっています。 例えば以下のプログラムを JAVA で実行すると、

public class RandTest {
    static java.util.Random r=new java.util.Random();
    public static void main (String[] args) {
        double x,Min=1,Max=0;
        for (int i=0;i<1024;i++) {
            r.setSeed(i); x=r.nextDouble();
            if (x>Max) Max=x;
            if (x<Min) Min=x;
        }
        System.out.println("Min="+Min+" Max="+Max);
    }
}

結果は、

Min=0.6753377750582709 Max=0.7669794809891215

となります。

また、C/C++の rand( ) は、精度が 15 bit しかなく、 最下位ビットの周期も 131072 しかありません。 ゲームでは役に立ちますが、 多くの数値計算では悪い結果をもたらすでしょう。 UNIX の lrand48( ) の最下位ビットの系列は、 どんな種を指定しても、4種類しかありません。 最下位ビットの周期は lrand48( ) でも 131072 しかありません。

数値計算などでは、よく乱数が使われるにもかかわらず、 標準で用意されている乱数には、良い物がないので、 ポコムは質の高い乱数発生プログラムを搭載しました。 ポコムの乱数は TinyMT という、以下の文献の方法を使っています。

http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/TINYMT/index-jp.html

これは統計的検定もよくされています。 メルセンヌ・ツイスターよりもメモリの使用量が小さいのが特徴です。

静的配列モード

Ver. 8.01 以降のポコムで STATIC を定義してコンパイルをすると、 静的配列モードになります。
Ver. 8.01 以降のポコムで SEMISTATIC を定義してコンパイルをすると、 準静的配列モードになります。
Ver. 7.07 以前のポコムや Ver. 8.01 以降で STATIC や SEMISTATIC を定義しないで コンパイルした場合を動的配列モードと呼ぶことにします。

※静的配列モードや準静的配列モードは基本的に推奨しません。
しかし、ALIGN_REAL の効果のベンチマークの結果の通り、 配列のサイズがわかっていれば、コンパイラによっては効果があります。
例えば、Example3 の場合は Visual C++ では STATIC を定義すると速くなりましたが、 gcc, g++ では STATIC を定義すると遅くなりました。

例えば、

INT( N,  2,    EJ("Num of data","数"))
IVEC(L,  2,N,  EJ("Num of jag col","可変列行列の列数"))
RMAT(A,307,N,N,EJ("Matrix","行列"))
RJAG(J,307,L,  EJ("Jag matrix","可変列行列"))

とすると、従来の動的配列モードの場合

 Int N,*L; real**A,**J;

のように宣言されます。そして配列はヒープ上に必要最低限なだけ確保されます。
一方、静的配列モードの場合は、

Int N,L[VROW]; real A[MROW][MCOL],J[JROW][JCOL];

のように宣言されます。配列は静的に確保されます。
STATIC の代わりに SEMISTATIC を定義してコンパイルすると、 準静的配列モードとなり

Int N,*L; real(*A)[MCOL],(*J)[JCOL];

のように宣言されます。 そして、プログラムの開始時に静的配列モードと同じサイズの領域がヒープ上に確保されます。 プログラムの終了時まで配列の領域の場所は変化しません。
VROW, MROW, MCOL, JROW, JCOL のデフォルト値は以下のように定義されています。

(MODEL_SIZE(A,B,C,D) の引数の意味は以下のとおりです。)
A:16ビットコンパイラのスモールモデル、ミィデアムモデル
B:16ビットコンパイラのコンパクトモデル、ラージモデル
C:32ビットコンパイラ
D:64ビットコンパイラ

#if defined(STATIC)
 #if !defined(VROW)
  #define VROW MODEL_SIZE(64,64,8192,65536)
 #endif
 #if !defined(MROW)
  #define MROW MODEL_SIZE(16,16,256,2048)
 #endif
 #if !defined(MCOL)
  #define MCOL MODEL_SIZE(16,16,256,1024)
 #endif
 #if !NO_JAG
  #if !defined(JROW)
   #define JROW MODEL_SIZE(16,16,256,2048)
  #endif
  #if !defined(JCOL)
   #define JCOL MODEL_SIZE(12,12,128,512)
  #endif
 #endif
#endif

#if defined(SEMISTATIC)
 #if !defined(VROW)
  #define VROW MODEL_SIZE(64,256,8192,65536)
 #endif
 #if !defined(MROW)
  #define MROW MODEL_SIZE(16,64,256,2048)
 #endif
 #if !defined(MCOL)
  #define MCOL MODEL_SIZE(16,64,256,1024)
 #endif
 #if !NO_JAG
  #if !defined(JROW)
   #define JROW MODEL_SIZE(16,64,256,2048)
  #endif
  #if !defined(JCOL)
   #define JCOL MODEL_SIZE(12,48,128,512)
  #endif
 #endif
#endif

VROW, MROW, MCOL, JROW, JCOL のデフォルト値の 静的配列モードと準静的配列モードの違いは B だけです。 静的配列モードの場合 B が A と同じな理由は、 全ての配列の大きさを合計して64Kバイト超える宣言を 一つのオブジェクトファイルにまとめることが出来ないからです。
16ビットコンパイラのコンパクトモデルやラージモデルの場合、 STATIC の代わりに SEMISTATIC にすると合計が64Kバイトを超えることが できるようになります。

次の場合、STATIC を定義すると自動的に STATIC は #undef され、 「#define SEMISTATIC」が行われます。

・Borland C++、VC++5.0、VC++6.0 で ALIGN_MEM が0以外だったり、ALIGN_REAL が定義されている場合

なぜなら、これら古いコンパイラは配列の宣言をするときに、 アライメントを指定できないからです。 準静的配列モードの場合、ポコムの GetMem() がアライメントを行います。

VROW, MROW, MCOL, JROW, JCOL は「#include "../pocom.h"」 の前の位置に

#define VROW 50

のように定義することにより変更可能です。

例えば VROW, MROW, MCOL, JROW, JCOL を指定しないでデフォルトのまま、 Mva.c をコンパイルした時の静的配列の合計サイズは以下のようになります。

64ビットコンパイラで多くの場合余裕があります。 大きくしなかった理由は MCOL と JCOL を大きくしすぎると Sort 関数や SwapRow 関数 の性能が悪くなるからです。

注意点

(1)RowSize()
Free の使い道では配列が空かどうかを調べるのに、

if (RowSize(Y)) ...

としています。これを動的配列モードでは、

if (Y) ...

と書いても問題はありませんでした。しかし静的配列モードと準静的配列モードでは、 Y は常に同じ領域を指しているので、「if (Y) 」は常に真となり バグとなります。 必ず「if (RowSize(Y))」のように RowSize() を使ってください。

(2)関数の行列引数
例えば、正方行列の全要素の総和を返す関数 SumOfMatrix() を考えます。

real SumOfMatrix(rmat a,Int n) {
    Int i,j; real sum=0;
    for (i=0;i<n;i++) for (j=0;j<n;j++) sum+=a[i][j];
    return sum;
}
    ...
    real SumOfA=SumOfMatrix(A,N);
    ...

上の宣言は動的配列モードでも静的配列モードでも準静的配列モードでも問題ありません。 しかし、宣言を

real SumOfMatrix(real**a,Int n) {
    ...
    real SumOfA=SumOfMatrix(A,N);
    ...

にすると、動的配列モードでは問題なくても、静的配列モードと準静的配列モードでは コンパイルエラーとなります。

(3)Alloc()
静的配列モードと準静的配列モードでは関数 Alloc() の役割が変わります。
動的配列モードで Alloc() は配列の解放と確保と初期化をしていました。
静的配列モードと準静的配列モードで Alloc() は配列の解放と確保はしません。
代わりに初期化のみ行います。
数値型配列ならば領域を0で埋めます。
文字列型配列ならば古い文字列の解放し、 配列の各要素を空文字列(ZeroStr)に設定します。

(4)アライメントされない配列
数値型の配列は ALIGN_MEM が0以外ならアライメントされます。
文字列型配列など(SVEC,CDEF,SMAT,SJAG)はアライメントしません。

(5)SwapRow() と Sort() の性能低下
行列を SwapRow() するとき動的配列モードならばポインタを交換するだけでしたが、 静的配列モードと準静的配列モードでは行全体を1バイトずつ交換するので遅くなります。
また Sort() で2番目以降の引数に固定列行列や可変列行列を指定した場合も、 行全体を1バイトずつ交換するので遅くなります。

ハッシュ法の詳細

ポコムは3つのハッシュテーブルを持っています。

①変数名・関数名の検索用
②ヒープ・メモリのエラーチェック管理用(CMEM)
③CDEF の検索用

①は要素が少ないのでハッシュテーブルの大きさは
 変数名数+関数名数で固定されています
②は16ビットコンパイラの場合既定では使いません。
 16ビットコンパイラで使用するには CHECK_MEM を0以外に定義します。
③は NO_CDEF を0以外に定義すると使用しません。

②③の大きさは既定で可変です。
②③を大きさを固定するには
NO_REHASH_CMEM, NO_REHASH_CDEF を定義します。
この場合既定のハッシュテーブルの大きさは
16ビット・スモールモデルで 256、ラージモデルで 1024、
32ビットコンパイラで 65536、64ビットコンパイラで 1048576 です。

また HASH_SIZE_CMEM, HASH_SIZE_CDEF, HASH_BITS_CMEM, HASH_BITS_CDEF を
0以外に定義しても大きさを違う値で固定できます。

可変の場合、小さなハッシュテーブルで開始して、要素数が2のべき乗を越えると
ハッシュテーブルの大きさを2倍、または、2倍に近い素数に拡大してゆきます。

ハッシュ法のカスタマイズ

※以下 MODEL_SIZE(A,B,C,D) を使用します。
A:16ビットスモールモデル
B:16ビットラージモデル
C:32ビットコンパイラ
D:64ビットコンパイラ

●HASH_SIZE_CMEM, HASH_SIZE_CDEF
これを定義するとハッシュテーブルの大きさを直接指定した値で固定します。
データの大きさがおおよそ分かっている場合その値より大きめの値を指定します。
データの大きさを知るには REPORT_MEM を定義してコンパイルし実行します。
すると、 MaxCdef:??? MaxMem:??? のような出力が得られ判ります。

例えば #define HASH_SIZE_CMEM MODEL_SIZE(0,0,65537,65537) とすると、
16ビットコンパイラの場合は既定になり、
32/64ビットコンパイラの場合 65537 になります。

ここで指定する大きさは素数や奇数にこだわる必要はありません。
次の次の章で例を挙げて説明します。

ただし、2のべき乗は上位ビットが無視されるので避けた方がよいでしょう。
2のべき乗の場合は次の HASH_BITS_CMEM, HASH_BITS_CDEF を使います。

●HASH_BITS_CMEM, HASH_BITS_CDEF ( 既定値 MODEL_SIZE(8,10,16,20) )
これを定義すると2の HASH_BITS_CMEM, HASH_BITS_CDEF 乗の大きさに
ハッシュテーブルの大きさを固定します。データの大きさがおおよそ分かっている
場合、2の HASH_BITS_CMEM, HASH_BITS_CDEF 乗がデータ大きさより大きめの
値になるように指定します。
例えば HASH_BITS_CMEM が16の場合はハッシュテーブルの大きさは
65536 になります。

●MIN_BITS_CMEM, MIN_BITS_CDEF ( 既定値 MODEL_SIZE(5,7,10,10) )
ハッシュテーブルの大きさが可変の場合、初期の大きさを決めます。
例えば MIN_BITS_CMEM が10の場合はハッシュテーブルの初期サイズは
1024 になります。

●MAX_BITS_CMEM, MAX_BITS_CDEF ( 既定値 MODEL_SIZE(10,13,23,30) )
ハッシュテーブルの大きさが可変の場合、最大の大きさを決めます。
例えば MAX_BITS_CMEM が23の場合はハッシュテーブルの最大サイズは
8388608 になります。

●PRIME_CMEM, PRIME_CDEF, BELOW_PRIME
HASH_SIZE_CMEM, HASH_SIZE_CDEF を定義しなかった場合、PRIME_CMEM, PRIME_CDEF
を定義するとハッシュテーブルの大きさを選ばれた2のべき乗以上の最小の素数に
設定します。固定でも可変でも素数になります。
同時に BELOW_PRIME を定義した場合、2のべき乗以上の最小の素数の代わりに
選ばれた2のべき乗以下の最大の素数に設定します。

●CRC32_CMEM, CRC32_CDEF
ハッシュ値の計算をするときに SSE4.2 の _mm_crc32_u8, _mm_crc32_u32,
_mm_crc32_u64 を使って計算します。CRC32_CDEF を定義すると
①[変数名・関数名の検索用]のハッシュ関数も _mm_crc32_u8 を使うようになります。

●ハッシュ値の計算方法
②[ヒープ・メモリCMEM]の場合
・16ビットラージモデルの場合
 ・HASH_SIZE_CMEM または PRIME_CMEM が定義されている場合はセグメント値を
  テーブルの大きさで割った値を使います。
 ・HASH_SIZE_CMEM も PRIME_CMEM も定義されていない場合はセグメント値を p、
  16-HASH_BITS_CMEM を s として (p^(p<%lt;s))>>s を使います。
・16ビットスモールモデルの場合
 ・HASH_SIZE_CMEM または PRIME_CMEM が定義されている場合はポインタ値を p と
  として (p^(p>>6))%(テーブルの大きさ) とします。
 ・HASH_SIZE_CMEM も PRIME_CMEM も定義されていない場合はポインタ値を p、
  16-HASH_BITS_CMEM を s として、((p*p)^p)>>s を使います。
・CRC32_CMEM が定義されている場合
 ・HASH_SIZE_CMEM または PRIME_CMEM が定義されている場合はCRC値を
  テーブルの大きさで割った値を使います。
 ・HASH_SIZE_CMEM も PRIME_CMEM も定義されていない場合はCRC値を
  右シフトした値を使います。
・32/64ビットコンパイラで CRC32_CMEM が定義されていない場合
 ・HASH_SIZE_CMEM が定義されていてそれが偶数ならばポインタ値を p として
  p^(p>>15)%(テーブルの大きさ) とします。
 ・HASH_SIZE_CMEM が定義されていてそれが奇数の場合、または PRIME_CMEM が
  定義されている場合はポインタ値をテーブルの大きさで割った余りを使います。
 ・HASH_SIZE_CMEM も PRIME_CMEM も定義されていないときはポインタ値に
  大きな奇数を掛け算したものを右シフトした値を使います。
 ※例えばポインタ値を p として #define HASH_SIZE_CMEM 65536 とした場合
  (p^(p>>15))%65536 となり #define HASH_BITS_CMEM 16 とした場合32ビット
  コンパイラなら (p*1103515245)>>16 となり64ビットコンパイラなら
  (p*1103515245*1103515245)>>48 となります。

③[CDEF]の場合
・CRC32_CDEF が定義されている場合はCRC値を基本値にします。
・CRC32_CDEF が定義されていない場合は基本値を掛け算と足し算で計算します。
・HASH_SIZE_CDEF または PRIME_CDEF を定義すると基本値をテーブルの大きさで
 割った余りを使います。
・HASH_SIZE_CDEF も PRIME_CDEF を定義されていない場合基本値を
 右シフトした値を使います。

※余りの計算は右シフトに比べて計算時間がかかります。

ハッシュテーブルの状態の表示

●REPORT_HASH
REPORT_HASH を定義すると終了時にハッシュテーブルの衝突状態が表示されます。
大きさ h のハッシュテーブルに n 個の要素を登録した場合、
各バケツに何個の要素が登録されたかは2項分布 B(n,p) p=1/h に従います。
「衝突」とは1つのバケツに2個以上の要素が登録されることを言います。

ハッシュテーブルの全ての度数の和を大きさ h で割った値は2項分布の平均は
どんなに悪いハッシュ関数でも必ず n*p となり固定されてしまいます。
一方、ハッシュテーブル全ての度数の分散は、衝突が少数のバケツに
偏って起きている場合に大きくなります。
最小完全ハッシュ関数の場合0になります。
2項分布 B(n,p) の分散の期待値 E(V) は n*p*(1-p) です。

REPORT_HASH を定義して表示される V:??? にテーブルの状態の分散が表示されます。
0~5個の各度数と6個以上の度数の度数表も表示します。

分散(V:???)が同じ行の E(V):??? よりも大きいと悪いハッシュ関数といえます。
なお、CMEM の場合は calloc() や new の返すポインタの値が、
コンパイラやOSの状態により異なってくるので、
同じプログラムを同じデータで実行しても分散(V:???) の値も変化します。
CDEF の場合は同じプログラムを同じデータで実行すれば
分散(V:???) は変化しません

●NO_PVALUE
ts:?? は偏差値です。(T-score)
pv:??% はいわゆるP値(p-value)でこれが95%以上の場合統計的に
有意に悪いハッシュ関数といえます。
50%ならば全くランダムに散らばったといえます。
5%以下の場合統計的に有意に平らに散らばったといえます。
偏差値とP値は小さければ小さいほど好ましいものです。

NO_PVALUE を0以外に定義すると偏差値とP値を計算・表示しません。
偏差値とP値の計算に時間がかかり、分散(V:???)の値で十分判断できる場合に
定義します。

●PVALUE_SEED
P値を計算するには2項分布の分散の分布関数が必要ですが
この分布関数をネットで探しても見つからなかったのでモンテカルロ法で
計算するようにしました。モンテカルロ法なので CDEF でもこのP値は
乱数によって結果が上下します。乱数の種は PVALUE_SEED によって変更できます。
PVALUE_SEED を0に定義すると time(0) を種にして実行ごとに異なる結果を得ます。

●EXACT_PVALUE
これを定義すると高速モード(fast)を無効にします。
行末に表示されるアルファベットは次の意味です。
'E':EXACT_PVALUE が定義されているか、ReportHash()の引数 mode に'E'を指定した
'e':EXACT_PVALUE を定義しないで ReportHash()の引数 mode に'F'を指定したが、
    近似ができないと判断されて近似しなかった。
'f':近似第一段階(ハッシュテーブルの大きさ:hsz)で近似をした。
'F':'f'に加えて近似第二段階(登録数の大きさ:n)でも近似をした。

●PVALUE_SCALE, 
PVALUE_SCALE は分散を計算する回数( PVALUE_SCALE*100 )を決めます。
既定値は64です。大きくすると計算時間が長くなりますがP値の結果が安定します。

●PVALUE_MAX_H
ハッシュテーブルの大きさ hsz が128( PVALUE_MAX_H ) より大きい場合は
計算に時間がかかるので128に縮小近似してモンテカルロ法を行います。

●PVALUE_MIN_N
ハッシュテーブルの大きさが PVALUE_MAX_H より大きい場合に、
ハッシュテーブルに登録する数 n の近似変換の結果が20( PVALUE_MIN_N )よりも
小さい場合は近似しません。
n が hsz に比べて極端に小さい場合2項分布は単調減少となり、
正規分布と形が大きく異なってしまいP値の近似計算が不正確になるからです。

●PVALUE_MAX_N
縮小近似した n が256( PVALUE_MAX_N )より大きい場合は、
n を256にした2項分布でさらに縮小します。
ハッシュテーブルの大きさは変わりません。

ハッシュテーブルの大きさを素数にすると悪くなる例

●pocom の場合
HASH_SIZE_CMEM, HASH_SIZE_CDEF を素数にする必要がないことの根拠です。
HASH_SIZE_CDEF を変えて、E(V) が 1.0 になる実験したところ、
偶数の 60000 で分散が 0.827(偏差値 -252,P値 0%)と良い結果が得られ、
素数の 59999 で分散が 1.327(偏差値  621,P値 100%)と悪い結果が得られる
場合がありました。以下のプログラムです。

#define FILE_NAME "Prime1.c"
/* ハッシュテーブルの大きさを素数にして衝突が多くなる例 */
#if defined(PDEF_H)
 INTF(N,4,10000,"Size of data")
 CDEF(Def1,6,N,"CDEF 1")
 CDEF(Def2,6,N,"CDEF 2")
 CDEF(Def3,6,N,"CDEF 3")
 CDEF(Def4,6,N,"CDEF 4")
 CDEF(Def5,6,N,"CDEF 5")
 CDEF(Def6,6,N,"CDEF 6")
 PROC(Test,"Test")
 AUTO("Test")
#else
/* #define PRIME */
 #define NO_LIST
 #define NO_OPEN
 #define STDERR_STDOUT
 #define REPORT_HASH
 #define CDEF_BASE 1
 #define CDEF_FORMAT "%05" PRIz "d"
 #if defined(PRIME)
  #define HASH_SIZE_CDEF 59999
 #else
  #define HASH_SIZE_CDEF 60000
 #endif
 #include "../pocom.h"
 CHECK_FILE_NAME
 void Test(void) { Alloc("Def1 Def2 Def3 Def4 Def5 Def6"); }
#endif

PRIME を定義しないときの結果
# cdef B(n:60000,p:1/60000) E(V):1.000 V:0.827 ts:-252 pv:0% f
#  0:20395 1:23181 2:12867 3:3167 4:366 *:24 max=5

PRIME を定義したときの結果
# cdef B(n:60000,p:1/59999) E(V):1.000 V:1.327 ts:621 pv:100% f
#  0:26598 1:16751 2:9430 3:4966 4:1838 *:416 max=7

以下に Prime1.c を pocom なしで再現したプログラム Prime2.c を示します。

/* Prime2.c
ハッシュテーブルの大きさを素数にして衝突が多くなる例 */
#define _CRT_SECURE_NO_DEPRECATE
#include <stdio.h>
#include <string.h>
#define MAX_HASH_TABLE_SIZE 60000

#define NUM_OF_GROUP 6

/* 番号 00000 ~ 10000 */
#define SIZE_OF_GROUP (MAX_HASH_TABLE_SIZE/NUM_OF_GROUP)

/* ハッシュ値の衝突数を記録する */
unsigned HashCount[MAX_HASH_TABLE_SIZE];

/* ハッシュ関数の例 */
unsigned HashFunction(const char*name,unsigned sum) {
    int i; unsigned k;
    for (i=0;(k=((unsigned char*)name)[i])!=0;i++) sum=1103515245U*(sum+k);
    return sum;
}

/* 文字列 "00000" "00001" ~ "14999" の各ハッシュ値で衝突数を調べる */
void CountUpHash(unsigned hsz) {
    int i,group; unsigned id=0; char name[32];
    memset(HashCount,0,sizeof HashCount);
    for (group=0;group<NUM_OF_GROUP;group++) {
        id=id*2053+12345;
        for (i=0;i<SIZE_OF_GROUP;i++) {
            sprintf(name,"%05d",i);
            HashCount[HashFunction(name,id)%hsz]++;
        }
    }
}

void TestHashing(unsigned hsz) {
    unsigned i,n=NUM_OF_GROUP*SIZE_OF_GROUP,empty=0;
    double p=1.0/hsz,mean=n*p,var=0.0;
    CountUpHash(hsz);
    for (i=0;i<hsz;i++) {
        unsigned t=HashCount[i]; double diff=t-mean;
        var+=diff*diff;
        if (t==0) empty++;
    }
    var/=hsz;
   /* var/=hsz を var/=(hsz-1) としないのは mean が母平均だから */
   /* 例えば n=1 hsz=2 の場合 V を 1/4 にするには 2 で割る必要がある */
    printf("B(n:%u,p=1/%u) E(V)=np(1-p)=%.3f ",n,hsz,n*p*(1-p));
    printf("V=%.3f EmptyRatio=%.2f%%\n",var,100.0*empty/hsz);
}

int main(void) {
    TestHashing(60000); /* 偶数 */
    TestHashing(59999); /* 素数 */
    return 0;
}

結果:
B(n:60000,p=1/60000) E(V)=np(1-p)=1.000 V=0.827 EmptyRatio=33.99%
B(n:60000,p=1/59999) E(V)=np(1-p)=1.000 V=1.327 EmptyRatio=44.33%

HashFunction() で足し算した後に大きな奇数で掛け算をしている理由は、
最後の文字を上位ビットに反映させるためです。
ハッシュテーブルの大きさが2のべき乗のときは HashFunction() の戻り値を
右シフトするので、最後の文字を上位ビットに反映しないと衝突が多くなります。

●pocom なしの場合
pocom.h を使わないプログラムの場合で、ハッシュテーブルの大きさを
素数にしない方が良い、もっと自然な例を探してしたところ見つけました。
以下のプログラムです。

/* Prime3.c
ハッシュテーブルの大きさを素数にして衝突が多くなる例 */
#define _CRT_SECURE_NO_DEPRECATE
#include <stdio.h>
#include <string.h>
#define MAX_HASH_TABLE_SIZE 60000

/* ハッシュ値の衝突数を記録する */
unsigned HashCount[MAX_HASH_TABLE_SIZE];

/* ハッシュ関数の例 */
unsigned HashFunction(const char*name) {
    int i; unsigned k,sum=0;
    for (i=0;(k=((unsigned char*)name)[i])!=0;i++) sum=69*sum+k;
    return sum;
}

/* 文字列 "00001" "00002" ~ "60000" の各ハッシュ値で衝突数を調べる */
void CountUpHash(unsigned hsz) {
    int i; char name[32];
    memset(HashCount,0,sizeof HashCount);
    for (i=0;i<MAX_HASH_TABLE_SIZE;i++) {
        sprintf(name,"%05d",i+1);
        HashCount[HashFunction(name)%hsz]++;
    }
}

void TestHashing(unsigned hsz) {
    unsigned i,n=MAX_HASH_TABLE_SIZE,empty=0;
    double p=1.0/hsz,mean=n*p,var=0.0;
    CountUpHash(hsz);
    for (i=0;i<hsz;i++) {
        unsigned t=HashCount[i]; double diff=t-mean;
        var+=diff*diff;
        if (t==0) empty++;
    }
    var/=hsz;
   /* var/=hsz を var/=(hsz-1) としないのは mean が母平均だから */
   /* 例えば n=1 hsz=2 の場合 V を 1/4 にするには 2 で割る必要がある */
    printf("B(n:%u,p=1/%u) E(V)=np(1-p)=%.3f ",n,hsz,n*p*(1-p));
    printf("V=%.3f EmptyRatio=%.2f%%\n",var,100.0*empty/hsz);
}

int main(void) {
    TestHashing(60000); /* 偶数 */
    TestHashing(59999); /* 素数 */
    return 0;
}

結果:
B(n:60000,p=1/60000) E(V)=np(1-p)=1.000 V=0.562 EmptyRatio=26.19%
B(n:60000,p=1/59999) E(V)=np(1-p)=1.000 V=1.514 EmptyRatio=47.76%

pocom で偏差値とP値を確認したところ、
60000 の場合の偏差値は -718、P値は0%、
59999 の場合の偏差値は  941、P値は100%でした。
以下に確認のプログラム Prime4.c を示します。

#define FILE_NAME "Prime4.c"
/* ハッシュテーブルの大きさを素数にして衝突が多くなる例 */
#if defined(PDEF_H)
 INTF(N,5,60000,"Num of Names")
 INTF(H,5,60000,"Hash table size")
 SVEC(Names,6,N,"Names")
 SVEC(Hash,10,H,"Hash table(Do not Print)")
 SVEC(Next,10,N,"Hash list(Do not Print)")
 PROC(Test,"Test")
 AUTO("Test")
#else
 #define NO_LIST
 #define NO_OPEN
 #define STDERR_STDOUT
 #define REPORT_HASH
 #include "../pocom.h"
 CHECK_FILE_NAME

/* ハッシュ関数の例 */
unsigned HashFunction(const char*name) {
    int i; unsigned k,sum=0;
    for (i=0;(k=((unsigned char*)name)[i])!=0;i++) sum=69*sum+k;
    return sum;
}

void TestHashing(unsigned hsz) {
    Int i; unsigned hv;
    memset(Hash,0,hsz*sizeof(int*));
    for (i=0;i<N;i++) {
        hv=HashFunction(Names[i])%hsz;
        Next[i]=Hash[hv]; Hash[hv]=(char*)&Next[i];
    }
    ReportHash("test",Hash,hsz,'F',0);
}

void Test(void) {
    Int i; char name[32];
    Alloc("Names Next Hash");
    /* 文字列 "00001" "00002" ~ "60000" を生成する */
    for (i=0;i<N;i++) {
        sprintf(name,"%05d",i+1);
        SetSvec(Names,i,name);
    }
    TestHashing(60000); /* 偶数 */
    TestHashing(59999); /* 素数 */
}
#endif

結果:
# test B(n:60000,p:1/60000) E(V):1.000 V:0.562 ts:-718 pv:0% f
#  0:15714 1:29708 2:13442 3:1136 max=3
# test B(n:60000,p:1/59999) E(V):1.000 V:1.514 ts:941 pv:100% f
#  0:28657 1:14596 2:8748 3:4928 4:2322 *:748 max=6

ポコムを使わない場合との比較

比較のために ポコムを使わずに、 例題の Example1.exe のように、 連立1次方程式を解くプログラムを書いてみました。

test.dat
  2.000   5.000   7.000    23.000
  4.000  13.000  20.000    58.000
  8.000  29.000  50.000   132.000
test.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX 60
#define LINESIZE 1024

double A[MAX][MAX],B[MAX+1],C[MAX][MAX],X[MAX];
int N,Line; FILE*fp; char*p,buf[LINESIZE];

void err(char*m) { printf("%d:%s\n",Line,m); exit(1); }

void Solve(void) {
    int i,j,k;
    for (i=0;i<N;i++) for (j=0;j<N;j++) C[i][j]=A[i][j];
    for (i=0;i<N;i++) X[i]=B[i];
    for (k=0;k<N;k++) {
        if (C[k][k]==0) err("div by zero");
        for (j=k+1;j<N;j++) C[k][j]/=C[k][k];
        X[k]/=C[k][k];
        for (i=0;i<N;i++) if (i!=k) {
            for (j=k+1;j<N;j++) C[i][j]-=C[i][k]*C[k][j];
            X[i]-=C[i][k]*X[k];
        }
    }
}

void ReadLine(void) {
    if (fgets(buf,sizeof buf,fp)==0) err("bad EOF");
    Line++;
    if (strlen(buf)==sizeof buf-1) err("line too long");
    p=strtok(buf," \t\r\n");
}

double ReadDouble(void) {
    double x;
    if (p==0) err("shortage of number");
    x=strtod(p,&p);
    if (*p) err("double format error");
    p=strtok(0," \t\r\n"); return x;
}
        
void ReadData(char*fname) {
    int i,j;
    fp=fopen(fname,"r");
    if (fp==0) err("file open error");
    ReadLine();
    for (i=0;p;i++) {
        if (i>MAX) err("max col error");
        B[i]=ReadDouble();
    }
    N=i-1;
    if (N<=0) err("col size error");
    for (i=0;i<N;i++) A[0][i]=B[i];
    B[0]=B[N];
    for (i=1;i<N;i++) {
        ReadLine();
        for (j=0;j<N;j++) A[i][j]=ReadDouble();
        B[i]=ReadDouble();
        if (p) err("too many numbers");
    }
    if (fgets(buf,sizeof buf,fp)) err("too many lines");
    fclose(fp);
}

int main(int argc,char**argv) {
    int i;

    ReadData(argv[1]); Solve();
    for (i=0;i<N;i++) printf(" %7.3f",X[i]);
    printf("\n"); return 0;
}

関数 Solve( ) は、example1.c のものと、ほとんど同じです。 小さな例題であるにもかかわらず、 入力のためのソースコードの行数が40行を超えていて、入出力を書く手間が、 プログラミングに占める割合が大きいことを物語っています。 ポコムを使えば、この手間が大幅に削減できます。

test.c を

C:\>bcc32 test.c

のように Borland C++ でコンパイルした結果得られた test.exe の大きさは 57856 バイトで、

C:\>bcc32 example1.c

のポコムの例題 example1.exe(122,880 バイト) の約47%の大きさになっています。 機能が豊富で完成度が高い example1.exe に比べて、約半分にしかなりません。 これは、printf( ) 等のライブラリが占める割合が大きいのが原因と思われます。 また、

C:\>bcc32 -DNO_ALL example1.c

とすると example1.exe は 79,872 バイトまで小さくなります。

test.c の MAX は 60 ですが、これを 61 にすると LSI-C 試食版では、 大きすぎてコンパイルできません。 しかし、Visual C++ や Borland C++ では、 実行マシンのメモリの搭載量によりますが、相当大きくできます。 したがって、test.c では、コンパイラや実行マシンによって、 いちいち MAX の適切な大きさを考えて定義しなおし、 再コンパイルする必要があります。 ポコムを使えば、そんなことは考える必要がなく、 同じソースで柔軟に対応できます。 また test.c では、1行の長さも 1024 バイトに制限されています。

もちろん、ポコムを使わなくても、double**A; のように宣言して、 動的にメモリを確保するプログラムを作成することはできます。 しかし、test.c でさえ40行以上ある、入力プログラムが、 動的に確保するようにするとなると Solve( ) の数倍もの大きさになるでしょう。 入力プログラムを変数ごとに用意したり、プログラムごと用意したりするのは、 大変な手間がかかります。 ポコムのように、汎用的な入出力の仕組みを用意するのが、 やはり賢いと言えるでしょう。