カテゴリー別アーカイブ: プログラム

WindowModePatch 0.68 Alphaを公開

実はこの記事を書く時点で公開してからすでに一週間経過していたりするのですがそれはおいておきましょう。前回の更新から半年前後経過していて忘れ去られていたのかといえばさにあらず。いまだけ時間を作ることができる状態だったことと気になったプログラムが残ったままになっていたので自分でサンプルを作って仮想マシン上で動作をテストすることで修正点を見いだすというマッチポンプではないけれども似ているようなことを二週間前くらいから行って更新にこぎ着けました。

 

主な更新は二つ

基本的には以下になります。

  • DirectDrawをDirect3Dでエミュレーションを行う場合に8bitパレット処理における互換性の向上
  • パッチ対象の拡張子がexe,dll以外である場合に処理が行うことが非常に困難であったので修正

サンプルプログラムを作ってテストしていたのは前者で特に文字列描画で文字色が本来白色にならなければならない場合での互換性が向上しているはずです。検証プログラムを詳細に書いてみて初めてわかる仕様というのもあるのですね…。後者は特に外部モジュールを読み込んで動作するタイプの場合に本体に非推奨の完全パッチ処理を行いライブラリのロード時に強制的にWindowModePatchを読み込ませるか外部モジュールそのものにパッチ処理を行うか…という選択肢で外部モジュールの拡張子がdllではなかった場合にリネームしないとパッチ処理ができないのが面倒ということで処理を変えました。元々パッチ処理では対象が実行ファイル系かどうかの判別が入っているので問題はないのですが、実際にそういうことが必要になるゲームに出会うとさすがに変えたくなるということだったりします。

 

付け加えようとしていた機能はなんだろう?

機能の追加については数ヶ月前に思い立ったことなのでどうしようとしたか今の自分には理解できない…じゃなくて。おそらく途中で解像度を切り替える機能とかのはずなのですが機能が中途半端に実装されているので今回の更新では対象としていません。というか数ヶ月前の私は最終的にどの形にして実装をするつもりだったのか、について今の私にはわからない、という状態だったり。おそらくは対象のゲームと関係のないキーの組み合わせを入れると別のダイアログが表示されて画面の解像度をリストから選ぶか入力するか、という感じだと思うのですが…。まあ、余裕があれば適当に実装してみたいと考えてはいます。

 

自分一人での更新も難しくなってくるのかな

フリープログラマというのも自分で宣伝できなかったり仕事を受ける人脈がない場合はフリーターより悪い状況と見えるので真面目に職に就こうかと思い活動しています。そうするとこのWindowModePatchのメンテナンスも全くできなくなる、ということになる予感がしておりもったいないような気がしているのでいろいろと方策を考えています。信頼できるほかの人にソースコードを更新できる権限を持ってもらって対応できるゲームを増やしていったり情報を集めたり、などですね。

なお、WindowModePatchに関するWikiもこのサーバー内に設置してあり、容量制限もつけたのでデザインが整ったら…と思っていたのですが使っているWiki(MediaWiki)が昔に記事を書いたときから見るとかなり更新されてしまったためにどうしても情報を書くときに使おうと思っているWikipediaのテンプレートであるInfoboxが正常に取り込めないという問題にはまっていて抜け出せないのですね。はてさてどうするのがよいのでしょうか…。

 

Hash Calculator 0.02を公開

というわけでハッシュ計算につきまして新規にいくつかのアルゴリズムを実装すると同時にバグ修正を行いましてHashCalculatorを更新しました。なんと初期公開が2012年末ということで4年以上間隔が空いているのがびっくりというところです。

 

計算可能なハッシュアルゴリズムの追加および更新をしてみた

一つ前の記事で自分のライブラリに追加する、という話は書いていたと思いますがアルゴリズムを追加するということはHashCalculatorも更新できるネタができた、ということだったのでマイナーなアルゴリズムだけではなくもう少しメジャーなアルゴリズムも追加してみました。今回追加したのは

  • BLAKE(224bit,256bit,384bit,512bit)
  • BLAKE2b,BLAKE2s
  • SM3

