2.3 * 100,000 のナゾ2008年03月27日 04時01分12秒

ちと意味わかんないんすけど

今日(あけてるので昨日か)の昼間、「PHPで 2.3を10万倍して int に cast したら誤差発生」という現象に見舞われた。最初はまたPHPのクサレ素敵仕様かバグかと思ったが、試してみたところ少なくともJavaScriptとC#(1.0)でまったく同様の結果になった。なので他の言語でもある話なのかも。

ちょっと理屈がわかんないんだよなー。

まずはすなおに出力

まずはこれ。

C:\Documents and Settings\dara-j>php -r "var_dump(2.3 * 100000);"
float(230000)

C:\Documents and Settings\dara-j>

まぁ、普通です。

キャストしてみる

んで、これをintにcastしてみる。

C:\Documents and Settings\dara-j>php -r "var_dump((int)(2.3 * 100000));"
int(229999)

C:\Documents and Settings\dara-j>

...えーっと...

もっともマニュアルの「浮動小数点」の説明を見ると、

こうなる理由のひとつとして、「有限小数に変換できない分数がある」 という事実があります。たとえば 1/3 を小数で表そうとすると 0.3333333. . . となります。

よって、小数の最後の桁を信用してはいけませんし、 小数が等しいという比較を行ってはいけません。より高い精度が必要な場合には、 任意精度数学関数または gmp 関数を代わりに使用してください。
とかあるので、まぁ精度の問題かしら、と思えなくもないのだが。

ちなみにJSでも

alert( Math.floor( 2.3 * 100000 ) );
で「229999」になる。

これは納得いかないんだが

こんどは1万倍と100万倍をやってみると、なんだか納得いかない。

C:\Documents and Settings\dara-j>php -r "var_dump((int)(2.3 * 10000));"
int(23000)

C:\Documents and Settings\dara-j>php -r "var_dump((int)(2.3 * 1000000));"
int(2300000)

C:\Documents and Settings\dara-j>
...なぜに意図どおりの結果が??いや、これで正しいんだけど。ってことは100,000だけ仲間はずれっすか。

さらに納得いかないんだが

よし、じゃあ10万倍が特別なのはよしとしましょう。でも、これは?

C:\Documents and Settings\dara-j>php -r "var_dump((int)(2.3 * 10 * 10000));"
int(230000)

...えええっと... えええっと...

内部での実行順序の違いってのがあるのかも知らんけど、* 100000と違う結果になるのはちとひどくないか?

この現象って、冒頭でも書いたけど別にPHP固有の現象ではなく、少なくともJavaScriptとC#ではまったく同じ結果になる。ちなみにここまでのコード例ではcastしてるけど、floor()使っても同じ結果になる。

10進型が使えりゃ気にしないのだが

実際、2.3 * 100000 で誤差がでると困るのでなんとか対応せにゃならなかったんだけど、さすがに * 10 してから * 10000 って、なんだかバッドノウハウくさいので、結局いったんstringを経由させてからintにキャストすることにしたんだけども。

こんな感じ。

function hoge($v) {
	// 元はこんな感じ
	// return (int)( $v * 100000 );
	
	// それをこんな感じに
	$v = $v * 100000;
	return (int)( "$v" );
	
	// なんというか、なんでこんなことを...
}

...なんだかなぁ。10進型(decimal)が使えればこんなことしなくてすむんだけどね。

あ、もちろんBCMath任意精度数学関数ってのがあるのは知ってるし、このケースで bcmul() なら問題ないことも確認してるんだけど、今回は実行環境にlibbcmathがなかったので。

オマケ

さらにさらに、これどうよ?

C:\Documents and Settings\dara-j>php -r "var_dump((int)(2.3 * 100 * 1000));"
int(229999)

C:\Documents and Settings\dara-j>php -r "var_dump((int)(2.3 * 1000 * 100));"
int(230000)

...ここまでくると、もはやどうでもいいや

HttpWebRequestとオレオレ証明書2007年10月10日 02時42分05秒

まずは1発目。

いま抱えてる仕事が月末納期で、そろそろ詰めの段階にきているのだが、本番系のサーバに暫定でオレオレ証明書を入れてSSL通信を始めた。

