latexmk設定メモ

LaTeX文書のコンパイルをよしなにやってくれるlatexmkについて。

全体設定である ~/.latexmkrcには以下のように記入してある。

#!/usr/bin/env perl

$pdf_mode = 3;
$latex = 'uplatex -kanji=utf8 -synctex=1 -file-line-error -halt-on-error -interaction=nonstopmode %O %S';
$bibtex = 'upbibtex %O %B';
$dvipdf = 'dvipdfmx %O -o %D %S';

$biber = 'biber --bblencoding=utf8 -u -U %O %S';
$makeindex = 'mendex %O -o %D %S';

$pvc_view_file_via_temporary = 0;
$pdf_previewer = 'SumatraPDF -reuse-instance'

この設定は、ディレクトリごとの.latexmkrcで上書きできる。

運用

ディレクトリごとに.latexmkrcを置いて作業している。上の設定ファイルのうちビューワー設定以外の部分を、各状況にあわせて変更して使っている。

  • latexmk pdfを作りたいだけのとき
  • latexmk -pvc 継続ビルド。pdfを見ながら編集したいとき
  • latexmk -pvc document.tex 上に同じ、ただしカレントディレクトリに*.tex複数あるときはこのように指定する

ビューワー設定

$pvc_view_file_via_temporary = 0;
$pdf_previewer = 'SumatraPDF -reuse-instance'
  • $pvc_view_file_via_temporary ... latexmk -pvc でPDFを作成するとき、既定では一時ファイルに出力してから目的のファイルに移動する仕様になっているが、この設定ではこれを0にしている。
  • $pdf_previewer ... Adobeなどファイルをロックするビューワーは使わないほうがよいだろう。この環境ではSumatraPDFにパスを通しているが、普通はフルパスを指定する

各種latexに共通のオプション

$latex = 'uplatex -kanji=utf8 -synctex=1 -file-line-error -halt-on-error -interaction=nonstopmode %O %S';
  • -synctex=1 ... *.synctex.gzを生成する。
  • -file-line-error ... エラー出力がわかりやすくなるらしい。
  • -interaction=nonstopmode ... 文法エラーなどのときにユーザーに尋ねなくなる。
  • -halt-on-error ... ファイルが存在しないなどのときにユーザーに尋ねなくなる。

特に最後の2つは、継続ビルドして作業するのには必須だろう

なお、%O %Sはそれぞれオプションとソースファイルを表す。コマンド指定系の変数では、%が全くない場合は自動的に適切なものが付与される。位置を変えたら何故か-interaction=nonstopmodeが効かなくなったので上のような順番がよいだろう。

latexごとの設定

生成経路に応じて数パターンあるが、ここではtex→dvi→pdfの場合と、tex→pdfの場合を書く。$pdf_modeが生成経路を指定している。

tex→dvi→pdf

uplatexを使う例。

$pdf_mode = 3;
$latex = 'uplatex -kanji=utf8 -synctex=1 -file-line-error -halt-on-error -interaction=nonstopmode %O %S';
$bibtex = 'upbibtex %O %B';
$dvipdf = 'dvipdfmx %O -o %D %S';

dvipdfdvipdfm(x)ではオプションの順序が違うらしいので注意

tex→pdf

lualatexを使う例。(upbibtexにしない理由があるらしいが、こちらでは肯定的にも否定的にも未確認)

$pdf_mode = 1;
$pdflatex = 'lualatex -synctex=1 -file-line-error -halt-on-error -interaction=nonstopmode %O %S';
$bibtex = 'pbibtex %O %B';

auxdirについて

-auxdir / $aux_dir というオプションがあり、これを使うと各種の中間生成物の場所を別のディレクトリにできるような気がしてくるが、これは半分正しくない。現在はこのオプションが有効なのはMikTeX版のlatex系コマンドだけらしいので、残念ながら避けるのが無難だろう。

volatileとatomicの違い

volatileとatomicの違いを調べるために、以下のC++プログラムをコンパイルしてみる。

#include <atomic>

