Rokiのチラ裏

学生による学習のログ

JPEG デコーダ書いた

以前 JPEG エンコーダをフルスクラッチで書いたが、今度は JPEG デコーダを書いて見た。該当するコミットはこちら。

github.com

プロジェクト全体はこちら。

github.com

量子化テーブルの要素数を間違えて指定していて、少しハマった瞬間もあったが今回は割とサクサクと一日ほどで作り終えた。次は画像処理としては、巷で流行っている HEIF 関係の処理系を書ければと思っている。

#追記・諸々のアップデート

元々は C++1z 標準の[[fallthrough]]attribute を使っていたがコンパイラの対応状況になるべく依存しないようその辺りを吸収してくれるライブラリを作ったのでそれを使うように変更。 github.com

エンコード、デコード処理で用いるコサインテーブルは、元々実行時に生成されていたが、コンパイル時にコサインテーブルを生成するライブラリを作ったので、それを使うように変更。 github.com

order of eval

Because my memory was a little vague, I learned about C++ order of eval again and wrote it in Japanese to my reference book.

github.com

The document of built version is here and for list of grounds and references of this document, consult this page. This beginning of thing is from these tweets so I am grateful for yohhoy’s polite response.

Why an access to a volatile glvalue is considered a side effect by [intro.execution] ?

side-effect についてドラフトで再確認していたところ、その定義として “Reading an object designated by a volatile glvalue” という文面があったのでタイトルの内容をメモ。執筆時現在、side-effect については [intro.execution]/14/sentence-1 にて以下のように述べられている。

Reading an object designated by a volatile glvalue, modifying an object, calling a library I/O function, or calling a function that does any of those operations are all side effects, which are changes in the state of the execution environment.

タイトルに答える内容は、同セクションの3つ目のセンテンスで述べられている。

When a call to a library I/O function returns or an access through a volatile glvalue is evaluated the side effect is considered complete, even though some external actions implied by the call (such as the I/O itself) or by the volatile access may not have completed yet.

ライブラリ I/O 関数への呼び出しが返されるか、volatile glvalue によるアクセスが評価されると、呼び出しによって暗黙の外部操作(I / O自体など)や volatile なアクセスをまだ完了していない可能性がある。
stackoverflow にも分かりやすい回答が載っていたので一部引用。