基本はWebアプリなのだが、印刷周りだけはWebじゃ如何ともしがたいため、C#で印刷アプリも作っていて、認証と基本情報の取得にWebアプリとHttpWebRequestで通信を行っているのだが、これがいきなりハマった。「リモート サーバーとの信頼関係を確立できませんでした」というやつだ。

証明書のインストールが必要かと思い、IEでアクセスして証明書のインストールを行ったが状況が改善しない。IEで表示される警告をよくよく読んでみると、どうやら(証明書の発行元が確認できないのはもちろんだが)証明書そのものが通信しているサーバ向けではなく、他のサイト向けとして発行されているとのことだ。

で、もう少し調べてみたら、MicrosoftのKBで「PRB:「 System.Net.WebException」 基になる接続は、閉じられました。 「リモート サーバーとの信頼関係は確立できる」でした。 .NET Framework をアップグレードすると、エラー メッセージ」なんてのが見つかった。

機械翻訳なので少々アレだが、

他のシナリオは、存在します。 たとえば、外部クライアント versus 内部的に一部のネットワークが別の名前解決方式を使用します。証明書が(www.adatum.com)などパブリック URL とイントラネット アプリケーションでサーバーに発行される場合に、内部ドメインネーム システム DNS サーバーは、(www.internal.corporate.adatum.com)など同じサーバーの別の名前を指定します。 この SSL での Web サービスの要求は、失敗することがあります。 この変更は、 SSL を使用する Web サービスのセキュリティを強化するのが行われます。
なんてことが書いてあり、どうやらそのものズバリくさい。

なので、このページに掲載されていたサンプルにあるように、「サーバが送りつけてきた証明書を無条件に信頼するICertificatePolicyを実装し、ServicePointManagerに設定する」という、なんともアレな回避策で無事(?)回避することができた。

ま、リリース時はちゃんとした証明書を発行してもらう(つか、手続き中)ので問題ないんだけども、なんだかなぁ、というお話でした。

C#で簡易テンプレート処理2007年09月17日 02時01分31秒

いきなりC#ネタ

ZF入門も書かずに脈絡なく。しかも.NET 1.1。ごめん、がんばる。

ありそうで見つからないので

ここしばらく久しぶりに仕事でC#を使っている。まぁだいぶ勘も戻ってきて、「やっぱ慣れてる言語はよいなー」とか思っていたのだが、ちょっと欲しい機能が見つからなかった。

いま実装中の機能で、DataSetにキャッシュしたデータをもとにUPDATE用のクエリを生成するのだが、状況によってすこしだけ異なるクエリを生成し分ける必要にせまられ、簡単なテンプレートエンジンが欲しくなったのだ。

ひょっとするとすでに優秀なライブラリがあるかもしれない(実はあとでNVelocityなんてのがあることに気づいた)が、探して評価している時間もないので割り切った機能で自作をしてみた。

仕事で作ったやつなのでネタにするのはちょっと抵抗あるが、仕様検討も含めて1時間かかるかかからないかくらいの規模だし、どうせ誰かが似たようなやつ作ってるだろうしでソースを公開してみることにした。需要あるかはわからんけど。

仕様

ってほど機能があるわけではないが、標準のString.Formatのインデックスベースのプレースホルダの代わりにIDictionaryのキーやDataColumnのカラム名を使うように考えた。要はこちらで紹介されている「JSON - String.prototype.supplant」みたいな記法だ。

たとえば

string template = "name={Name}, value={Value}";
みたいなテンプレートがあって、これに
  • "Name", "Value"というColumnがあるDataTableのDataRow
  • "Name", "Value"というキーを持つIDictionary(HashtableとかNameValueCollectionとか)
を与えるって使い方。 その他はなんの付加価値もない。条件分岐も繰り返し処理もサブテンプレートの差込もできない。

ただし、パラメータ名の表記は後ろにコロン+フォーマット指定ができるように展開するため、String.Formatと同様にフォーマット指定できるようにしてみた。

仕組み

ソースみればだいたいわかると思うが、以下のようなプロセスで展開する。

  1. {}で囲まれたパラメータ名を抽出順にリストにし、そのインデックス番号でもってテンプレート内のパラメータ名を置換する
  2. 展開時はパラメータ名のリストを順次走査して与えられたDataRowやIDictionaryから値を抽出してobject[]を作成、先に変換したテンプレートを用いてString.Formatする