void func1(int *p) {
  ++*p; ++*p;
}

void func2(volatile int *p) {
  ++*p; ++*p;
}

void func3(std::atomic_int *p) {
  ++*p; ++*p;
}
$ g++ -std=c++11 -pthread -O2 -Wall -Wextra -g -c func.cpp -o func.o

環境による可能性はあるが、出力された機械語は端的に言うと次のようなものになる。(なおアーキテクチャLinux x86-64)

func1:
	addl	$2, (%rdi)
	ret
func2:
	movl	(%rdi), %eax
	addl	$1, %eax
	movl	%eax, (%rdi)
	movl	(%rdi), %eax
	addl	$1, %eax
	movl	%eax, (%rdi)
	ret
func3:
	lock addl	$1, (%rdi)
	lock addl	$1, (%rdi)
	ret
  • func1 は、「2を足す」という動作をしている。
  • func2 は、「メモリから読み込んで1を足して書き込む」という動作を2回している。
  • func3 は、「lockしながら1を足す」という動作を2回している。

これは以下の違いによる:

  • volatile は、メモリの読み込みや書き込みを、副作用を伴う動作と見なす。*1 そのため、読み込みや書き込み動作を減らす最適化を行わない。ただし、回数や順番さえ合っていればよいので、他のスレッドに干渉されるかどうかは考えない。
  • atomic は、他のスレッドが同時に読み書きしようとしても、あるひとまとまりの動作の間は独占的に動作するような振舞いになる。x86の場合はlockプレフィックスで実現できる。

動作確認

この動作は以下のプログラムで確認できる。

#include <cstdio>
#include <atomic>
#include <thread>

void func1(int *p);
void func2(volatile int *p);
void func3(std::atomic_int *p);

void count1(int *p) {
  for(int i = 0; i < 1000000; ++i) {
    func1(p);
  }
}

void count2(volatile int *p) {
  for(int i = 0; i < 1000000; ++i) {
    func2(p);
  }
}

void count3(std::atomic_int *p) {
  for(int i = 0; i < 1000000; ++i) {
    func3(p);
  }
}

int main(int argc, char *argv[]) {
  int num = -1;
  if(argc > 1) {
    std::sscanf(argv[1], "%d", &num);
  }
  if(num == 1) {
    int x = 0;
    std::thread th0(&count1, &x);
    std::thread th1(&count1, &x);
    th0.join();
    th1.join();
    std::printf("%d\n", x);
  } else if(num == 2) {
    volatile int x = 0;
    std::thread th0(&count2, &x);
    std::thread th1(&count2, &x);
    th0.join();
    th1.join();
    std::printf("%d\n", x);
  } else if(num == 3) {
    std::atomic_int x = ATOMIC_VAR_INIT(0);
    std::thread th0(&count3, &x);
    std::thread th1(&count3, &x);
    th0.join();
    th1.join();
    std::printf("%d\n", (int)x);
  }
  return 0;
}
$ g++ -std=c++11 -pthread -O2 -Wall -Wextra -g -c main.cpp -o main.o
$ g++ -std=c++11 -pthread -O2 -Wall -Wextra -g func.o main.o -o main
$ ./main 1
2497250
$ ./main 1
2432386
$ ./main 1
3136510
$ ./main 1
2411326
$ ./main 1
3466956
$ ./main 1
2367656
$ ./main 1
2297168
$ ./main 2
2324260
$ ./main 2
3137164
$ ./main 2
2374254
$ ./main 2
2627152
$ ./main 2
2593840
$ ./main 2
2871581
$ ./main 2
2218822
$ ./main 2
2617198
$ ./main 3
4000000
$ ./main 3
4000000
$ ./main 3
4000000
$ ./main 3
4000000
$ ./main 3
4000000
$ ./main 3
4000000
$ ./main 3
4000000

これはマルチコアの動作結果に関わるので環境によって異なる動作をするかもしれない。手元の環境はVirtualBoxで仮想化されたLinux x86-64であった。

atomicでは不十分な場合もある