Hardware registers, which is the normal use-case for volatile (typically in conjunction with pointers, but doesn't have to be), the read of a register will potentially have actual side-effects on the hardware - for example reading a fifo-register on a serial port [or network card, hard disk, or whatever], will affect the state of the hardware, since the fifo has now "moved on" one step. Skipping over, duplicating, caching the result of, or some other such optimisation would definitely cause a piece of driver code and hardware to behave in a different way than the programmer wanted - which would be the case if volatile wasn't considered as having a side-effect.

普段使いのコンパイル環境の整備

私は普段ちょっとしたコードに対してコンパイルを行う時、GCC と Clang のどちらかを使う。使うたびにコンパイルオプションを付与するのは面倒なので、これまでは単にオプションを含めた文字列をそのまま .zshrc のエイリアスとして設定していたのだが、あんまり管理の仕方としてはよろしくないなと思ったので、make で管理したいと思った。しかし、毎度 Makefile を作業ディレクトリごとに移動させたりパスを通したり何なりするのは元も子もないので、それに加えて zsh を使ってちょっとした環境整備をした。

で、これを呼び出す zsh スクリプト--version-Sは割と使うのでそれだけ用意した。

そして .zshrc には以下のように指定。これで、gcc, g++, clang, clang++ がこの Makefile の設定で実行される。

alias g++="$HOME/build_setting/make.zsh GXX"
alias clang++="$HOME/build_setting/make.zsh CLANGXX"
alias gcc="$HOME/build_setting/make.zsh GCC"
alias clang="$HOME/build_setting/make.zsh CLANG"

もっと良い管理法などはあるだろうか。個人的にはこれで満足しているので特別問題はないのだが。

Template non-type arguments によるコンパイル時四則演算パーサー

書いて見た。

テスト。

テストの通り、無駄に C++11 に対応している。勿論の事だが C++14 では variable template が使えるし、C++1z(17) では型推論が効くのでそれによって使い勝手も良くなっている。尚、clang 4.0.0 でテストを行ったが、少し前の GCC とかだと

というようなバグに引っかかるので注意。

javascript parseInt 関数の挙動

javascript (ECMAScript 2015) の parseInt 関数の挙動についてメモ。parseInt 関数は、第1引数の文字列をパースし、第2引数に与えられた基数(数学的記数法の底)に基づく整数を返す関数である。基数には、2 ~ 36 までの整数を指定する事ができ、0 を除いたそれ以外の値を指定した場合は NaN を返す*1。以下、ソースから一部改変して引用。

isNaN(parseInt('null',10)) === true // #1
isNaN(parseInt('null',23)) === true // #2
parseInt('null',24) === 23 // #3
parseInt('null',31) === 714695 // #4

♯1 と ♯2の挙動は、分かりやすい。'null'は文字列であり数ではないので、NaNが返ってくる。しかし、♯3 と ♯4 は NaN ではなくそれぞれ両者とも値が返ってくる。これは、第1引数文字列の頭から、第2引数で与えられた基数による単位解釈の基、数として認識できる値が有る限りパースが続き、そうでない部分を切り捨てて返すという parseInt 関数の仕様によるものである。
♯3 では基数として 24 を指定しているが、24進法で n は 23 という値を持つ。しかしその後に続く u は、24進法では値を持たないため無視されて 23 が返ってくる。♯4 は 31進法で全てを有効な数として捉える事ができるため、714695 が返ってくる。
この事実確認のため、MDN web docs の parseInt を見たら、日本語化されたドキュメントが参照している翻訳元が以前のリビジョンでは説明として間違っており、その後更新されて修正されたものの、日本語化されたドキュメントはその間違った説明がされているリビジョンから更新されていなかったため更新を加えた。その時に、parseInt 関数の関係で少し興味深い挙動を示す質問を見つけたので記載する。

stackoverflow.com

parseInt(123123123123123123123123);というように引数には数値が指定されているため、.toStringによって文字列に変換されて以下のようになる。

parseInt("1.2312312312312312e+29").

基数を指定しない場合 parseInt はそれを10進法と解釈するので、"1.2312312312312312e + 29"から + に達してその時点で中断し、"1.2312312312312312"を解析して、1が返される。

Template non-type arguments (Variadic templates) によるコンパイル時乱数生成

Variadic templates でコンパイル時に乱数を生成してみた。現段階ではエンジンとして linear congruential, Xorshift, mersenne twister を、Discrete Uniform Distribution として uniform_int_distribution を実装してみた。ただし Template non-type arguments に対して実数を扱わせる事はできないので*1、そのようなものに対する Discrete Uniform Distribution は linear congruential で用いられる方法を流用している*2また、discard 的なものは未実装であるがこれも追々実装予定。実装した。

github.com

一応こんな感じに書ける。

make_random_sequenceという命名がなんだか微妙だが、make_index_sequenceに則って取り敢えずそうしている。それぞれ乱数エンジンの型は、resultnext_type型とdiscart_type型を持っており、resultが現在の擬似乱数の生成された値、next_typeが次の擬似乱数を生成する型、discart_typeが指定された回数分内部状態が進んだ擬似乱数を生成する型となっている。
尚、パラメータパックの保持する値に対してランダムアクセスを行おうとすると、再帰によって値を1つ1つ捨てる他がないので、これが線形となってしまってとても時間がかかる。これを極力避けた。また、C++1z からテンプレート引数型にautoを記述する事ができるようになったが、これを再帰中に多様すると型推論の負荷が強く、手元のコンパイラ(GCC 7.1.0)では有限時間内にコンパイルが完了せず abort trap で死んだので、 mersenne twister の実装などでは特に型を明示するようにしている。

以下は、srook::anypack<>::random::mersenne_twister のテストコード。実装はこれをパスする。

*1:N4660 [temp.arg.nontype]. ただし参照を除く

*2:std::ratio を使って追々実装するかもしれない