こんな感じでかなりシンプル。

ソース

このくらいの感じ。例によってNYSLってことで。

using System;
using System.Collections;
using System.Data;
using System.Text.RegularExpressions;

namespace DaraJ {
  public class TemplateProcessor {
    // テンプレートソース
    protected string _source;
    
    // コンパイル済みソース
    protected string _compiledSource;
    
    // テンプレート変数名のリスト
    protected ArrayList _varNames;
    
    // Regex.Replace用のMatchEvaluator
    protected MatchEvaluator _evaluator;
    
    // テンプレート変数抽出用正規表現
    protected Regex _variableParser;
    
    // コンパイル済みフラグ
    protected bool _compiled;
    
    // デフォルトコンストラクタ
    public TemplateProcessor() : this(null) {
    }
    
    // テンプレートソースを指定するコンストラクタ
    public TemplateProcessor(string source) {
      this._variableParser = new Regex( @"\{([a-zA-Z_]\w*)(:.+?)?\}" );
      this.Source = source;
      this._evaluator = new MatchEvaluator( this.evaluateVariable );
    }
    
    // テンプレートソースを取得・設定
    public string Source {
      get {
        return this._source;
      }
      set {
        if( value == this._source ) return;
        this._source = value;
        
        this._compiledSource = null;
        this._varNames = new ArrayList();
        this._compiled = false;
      }
    }
    
    // コンパイル済みソースを取得
    public string CompiledSource {
      get {
        return this._compiledSource == null ? String.Empty : this._compiledSource;
      }
    }
    
    // テンプレート変数名リストを取得
    public string[] VariableNames {
      get {
        return (string[])(this._varNames.ToArray( typeof(string) ));
      }
    }
    
    // コンパイル済みか
    public bool Compiled {
      get {
        return this._compiled;
      }
    }
    
    // テンプレートをコンパイル
    public void Compile() {
      this._varNames = new ArrayList();
       this._compiledSource = this._variableParser.Replace( this.Source, this._evaluator );
      this._compiled = true;
    }
    
    // テンプレートソースを指定してテンプレートをコンパイル
    public void Compile(string source) {
      this.Source = source;
      this.Compile();
    }
    
    // IDictionaryをパラメータにしてテンプレート処理を実行
    public string Exec(IDictionary parameters) {
      if( ! this.Compiled ) this.Compile();
      
      ArrayList paramList = new ArrayList();
      foreach(string varName in this._varNames ) {
        paramList.Add( parameters[ varName ] );
      }
      return String.Format( this.CompiledSource, paramList.ToArray() );
    }
    
    // DataRowをパラメータにしてテンプレート処理を実行
    public string Exec(DataRow parameters) {
      if( ! this.Compiled ) this.Compile();
      
      ArrayList paramList = new ArrayList();
      foreach(string varName in this._varNames ) {
        paramList.Add( parameters[ varName ] );
      }
      return String.Format( this.CompiledSource, paramList.ToArray() );
    }
    
    // Regex.Replaceから呼び出される置換メソッド
    // テンプレート変数名を出現順序のインデックスに置換しつつ
    // テンプレート変数名をリストに追加する
    private string evaluateVariable(Match m) {
      int index = this._varNames.Count;
      string varName = m.Groups[1].ToString();
      
      this._varNames.Add( varName );
      
      return String.Format( "{{{0}{1}}}", index, m.Groups[2] );
    }
  }
}
で、使い方はこんな感じ。
using System;
using System.Collections;
using System.Data;

namespace DaraJ {
  public class TestClass {
    public static void Main() {
      TemplateProcessor template = new TemplateProcessor();
      // テンプレートソースを設定
      template.Source = @"{ItemName} \{Price:#,##0} (\{UnitPrice:#,##0} x {ItemNum})";
      
      // IDictionaryで実行
      Console.WriteLine( template.Exec( createHashtable() ) );
      
      // DataRowで実行
      foreach(DataRow row in createTable().Rows) {
        Console.WriteLine( template.Exec( row ) );
      }
    }
    