atomicで一まとまりの動作と見なされる範囲はごく小さい。例えば*pが偶数であったとすると、以下の関数を繰り返し実行しても*pの値が変わらないことが意図される(シングルスレッドではそうなる)が、マルチスレッドではそうはならない。

#include <atomic>

void func(std:atomic_int *p) {
  *p += 1;
  *p ^= 1;
}

このような場合は単に*pをatomicにするだけでは不十分だが、mutexなどを使えばうまくいく。

*1:これはマルチスレッドのための仕組みというよりも、Memory-Mapped I/Oなどでメモリの読み書きが動作を伴う場合が想定されていると思われる。

文脈自由文法(CFG)と解析表現文法(PEG)をHaskellのモナドで説明する話

文脈自由文法(Context Free Grammar) と 解析表現文法(Parsing Expression Grammar) は記法が似ているものの、その性質は大きく異なっている。しかし、以下のようにHaskellモナドを用いて、左再帰的でない文脈自由文法をそのままパーサーコンビネーターとして変換すると、PEGはList monadをMaybe monadとして置き換えたものとして説明できる。

-- CFGとPEGの関係を List vs. Maybe として説明するサンプル

import Control.Monad
import Control.Monad.State

-- 型の説明 : StateT String m a
-- StateT String ... 構文解析の残りの文字列を記憶している。
-- m ... 正否をあらわす。ListにするとCFG, MaybeにするとPEGになる
-- a ... 今回は構文解析の正否のみに興味があるので () にする。

-- 演算子の説明
-- A >> B ... 構文要素の連接を表す。
-- A `mplus` B ... 構文要素の選択を表す。

-- CFG/PEG共通 : 指定した1文字を読む。
readChar :: MonadPlus m => Char -> StateT String m ()
readChar ch = do
  (ch0:str) <- get
  guard $ ch0 == ch
  put str

-- CFG/PEG共通 : 文字列の終端か検査する。
eof :: MonadPlus m => StateT String m ()
eof = do
  [] <- get
  return ()

-- 文法その1
-- S -> A $
-- A -> "a" A "a" | "a"
a :: MonadPlus m => StateT String m ()
a = (readChar 'a' >> a >> readChar 'a')
    `mplus` readChar 'a'

grammar1 :: MonadPlus m => StateT String m ()
grammar1 = a >> eof


-- 文法その2
-- S -> B $
-- B -> "a" B "a" | "b" B "b" | ""
b :: MonadPlus m => StateT String m ()
b = (readChar 'a' >> b >> readChar 'a')
    `mplus` (readChar 'b' >> b >> readChar 'b')
    `mplus` return ()

grammar2 :: MonadPlus m => StateT String m ()
grammar2 = b >> eof


-- 文法を(左再帰的でない)CFGとして検査する
checkCFG :: StateT String [] () -> String -> Bool
checkCFG grammar str = runStateT grammar str /= []

-- 文法をPEGとして検査する
checkPEG :: StateT String Maybe () -> String -> Bool
checkPEG grammar str = runStateT grammar str /= Nothing


main :: IO ()
main = do
  forM_ [
      "",
      "a",
      "aa",
      "aaa",
      "aaaa",
      "aaaaa",
      "aaaaaa",
      "aaaaaaa"
    ] (\str -> do
      putStrLn $ "checkCFG grammar1 " ++ show str ++ " = " ++ show (checkCFG grammar1 str)
      putStrLn $ "checkPEG grammar1 " ++ show str ++ " = " ++ show (checkPEG grammar1 str)
    )
  forM_ [
      "aa",
      "abba",
      "bbbbbb",
      "baaaab"
    ] (\str -> do
      putStrLn $ "checkCFG grammar2 " ++ show str ++ " = " ++ show (checkCFG grammar2 str)
      putStrLn $ "checkPEG grammar2 " ++ show str ++ " = " ++ show (checkPEG grammar2 str)
    )

実行結果