の3種類になります。BLAKEはSHA-3の公募が行われたときに最終候補にまで残ったアルゴリズムとして有名でBLAKE2系はそれの改良系であり、MD5並みの計算速度を持つということを売りにしているアルゴリズムです。SM3は中国で標準のハッシュ処理として計算方法が公開されているもので中国圏で開発されている規格などでハッシュ処理を使うときによく使われている形式となっています。BLAKEについては公募の関係でドキュメントなどもかなり豊富にあるのですがSM3は範囲がかなり狭く国が公開している中国語の物かIETFが公開しているドキュメントくらいしかないようでIETFのものがあるとは知らず実装テストが終わるまでずっと中国語のドキュメントを訳してやっていました。まあ、数式の部分は共通ですし訳す必要がある部分も中国語の文法などを簡単に知っていれば漢字の意味をある程度共通にとれるので難しくはないところがありがたかったです。

 

KeccakとSHA-3の計算方法は「終端が微妙に」違う

「皆さんも気をつけましょう」という話題です。SHA-3のアルゴリズムは本家であるKeccakをそのまま使っていますが最後のメッセージパディングの部分が微妙に違います。これはSHA-3の仕様をちゃんと確認してみるとよいですがKeccak本体だとメッセージパディングは数式を使わず言葉で書くと

1を追加して0を(ブロック長ビット数-1)まで追加後最後に1を追加する

ですが、SHA-3だとSHA3-nとSHAKEnでは

SHA3-n:01を追加してkeccakの終端処理(1を追加して…)を行う

SHAKE-n:1111を追加してkeccakの終端処理(1を追加して…)を行う

となります。このあたりは仕様書をちゃんと読んでおく必要があると思いますので気をつけてください。多分以前に作ったサンプルだとKeccakの終端処理を使っているために間違っているような気がしないでもないです。

 

中国の暗号化規格であるSMnって複数あるのですね…

SM2とかSM3とかSM4とかあるようです。多分日本語のサイトで言葉を検索すると全く違う単語に引っかかるでしょうね。私も時々「三國無双」の略称じゃないの?と考えしまうこともあるくらいですから。ちなみにそれぞれ

  • SM2:楕円曲線を使った公開鍵暗号方式
  • SM3:SHA-256相当のハッシュ関数
  • SM4:ブロック構造を使った共通鍵暗号方式

となっています。最新の規格もあるようですが今回はそこまでは見ていません。「中国製のCPUで処理に対応した、という記事を見て実装してみたいと思った」というなんとも微妙な動機でやり始めた今回の拡張ですがこの数年間で暗号にまつわることも大分発展したのだな、と思いました。なお私個人だと暗号に用いられる数学の根本的な部分を学びましたので楕円曲線でなぜRSA暗号が成立するのか?といった部分もいまなら説明できるようになっています。

 

ついでにコンパイラのバージョンもアップ

VisualStudio2005からVisualStudio2010に変更しまして対応OSもWin8系およびWin10系を追加しました。これが今回の副産物ですね。数年前にVisualStudio2005で作成して公開したきり一度も更新していないプログラムが複数ありますがそれもやった方がよいのかな…と思っているところです。

SM4(formerly SMS4)を実装してみた

久しぶりのプログラムネタです。題名を見ても「は?」としか思われないかもしれませんね。

 

SM4(formerly SMS4)とは

おそらくこれの意味がわからないと思いますので説明を。SM4を単体で検索してもまともに出てこないですしSMS4で検索すると今回のものとは全く無関係のことが出てくるほどマイナーなアルゴリズムですので。