    // テストデータのDataTableを作成
    private static DataTable createTable() {
      DataTable table = new DataTable();
      table.Columns.AddRange( new DataColumn[] {
        new DataColumn( "ItemName", typeof(string) ),
        new DataColumn( "UnitPrice", typeof(decimal) ),
        new DataColumn( "ItemNum", typeof(int) ),
        new DataColumn( "Price", typeof(decimal), "UnitPrice * ItemNum" )
      } );
      foreach(object[] data in new object[][] {
        new object[] { "HDD (40GB)", 1980, 1 },
        new object[] { "ボールマウス", 400, 3 },
        new object[] { "キーボード (白)", 980, 3 }
      } ) {
        DataRow row = table.NewRow();
        row.ItemArray = data;
        table.Rows.Add( row );
      }
      
      return table;
    }
    
    // Hashtableのテストデータを作成
    private static Hashtable createHashtable() {
      Hashtable hash = new Hashtable();
      
      hash["ItemName"] = "HDD (80GB)";
      hash["UnitPrice"] = 5000;
      hash["ItemNum"] = 2;
      hash["Price"] = 10000;
      
      return hash;
    }
  }
}

改造など

いまのとこIDictionaryとDataRowの受け取りをオーバーロードで実装しているため、メソッドシグニチャを除けばまったく同じコードでかなりみっともないが、TypeをキーにしたHashtableに、テンプレート変数名から値を取り出すメソッドをくるんだdelegateを登録するように改造したりすれば他の型(ってちょっと思いつかないが)への対応もむずかしくないかと。

でも、NVelocityを先に見つけてりゃこんなんつくらんかったな。

HttpWebRequestでちとハマってた2007年09月04日 01時28分16秒

HttpWebRequestでCookieを使う

先週までのLL漬けからうって変わって、今日から久しぶりに.NETプログラミング。いや~、半年振りくらいのC#だもんで、結構コードかけなくなってるし。明日くらいまではちょっとリハビリ状態かしらん。

で、本題。これ知らずに今日は結構時間使ったのでメモ。でも.NET1.1だからいまやあまり役にたたん情報かも。

.NETアプリでWebアプリからちょっとしたデータを取得しようとしてたんだけど、まず手抜きでWebClient使って 認証リクエスト → データ取得処理 てな手順でコード書いてみたらうまくいかない。

Webアプリ側では認証とおってないリクエストはすべてログインフォームがあるページに飛ばすようにしてるんだけど認証リクエストの戻りからすでにログインフォームのコンテンツが返ってきてる状態。

おんなじ処理をJScriptでXHR使ってやってみたらこっちは認証とおって目的のデータがちゃんと取れてる。ふむ。

1.1のWebClientはかなり割り切った使い方をするものらしく(いままであんまり使ったことなかった)、URL直接食わせてコンテンツをダウンロードするくらいのインターフェイスしかないみたいなので、不精せずにHttpWebRequestに切り替えてみたんだけどこれも結果変わらず。

Webサーバのログを見てみると、一応希望のURLへのアクセスで200を返しているみたいだけどしつこくログインフォームのコンテンツだけが返ってくる。「ひょっとしてヘンなキャッシュ引いてるんじゃね?」とか思ったんだけどどうもそうでもない。

で、レスポンスヘッダ調べてるうちにPHPが返すセッションの値がリクエストごとに違っている。なんだ、そんな話かよ

要するにサーバがCookieセットしようとしてるけどWebRequest側でそれを受け付けてないので、当然認証していないことになってたわけだ。

CookieContainer

と、ここまでわかったけどはて、どうやってCookieサポートするんだ?

でMSDNとにらめっこしてたらHttpWebRequestにCookieContainerやらいうプロパティがあることを発見。

解説の項を見てみると

CookieContainer プロパティは、要求に関連付けられている Cookie を格納する CookieContainer クラスのインスタンスを提供します。 CookieContainer の既定値は null 参照 (Visual Basic では Nothing) です。GetResponse メソッドが返す HttpWebResponse の Cookies プロパティで Cookie を返すには、CookieContainer オブジェクトをそのプロパティに割り当てる必要があります。
などと、わかるようなわからんような記述がある。まぁHttpWebRequestでCookieの文言があるメンバも他に見当たらんし、これを使ってみるのかな。

で、このCookieContainerプロパティは型がまんま「System.Net.CookieContainer」クラスで、これがパラメータなしコンストラクタをサポートしてるので、こんな感じのコードを書いてみた(注:コード未検証。抜けとか間違いあるかも)。