checkCFG grammar1 "" = False
checkPEG grammar1 "" = False
checkCFG grammar1 "a" = True
checkPEG grammar1 "a" = True
checkCFG grammar1 "aa" = False
checkPEG grammar1 "aa" = False
checkCFG grammar1 "aaa" = True
checkPEG grammar1 "aaa" = True
checkCFG grammar1 "aaaa" = False
checkPEG grammar1 "aaaa" = False
checkCFG grammar1 "aaaaa" = True
checkPEG grammar1 "aaaaa" = False
checkCFG grammar1 "aaaaaa" = False
checkPEG grammar1 "aaaaaa" = False
checkCFG grammar1 "aaaaaaa" = True
checkPEG grammar1 "aaaaaaa" = True
checkCFG grammar2 "aa" = True
checkPEG grammar2 "aa" = True
checkCFG grammar2 "abba" = True
checkPEG grammar2 "abba" = True
checkCFG grammar2 "bbbbbb" = True
checkPEG grammar2 "bbbbbb" = True
checkCFG grammar2 "baaaab" = True
checkPEG grammar2 "baaaab" = False

文法の例として挙げたもの

S -> A
A -> "a" A "a"
A -> "a"

このCFGは奇数個のaからなる文字列を受理する。

S <- A
A <- "a" A "a"
   / "a"

このPEGは(2の冪-1)個のaからなる文字列を受理する。

S -> B
B -> "a" B "a"
B -> "b" B "b"
B -> ε

このCFGはaとbからなる偶数文字の回文を受理する。

S <- B
B <- "a" B "a"
   / "b" B "b"
   / ε

このPEGはよくわからないが回文でも受理しない場合がある。(上の実行結果を見よ。)

GCC拡張の無効化 (と、それにまつわる細かい話)

端的に言えば-pedantic-errorsを使えばよい。(できれば-std=...も併用したほうがいいだろう)

以下解説

GCCC/C++コンパイラは、独自の拡張機能を導入している。これを無効化するオプションには2種類あり、意味が異なる。

  1. C標準と矛盾する拡張機能を無効化する。
  2. (C標準と矛盾しないかもしれない)拡張機能を無効化する/または警告を出す。

実は、C言語の規格は、それぞれのコンパイラ拡張機能を全面的に禁止しているわけではない。「正しいプログラム(strictly conforming program)を正しく動かす」ことさえ満たせばいいので、いくつかの拡張機能が有効化されたままでも、標準に準拠していると言える場合があるのだ。

C標準と矛盾する拡張機能を無効化する

C標準と矛盾する拡張機能を無効化するには、 -std オプションでISO C/C++を指定すればいい。 C Dialect Options - Using the GNU Compiler Collection (GCC) このオプションは、「C/C++のバージョン」「ISO準拠またはGNU独自」の2つの内容を指定する。

このオプションのデフォルトは、-std=gnu11-std=gnu++03のように、gnuが含まれている設定である。これには、C標準と矛盾する拡張機能が含まれている。これを-std=c11-std=c++14のように、cが含まれている設定に変えれば、C標準に準拠した挙動になる。

C標準と矛盾する拡張機能の例

  • 1行コメント : C89と矛盾する。一見矛盾しないように見えるが、1行コメントの有無により挙動が変わるコードが存在する。
  • Trigraphの無効化 : "??/"のようなコードの解釈が変わる。
  • typeof : C++11以降のdecltypeに相当する機能。このような予約語をC標準の範囲内で追加することはできない(変数名と衝突する可能性があるからである)。同じ機能をもつ__typeof__は問題ない。

多くのGCC拡張機能は、既存のCコードの意味を変えないように配慮されている。したがって、この方法で拡張機能を無効化することによる影響はそれほど大きくはない。

拡張機能を無効化する/または警告を出す

これは、C/C++コード中で拡張機能が利用されていた時に、警告またはエラーにする機能である。