このアルゴリズムですが実はブロック暗号アルゴリズムの一つで中国ではよく使われている形式です。中国国内で使われる無線通信の暗号形式として登場することが多いようで、これを実装してみようと思った理由がVIAもといCentaurの流れを汲む8コアx86 CPUが中国で登場していた模様 – やじうまPC Watchの記事を読んでいるときにSMS4が追加サポートされたという部分があり、「どういうアルゴリズムなのだろうか?」というところからどうせなのでプログラムとして実装してみよう、という流れでやってみました。

なお、ドキュメントなどを探せばわかると思いますが本来のアルゴリズムを説明した文章は中国語(汉语)で書かれていてそれを英語に翻訳したものが別に存在しています。ほとんどの人は英語版を読めばよいと思いますが中国語(汉语)が読めるのであればそちらのバージョンを読んでみてもよいと思います。汉语を少しですが勉強していたので読んでみたのですが、読み方はわからない漢字が大量にあっても文法がある程度理解できるようになったので意味を大筋で理解できるのが面白かったですね。

 

アルゴリズムはXOR、SBox置換、巡回シフトを1ラウンドとした複数ラウンド構成

暗号キーの作成も暗号化の手順もほぼ同じ手順(線形処理である巡回シフトの式が異なるだけ)で、かつ処理の構成の方法から暗号キーから内部暗号キーを作り出してしまえば暗号と復号は内部暗号キーを使う順番が逆になるだけ、という至ってシンプルなアルゴリズムです。何回か暗号処理を実装した人であれば最適化を考えなければ数時間で完了できるほどのものです。

一部面倒なのはこの手のアルゴリズムの実装では当たり前の話なのですがエンディアン処理をうまくすることです。巡回シフト演算の関係で32bitワードの状態での演算が必要でその部分がビックエンディアンで行う、という定義なのでリトルエンディアンを前提としたコードだとどの部分に変換処理を入れる必要があるのか、を正しくすることが重要になってきます。この部分に気がつくのに時間がかかったために資料に記述されている例と状態が合わないために四苦八苦しました…。

 

実装しても使い道が…

まあ、中国語(汉语)のドキュメントを直接読む機会となりよい経験ができた、というところでしょうかね。もちろん普通のブロック暗号なのでCBCなどと組み合わせれば普通にファイルの暗号化もできるのですが、だからといってゲームのアーカイブなどに使ってみても意味がなさそうですし。それ以外だと一からプログラムを組む機会が少なくなっていたのでそちらでもよい刺激になった、というところで。

もしコードを見てみたい、という人がいればコメントにでも書いてもらえれば追加で記事にするかもしれません。

 

1/0を数学的、教育学的、プログラム的に考える

というわけで今回は三種すべてに絡んだネタをやっていきたいと思います。この頃いろいろな場所でこの「ゼロ除算」の話が書いてあったのでそれがあり得る環境としてこの3つで書いていきたいと思います。というかゼロ除算で問題になるのは大別するとこの3つになるような気がしないでもありません。

 

数学的にはどのように扱うか、をおさらいする

まずこれが大切です。ここの認識が誤るとその後ろの2つについて「なぜそういうことになるのか?」を説明できなくなる可能性が高いからです。注意していきましょう。

まず数学的に1/0を考える場合、この演算を代数学を主としてとらえるか解析学を主としてとらえるか、によってかなり差が出ます。基本的には代数学の考え方が主となるのですが…。

 

代数学の考え方で1/0を考える

代数学で1/0を考えるときはたいていの場合『「1」や「0」は整数と考えてその中に自然に定義される(乗法の逆演算である)除法を用いて演算せよ』という問題と考えることができます。ここで問題となるのが「整数」や「乗法」および「逆演算」をどう考えるか、にあります。そもそも代数学では0や1は実は形式的な記号と考えることができます。また、今回の場合「整数」を対象としたとしてその場合乗法だけではなく加法も同時に定まっていると考えるとこの演算は「整数環、あるいは有理数体(実数体、複素数体)上で」という制約がさらに考えられる、というところまで考えないと正しく考えることができません。