CookieContainer cookie = new CookieContainer();

HttpWebRequest req = (HttpWebRequest)(WebRequest.Create( authUrl ));
req.CookieContainer = cookie;

using( HttpWebResponse res = (HttpWebResponse)(req.GetResponse()) ) {
	// 認証ページのコンテンツを取得
}

// 同じCookieContainerを使いまわしてデータ取得URLへアクセス
req = (HttpWebRequest)(WebRequest.Create( url ) );
req.CookieContainer = cookie;

using( HttpWebResponse res = (HttpWebResponse)(req.GetResponse()) ) {
	// データを取得
}

で試してみると、これで正解だったみたい。いやー、わからんかった。

つか、定番処理っぽい

ちと時間くったけど、まぁ最悪「MSXMLをtlbimpするかぁ?」とか考えてたワケで、それに比べればマシなのでほっとしてたんだけど、あとでなんとなくぐぐってみたらいっぱい引っかかるじゃん。探し方悪すぎ。orz

Win2000でPaint.NET2007年09月01日 15時49分13秒

やっと見つかりましたがな

以前「Paint.NETがインストールできない」というエントリを書いたが、なんとかインストールできるものが入手できた。それまでに若干の紆余曲折があったのでメモっておこう。

2.5をインストールしてみる

会社のPCには2.5のインストーラが残っていたのでそれを持ち帰ってインストールを試みたが、いきなり「Paint.NET requires the .NET Framework 1.1.」と拒絶された。実は今回のクリーンインストールでは、.NET関連は2.0のみインストールして1.1は入れていなかったのだ。

2.5は.NET 1.1系対応の最終版で、これ以降は.NET 2.0に移行していたのは知っていたが、サイドバイサイドで動くと思ってそのまま試してみたがインストールすらできなかった。むぅ。

インターネット図書館

となると、2.5以上3.0未満のバージョンをなんとか入手したくなるわけで、まずはWeb Archiveだのみとなる。

以前のバージョンは現在の公式ページに移転する前に入手していたのでそちらの古いサイトのアーカイブを探って、いくつかページをたどってみた。

後半のほうはすでに移転先へのリダイレクトになっていたが、2006/07/15以前はかろうじて元ページが残っていたのでそこからダウンロードページへ移動、ダウンロードリンクをクリックしてみたが、リンクされているダウンロードサイトでも現在のバージョンしかダウンロードできない。むぅぅ。

ファイル名がわかれば、ひょっとして...

しかし、上記ダウンロードページにたどり着いたことで、もともとのダウンロードファイル名が判明(PaintDotNet_2_xx.exe)したので、これで検索してみることにした。

ぐーぐる先生に「PaintDotNet_2_63.exe」を尋ねて返ってきたのをいくつかたどると、なぜかゲーム系のサイトのダウンロードページへ。いってみたらダウンロードできた。いやぁ、探せばなんとかあるもんだ。

さらに日本語化

ということで、なんとか2.63をインストールでき、さらに「Paint.NET - 日本語化WEB」さんから2.5向けの非公式言語パックをダウンロード、だめ元で適用してみたらうまくいってメニュー周りも日本語化され、快適な状態で使えるようになった。まぁ、リソースファイルだけなのでランタイムバージョンには依存しないか。これでやっと、ひと段落。

ただ、アプリケーションとしての使い勝手は3.0系には及ばないんだけど、まあちょっとした画像加工とかなら間に合うからこれでいいか。

蛇足:バージョンに関する訂正

以前のエントリでは2.5系と2.8系(?たぶん3.0前の最終がこれだと思った)とか書いていたが、2.x系の最終はどうやら2.72が最終らしく、このバージョンですでにWin2Kのサポートが切られているみたい。

まとめると、

  • 2.5以前 → .NET 1.1ベース。対応OSはWin2K/WinXP(Server2003も入るかも。間違いなく動きそうだけど)
  • 2.6系 → .NET2.0ベース。対応OSは変わらず。最終は2.64みたい。
  • 2.7系 → .NET2.0ベース。Win2Kサポート打ち切り。最終は2.72?
  • 3.x系 → .NET2.0ベース。対応はWinXP/Vista/Server2003。2007.9.1現在の最新は3.10
といった感じか。そろそろWin2Kも限界かなぁ。安定していて良いOSだと思うんだけど。