このオプションの挙動も明快に説明できるわけではなく、色々な注釈が必要である。

  • long longなど、「以前のバージョンではGCC拡張であったが、新しいバージョンでは標準」という機能は多数存在する。これらは、-std=c...で指定されたバージョンに応じて判定する。
  • -std=gnu...が指定されていた場合は、対応するISO C/C++標準に基づいて判定する。しかし、-pedantic-errorsを指定していても、例えば-std=c90-std=gnu90では意味が違う。上記の1行コメントの例は、前者ではコンパイルが通るが、後者ではコンパイルエラーになる。
  • -Werror=pedanticとすると、-Wpedanticで出る警告すべてをエラーにすることができる。これは-pedantic-errorsとは微妙に意味が異なる。*2
  • 明示的に「拡張機能」とされているものを判定することはできるが、これによってプログラムが本当の意味でISO C/C++準拠であるかを確認できるとは限らない。
    • コンパイル時にわかる範囲の例では、int _Foo = 1;のようなコードを規格違反として検出できない。*3
    • 処理系定義・未規定・未定義な挙動により、「たまたま動いているけど動く保証がないコード」が生まれることはよくある。これを全てコンパイル時に検出するのは不可能である。実行時にある程度検出することはできる (デバッグオプションで説明されている-fsanitize=undefinedオプションなど) が、完全ではないし、実行効率が落ちると予想される。

*1:Wがない -pedantic も同じ意味

*2:とドキュメントには書いてあるが、詳細は未調査

*3:その部分が標準ライブラリ由来であるか、ユーザーの提供したプログラム由来であるかを判定するには、コンパイラの構造をそれなりに変えないといけないので、将来のコンパイラでもこれを判定することはないだろう。

64bit windows上でのalloy* (hola)

Alloy (hola) のバックエンドはSATソルバであり、いくつかのバックエンドから選べる。しかし、64bit windows上の64bit JREでは、SAT4J以外は動作しない。

以下の手順で自分でjarを作成すれば、minisat等が動くバイナリが作れる。

kodkodのネイティブライブラリをダウンロードして展開

/some/path$ wget http://alloy.mit.edu/kodkod/release/win_x86_64.zip
/some/path$ unzip win_x86_64.zip

Alloyをダウンロードして展開

/some/path$ wget http://alloy.mit.edu/alloy/hola/downloads/hola-0.2.jar
/some/path$ mkdir hola-0.2
/some/path$ cd hola-0.2
/some/path/hola-0.2$ jar xvf ../hola-0.2.jar

kodkodのネイティブライブラリをコピー

/some/path/hola-0.2$ cp -r ../win_x86_64 amd64-windows

jarを作成

/some/path/hola-0.2$ jar cvfm ../hola-0.2.jar META-INF/MANIFEST.MF .

以上により、64bit windows用のネイティブライブラリが含まれたhola-0.2.jarが作成された。

C++11 random 覚え書き

C++11以降では<random>ヘッダで良質な擬似乱数を得ることができる。

#include <random>

外部からの乱数

外部から乱数を得るにはrandom_deviceを使う。

#include <random>
#include <iostream>

int main() {
  std::random_device rand_dev;
  std::cout << rand_dev() << std::endl;
  std::cout << rand_dev() << std::endl;
  return 0;
}

上のように、外部からの乱数源は関数として呼び出すことで乱数を生成する。

このプログラムは実行するごとに異なる値を出力する。

擬似乱数

擬似乱数は、シード値を放り込むと乱数っぽい数列を吐き出すプログラム。

生成方法が何種類かあるが、mt19937だけ把握していればよい。

#include <random>
#include <iostream>

int main() {
  std::mt19937 rand_src(12345);
  std::cout << rand_src() << std::endl;
  std::cout << rand_src() << std::endl;
  return 0;
}

上のように、擬似乱数生成器は関数として呼び出すことで擬似乱数を生成する。

このプログラムは異なる整数を2つ出力するが、プログラムの実行ごとに同じ整数を出力する。

ソース中の12345がシード値である。この数値を変更すると、異なる整数を出力するようになる。

乱数の加工

上で生成した乱数は32bit一様乱数等なので、必要な分布が出るように加工する。

#include <random>
#include <iostream>

int main() {
  std::mt19937 rand_src(12345);
  std::uniform_int_distribution<int> rand_dist(0, 99);
  std::cout << rand_dist(rand_src) << std::endl;
  std::cout << rand_dist(rand_src) << std::endl;
  return 0;
}