で、ここまで来て初めて公理系から考えていきます。必要となるのは環の公理および体の公理です。まずは環の公理から。Wikipediaにある環の公理とは少し違いますが私の持っている教科書の書き方に準拠します。

加法と乗法という2つの演算が定義された集合Rが環(ring)であるとは次の4つの条件を満たすことである。(この4つを「環の公理系」と呼ぶ)

  1. Rは加法に関して加群である。
  2. Rは乗法において結合法則を満たす。
  3. Rは乗法と加法において分配法則を満たす。
  4. Rは加法の単位元0Rとは異なる元1Rが存在しに対してを満たす。

ちなみにこの公理から証明される定理として

加法の単位元0Rに対してとなる

があります。次に体の公理です。

環Rにおいて以下の条件を満たすときRは体(field)であるという。

  1. Rは加法の単位元0Rを除いて乗法において逆元を持つ。
  2. Rは乗法において交換法則を満たす。

で、ここまで見てみると気がつくと思いますが「0の逆元は体の公理系では定義されていない」ということです。わざわざ体の公理でも「0Rを除いて」ということになっています。このことから代数学的には体において「1/0」という操作は定義されていない(考えない)ということになっています。また環で考えたとしても除法は乗法の逆演算として考えるのでこの場合は考えることができない代物になっています。演算ができないというよりは考えない、が正しいです。ちなみに上記からわかると思いますが0/0も代数学の環や体では定義されません。

 

解析学的の考え方で1/0を考える

で、これで終わらないのが数学です。代数学的にはそうなのですが、解析学的には極限の考え方を用いることでいくつかの場合によって値を考える、ということを行います。つまり直接的に1/0は定義されていないとしても0にごく近い範囲で考えれば除数は0ではない限り乗法において逆元を持つ(有理数体や実数体より)ので0に近づけていくとどうなるのか?というものです。

たとえば関数を考えてxを1から0に正の数として近づけて答えを考えます。すると

  • 1/1=1
  • 1/0.1=10
  • 1/0.01=100
  • 1/0.001=1000

のように答えが大きくなっていっているのがわかります。これを続けていくなら1を一つ前に考えていた数より0に近い正の数で除法を行うと一つ前の答えより正の数として絶対値が大きな値となりどこかで一定の答えとなることはないという結論が得られます。で、これを極限の書き方で書くととなるわけです。なお、この無限大(∞)という書き方ですがこれは数ではなく状態を表す記号です。そのため数としての演算は一切できません。

ただ注意が必要なのが今は「正の数として」近づけていきましたが逆に「負の数として」近づけてみるとどうなるでしょうか。xを-1から0に負の数として近づけて答えを考えると

  • 1/(-1)=-1
  • 1/(-0.1)=-10
  • 1/(-0.01)=-100
  • 1/(-0.001)=-1000

ということで想像できると思いますがこの場合は1を一つ前に考えた数より0に近い負の数で除法を行うと一つ前の答えより負の数として絶対値が大きな値となる、という結論が得られます。上と同様に極限の書き方で書くととなります。

関数の場合は実は近づけ方によってx=0の極限を考えると複数の答えが出てきてしまします。そのため、解析学的にも極限を考えたとしてもx=0における極限は存在しない、という解答となります。(ただし正の方向から近づけた場合は正の無限大、負の方向から近づけた場合は負の無限大という解答にはなります)

おまけですが、解析学的として0/0を考えるといろいろと大変なことが起こります。たとえば関数を考えるとx=0では0/0の形となり値を持たない、となりますが極限を考えれるととなりnの値に依存した答えとなります。これだけ見てもわかりますが0/0を見ただけでは極限値がどうなるか解析学的にもわからないわけです。

 

教育学的にはどう扱うべきなのか考察する

問題なのはここですよね…。このセクションについては完全に個人的見解となりますのであしからず。