上のように、分布を関数として使うときは、引数に乱数源への参照を渡す。内部で乱数源が(必要に応じて複数回)呼ばれている。

上のソースコードでは、MTで生成される擬似乱数列をもとに、一様分布に従う0以上99以下の整数を2つ生成している。

よく使う分布:

暗号論的擬似乱数

たぶんこのライブラリには無い。

分布と擬似乱数生成器を合体させる

分布に毎回擬似乱数生成器をつけるのは面倒であるから、<functional>ヘッダにあるbindrefを使って以下のようにする。

#include <random>
#include <iostream>
#include <functional>

int main() {
  std::mt19937 rand_src(12345);
  std::uniform_int_distribution<int> rand_dist(0, 9);
  auto rand_dist_bound = std::bind(rand_dist, std::ref(rand_src));
  std::cout << rand_dist_bound() << std::endl;
  std::cout << rand_dist_bound() << std::endl;
  return 0;
}

refを使わずに、

  auto rand_dist_bound = std::bind(rand_dist, rand_src);

としても一応動作するが、これは擬似乱数生成器のコピーを作成してしまうので意味が変わる。同じ擬似乱数生成器を複数の分布で使う場合は注意が必要(通常、refをつけるほうが好ましい)


これは以下のようにしても同じことである:

#include <random>
#include <iostream>
#include <functional>

int main() {
  std::mt19937 rand_src(12345);
  auto rand_dist_bound = std::bind(
    std::uniform_int_distribution<int>(0, 9),
    std::ref(rand_src));
  std::cout << rand_dist_bound() << std::endl;
  std::cout << rand_dist_bound() << std::endl;
  return 0;
}

さらに、この擬似乱数生成器はこの分布でしか使わないというのであれば、以下のようにもできる(この場合はrefは使わない):

#include <random>
#include <iostream>
#include <functional>

int main() {
  auto rand_dist_bound = std::bind(
    std::uniform_int_distribution<int>(0, 9),
    std::mt19937(12345));
  std::cout << rand_dist_bound() << std::endl;
  std::cout << rand_dist_bound() << std::endl;
  return 0;
}

シードの決め方

シードの決め方は場合による。大雑把に言うと次の2種類:

  • 外部からの乱数に基づいて決める
  • 決め打ち

ただし、以下のことに注意が必要だ:

  • 外部からの乱数が必要な状況はあまり多くない : 乱数に基づくアルゴリズムは、シード決め打ちの擬似乱数で問題なく動作する。
  • むしろ、再現性を考えると、シードを決め打ちにしたほうが良い。
  • 「実行ごとに動作を変えたい」という需要は、コマンドライン引数などでシードを指定できるようにするだけで満足されることがある。

外部からの乱数に基づいてシードを決める

外部からの乱数に基づいてシードを決めるには、単に以下のようにすればよい:

#include <random>
#include <iostream>

int main() {
  std::random_device rand_dev;
  std::mt19937 rand_src(rand_dev());
  std::cout << rand_src() << std::endl;
  std::cout << rand_src() << std::endl;
  return 0;
}

このようにすると、毎回シードが変わるので、実行するごとに異なる整数が表示される。

外部からの乱数源を1回しか使っていないので、以下のようにもできる:

#include <random>
#include <iostream>

int main() {
  std::mt19937 rand_src(std::random_device{}());
  std::cout << rand_src() << std::endl;
  std::cout << rand_src() << std::endl;
  return 0;
}

コマンドライン引数に基づいてシードを決める

例えば、コマンドライン引数の1番目がある場合はそれをシードとして使うようにするには:

#include <random>
#include <iostream>
#include <string>

int main(int argc, char *argv[]) {
  int seed = 12345;
  if(argc > 1) {
    seed = std::stoi(argv[1]);
  }
  std::mt19937 rand_src(seed);
  std::cout << rand_src() << std::endl;
  std::cout << rand_src() << std::endl;
  return 0;
}

また、コマンドライン引数を与えない場合のデフォルトは、外部からの乱数でもよいかもしれない:

#include <random>
#include <iostream>
#include <string>

int main(int argc, char *argv[]) {
  int seed;
  if(argc > 1) {
    seed = std::stoi(argv[1]);
  } else {
    seed = std::random_device{}();
  }
  std::mt19937 rand_src(seed);
  std::cout << rand_src() << std::endl;
  std::cout << rand_src() << std::endl;
  return 0;
}

サンプルコード

POSIX usleep(3)を使って、1秒間あたり平均5個のランダムな(ポアソン過程に基づく)イベントを発生させる (十分たくさんの粒子があるときの放射線の発生とかがこれ)

#include <random>
#include <iostream>
#include <string>
#include <functional>
#include <unistd.h>

int main(int argc, char *argv[]) {
  int seed;
  if(argc > 1) {
    seed = std::stoi(argv[1]);
  } else {
    seed = std::random_device{}();
  }
  std::mt19937 rand_src(seed);
  auto rand = std::bind(
      std::exponential_distribution<double>(5.0 / 1000000.0),
      std::ref(rand_src));
  while(true) {
    std::cout << "a" << std::flush;
    usleep(rand());
  }
  std::cout << rand_src() << std::endl;
  std::cout << rand_src() << std::endl;
  return 0;
}

いわゆる「中卒」は数学/情報オリンピックには参加できるのか否か

「高校生に相当する年齢だが、高校等の教育機関に在学していない」場合、数学オリンピック情報オリンピックには参加できるのか、という問題がある。

該当する組織に直接訊ねるのが最も正確だと思われるが、2015/06/26時点の資料を読む限り、おそらく以下の通りだと思われる。

以下、根拠となる文書を引用する。

日本数学オリンピック

2016年1月時点で大学教育(またはそれに相当する教育)を受けていない20歳未満の者。但し、IMO代表資格は、IMO大会時点で大学教育等を受けていない20歳未満の者。

http://www.imojp.org/mo2016/jmo2016/recruit.html

国際数学オリンピック

A Country’s Contestants should normally be citizens or residents of that Country, and should be selected through that Country’s national Mathematical Olympiad or equivalent selection programme. Contestants must not have formally enrolled at a university or any other equivalent post-secondary institution, and they must have been born less than twenty years before the day of the second Contest paper.

https://www.imo-official.org/documents/RegulationsIMO.pdf

以下、qnighyによる訳:

通常、各国の競技者(参加者)はその国の市民ないしは住民であるべきであり、その国の国内数学オリンピックまたはそれと同等の選抜プログラムによって選ばれた者であるべきである。競技者は、大学やそれと同等の高等教育機関に正式に入学したことのある者であってはならない。また、競技者の誕生日は、競技日のうち2日目から数えて20年前よりも後でなくてはならない。

日本情報オリンピック

  1. 2016 年 2 月 14 日(日)の第 15 回日本情報オリンピック本選競技実施時点で、高等学校、高等専門学校、中学校、中等教育学校、小学校、特別支援学校に在学し、学年が高等学校2年以下(中等教育学校高等専門学校などの在校生は高等学校2年に相当する学年以下)であること。 日本国内において学校以外の初等・中等教育機関に所属している人は個別に相談。
  2. 生年月日が 1996 年 4 月 2 日以降であること。
http://www.ioi-jp.org/joi/2015/joi-2016-outline.pdf

国際情報オリンピック

A Contestant is a student who was enrolled at a school for secondary education, in the Country they are representing, during the period September to December in the year before IOI’n, and is not older than twenty years on the 1st of July of the year of IOI’n.

http://www.ioinformatics.org/rules/reg14.pdf

以下、qnighyによる訳:

競技者(参加者)とは、該当年のIOIの前年9月から12月の間に、その者が代表する国にある中等教育のための学校に在学し、かつ、該当年のIOIのある年の7月1日時点で20歳以下である生徒のことである。

(was enrolledって在学と解釈していいんだろうか。)