まず、学習指導要領および学習指導要領解説にゼロ除算について何か書いていないかな~?と思って読み解いてみたのですがさすがにその部分は疑問に持つ児童や生徒はいたとしても学習する項目としてではないので言及は一切ありませんでした。このことからもし児童や生徒に質問された場合どう返すか、は先生の考え方および力量に依存することになると考えられます。

次に除法を学習するときにどうやって学習するか、からゼロ除算について何かいい言い方がないのか?を考えてみました。気になったのが小学校三年生での除法と乗法、減法との関係についてで、12÷3の意味を考える場合に、

包含除は3×□=12の□を求める場合であり、等分除は□×3=12の□を求める場合である。また、実際に分ける場合でも、包含除も等分除と同じ仕方で分けることができることなどにも着目できるようにしていくことが大切である。

とあるので、ゼロ除算を除法について習ったばっかりなら「じゃあ1÷0の答えを考えてみるけれども0×□=1となる□に当てはまる数はある?ないよね?だから0でわることはできないんだよ」というよくある教えかたがこの場合は適しているといえそうだ、と考えました。また減法との関連であまりが出る除法においてもカードを分ける場合などの例を使うことから「カードを配るやり方で1÷0を考えてみるけれども、一枚のカードを一人に一枚も配らないで配ったふりだけをすると何回できる?(なんか不思議に思えるけれども)何回でもできるよね。そうすると答えが出そうにないね」という言い方になるのでは?と考えました。

ちなみにこの考え方ですが、前者の言い方は数学的には代数学における「0の逆元は定義されていない」を形式的に説明したものであり、後者はどちらかというと解析学における正の数から0に近づけていったときの極限の考え方に近い説明だと考えています。

なお、この後文字と式で文字を使った式を学んだり、方程式で式の変形を考え始めるといつの間にかゼロ除算をしてしまったパターンが現れてくることもあり、そのときは「ゼロ除算となる場合を分けて考えないと正しくない答えが入ってしまうね。これはゼロで割る、ということができない(定義されていない)からこうなるのだよ」という説明はできそうな気がします。高校レベルでも極限までいければだいぶ説明がしやすいですが…。

 

最後にプログラム的にはどうなる?

実はいろいろなコメントを読んでいて一番気になったのがこのポイント。「1/0はDivision by zeroでエラーとなる」という発言が多かったです。ただし注意してほしいのが(たいていの人は理解して言っていると思いますが)対象のプログラミング言語や変数の型によっては必ずしもそうではない、ということを書いておきたいと思います。

以下のC言語のサンプルコードはすべてゼロ除算をしているパターンです。

#include <stdio.h>

int main(void)
{
        printf("1.0/0.0=%f\n",1.0/0.0);
        printf("1.0/(-0.0)=%f\n",1.0/(-0.0));
        printf("0.0/0.0=%f\n",0.0/0.0);
        printf("1/0=%d\n",1/0);
        return 0;
}

これをgccでコンパイルしてみました。ファイル名をtest.cとしてやってみると

$ gcc test.c
test.c: 関数 ‘main’ 内:
test.c:8:21: 警告: ゼロ除算が発生しました [-Wdiv-by-zero]
  printf("1/0=%d\n",1/0);

となります。警告が出ているのは実は一番最後の1/0だけで残りの1/0や0/0は警告が出ていません。さらにできたファイルを実行してみます。

$ ./a.out
1.0/0.0=inf
1.0/(-0.0)=-inf
0.0/0.0=-nan
Floating point exception (コアダンプ)

例外により強制終了しているのは最後の演算だけで、残りの3つに関しては(一応)答えを出しています。これはどういうことかというと浮動小数型の場合はIEEE754にもありますが無限大やNaN(Not a Number、非数)が定義されていて、浮動小数として演算を行った場合はそれぞれ解析学的に極限を考えたものとして計算する、という動作になります。0/0の場合は極限が不定となるためにその状態として非数を用いて表現されているのがわかります。浮動小数型の場合は科学技術演算や3D演算等で無限大を考える必要があるのでこの方が都合がよいのでしょう。また整数型の場合は代数学的に計算されると考えればよいようで、そうなるとゼロ除算の結果は定義されていないため直接わかる場合はコンパイラによる警告が表示され、実行時では例外となるわけです。

 

ちなみにスクリプト言語としてPHPでやってみるとどうなるか、というとこういうコードで試してみました。

<php
  print 1/0;
?>

これをtest.phpに保存して実行してみると

$ php test.php
PHP Warning:  Division by zero in /…/test.php on line 2
INF

ということでゼロ除算による実行警告は出てきましたが計算結果としてはINF、つまり正の無限大となったわけです。なのでこの処理をただの警告と処理するなら常に「エラーとなる」訳ではなく、処理をそのまま継続することが可能です。このあたりは注意して扱う必要があります。

 

本当に考えるのであれば数学上での考え方を基礎にして考える必要があり

高校以下の数学および算数やプログラムでもそうですが、元となる数学の考え方をそれぞれに落とし込んだものですので元の考え方を知らないと理由を突き止めることは難しくなります。もちろん結果そうなるだけで納得できるならそれはそれでもいいですが、深く知っておくと似たような場面で理由が連想できるようになり、人に説明するときにうまく説明できるようになったり自分で問題が起こったときに学ぶことができるようになると思います。

PHPの「Fatal error: Can’t use function return value in write context」に要注意 (WordPressで管理画面に入れないことも)

ということで先ほどプラグインを更新したときにいきなり発生した事態について書いてみます。「いったい何事?」と考えてしまいましたが理由がわかればプログラムの修正でなんとかなるのですが、知識がない人にもわかるようにいろいろと書いてみます。

 

PHPのとある処理に起因する問題

エラーメッセージで検索すれば詳しく出てくるので詳しくは書きませんが、もしPHPやHTTPサービスのログを直接見られる場合はこんなログが書いてあることがあります。

PHP Fatal error:  Can't use function return value in write context

このエラーですが、調べてみると関数に見えるPHPの演算子(に近いのかな?)に対して関数の戻り値を直接渡した場合エラーとなる、というものです。よくあるのがisset()やempty()に関数の戻り値を直接渡す場合、例としては

function hoge()
{
	return array(...);
}

if(empty(hoge())){

}
else{

}

というものです。この場合、エラーを避けるためには一時的に変数に関数の戻り値を受け取ってからempty()やisset()に判定させる必要がある、つまり

function hoge()
{
	return array(...);
}

$ret = hoge()
if(empty($ret)){

}
else{

}

としなければならない、ということだそうです。で、もう一つ重要なポイントとしてはPHP5.5以降であればempty()については関数の戻り値を直接渡してもエラーとならない、ということになったようです。今回引っかかったのがこれです。

 

EWWW Image Optimizerを3.2.0に更新したら管理画面に入れなくなった…

つまり見事に上の現象に引っかかってしまったわけです。私の場合はエラーログを直接確認できる立場だったので何が起こったのか即座に判定してなんとかしたわけですが知らない人だと検索しても出てこないのでたぶん大変ですね…。まあ、レンタルサーバーなんかだとPHP5.5系以上のはずなので問題ないはずですが…。

 

PHPのバージョンを上げるか回避処理を追加しよう

通常はバージョンを5.5以上にあげる処理を行えばOKです。もしそれができないのであれば一時的に変数に受け取ってempty関数に渡すようにスクリプトを変更することで回避しましょう、とだけ書いておきます。どんなスクリプトになるのか知りたい方はコメント欄にでも要望を書いておいてください。あとで追記するかもしれません。

 

12:00 追記

EWWW Image Optimizerですが、3.2.1で修正されていることを確認しました。なので手動修正としては3.2.1のコードを手元にダウンロードしてbulk.phpをFTP経由などで書き換えることで対応することが可能です。この手の問題って確認漏れなのでいろいろな環境で試さないとだめなのとバグ報告の早さおよび対応の早さが評価の鍵となってしまいますかね。