[Tcl] How to know decimal representation of binary float

I wrote experimental script to convert binary float to decimal.

References:
http://www.h-schmidt.net/FloatApplet/IEEE754.html

http://sandbox.mc.edu/~bennet/cs110/flt/ftod.html

set exponent 00000001
set mantissa 10001001001101110100110
 
binary scan [binary format B* $exponent] c exponent
set exponent [expr {$exponent - 127}]
 
if {$exponent <= -127} {
	set mantissa "$mantissa"
} else {
	set mantissa "1$mantissa"
}
 
set i 0
set sum 0.0
foreach b [split $mantissa {}] {
	set sum [expr {$sum + pow(2,$exponent-$i)*$b}]
	incr i
}
puts $sum
#=> 1.80555933856e-038

[Tcl] gets or read - which is faster

Usually, I use "gets" to read a text file.
But I tried "read" to know which is really faster.

set exponent 00000001
set mantissa 10001001001101110100110
 
binary scan [binary format B* $exponent] c exponent
set exponent [expr {$exponent - 127}]
 
if {$exponent <= -127} {
	set mantissa "$mantissa"
} else {
	set mantissa "1$mantissa"
}
 
set i 0
set sum 0.0
foreach b [split $mantissa {}] {
	set sum [expr {$sum + pow(2,$exponent-$i)*$b}]
	incr i
}
puts $sum
#=> 1.80555933856e-038

If you read the whole text, "read" is a little faster.
But if you stop reading in the middle of the content, "gets" is more efficient.

"read" uses more memory as the file size increases.

Though "read" is a little bit faster in some situation,
I think "gets" is more efficient in most cases.

Decoding IEEE754 Single Precision Floating Point Number in Tcl - NaN, Infinite etc.

Each version of Tcl recognizes big-endian IEEE754 encoded exceptional single precision floating point numbers like followings.

proc test_gets {f} {
	set ch [open [format {|nkf -s "%s"} $f]]
	while {![eof $ch]} {
	    if {[gets $ch line] != -1} {
	        puts $line
	    }
	}
	close $ch
}
 
proc test_read {f} {
	set ch [open [format {|nkf -s "%s"} $f]]
	set txt [read $ch]
	close $ch
	set lines [split $txt \n]
	foreach line $lines {
	    puts $line
	}
}
 
set filename [file join [pwd] "hoge.txt"]
set bench1 [time {test_gets $filename} 10]
set bench2 [time {test_read $filename} 10]
 
puts $bench1
puts $bench2

# 1162 bytes
# gets => 39963 microseconds per iteration
# read => 27447 microseconds per iteration

# 10350085 bytes
# gets => 6420444 microseconds per iteration
# read => 6246802 microseconds per iteration

ipconfigを呼ばないでMACアドレスのリストを取得する

TclからNICのMACアドレスのリストを取得する場合、ipconfig/allの出力から取り出してたけど、Windowsのバージョンやロケールによって出力が変わるものを使うのはどうも気に食わんかったので、ちゃんとそれ用のWindows APIを使いたいと思っていた。
今回twapiを使えばできることが分かったので、メモしておく。

# IEEE754 Reference
# http://babbage.cs.qc.edu/courses/cs341/IEEE-754references.html
namespace eval IEEE754 {
	variable CONST {
		Quiet-NaN_MIN            FFFFFFFF
		Quiet-NaN_MAX            FFC00001
		Indeterminate            FFC00000
		Signaling-NaN_MIN        FFBFFFFF
		Signaling-NaN_MAX        FF800001
		-Infinity                FF800000
		NegativeNormalized_MIN   FF7FFFFF
		NegativeNormalized_MAX   80800000
		NegativeDenormalized_MIN 807FFFFF
		NegativeDenormalized_MAX 80000001
		-0                       80000000
		+0                       00000000
		PositiveDenormalized_MIN 00000001
		PositiveDenormalized_MAX 007FFFFF
		PositiveNormalized_MIN   00800000
		PositiveNormalized_MAX   7F7FFFFF
		Infinity                 7F800000
		Signaling+NaN_MIN        7F800001
		Signaling+NaN_MAX        7FBFFFFF
		Quiet+NaN_MIN            7FC00000
		Quiet+NaN_MAX            7FFFFFFF
	}
}
 
if {$::tcl_version >= 8.5} {
	proc IEEE754::string_reverse {s} {
		string reverse $s
	}
} else {
	package require struct::list
	package require textutil
	proc IEEE754::string_reverse {s} {
		join [struct::list reverse [textutil::splitn $s 1]] ""
	}
}
 
proc IEEE754::test {} {
	variable CONST
	puts "tcl_patchLevel = $::tcl_patchLevel"
	foreach {name hex} $CONST {
		set name [format %-24s $name]
		binary scan [string_reverse [binary format H* $hex]] f a
		puts "FLOAT: $name $a"
	}
}
 
IEEE754::test

# tcl_patchLevel = 8.4.19
# FLOAT: Quiet-NaN_MIN            -1.#QNAN
# FLOAT: Quiet-NaN_MAX            -1.#QNAN
# FLOAT: Indeterminate            -1.#IND
# FLOAT: Signaling-NaN_MIN        -1.#QNAN
# FLOAT: Signaling-NaN_MAX        -1.#QNAN
# FLOAT: -Infinity                -1.#INF
# FLOAT: NegativeNormalized_MIN   -3.40282346639e+038
# FLOAT: NegativeNormalized_MAX   -1.17549435082e-038
# FLOAT: NegativeDenormalized_MIN -1.17549421069e-038
# FLOAT: NegativeDenormalized_MAX -1.40129846432e-045
# FLOAT: -0                       -0.0
# FLOAT: +0                       0.0
# FLOAT: PositiveDenormalized_MIN 1.40129846432e-045
# FLOAT: PositiveDenormalized_MAX 1.17549421069e-038
# FLOAT: PositiveNormalized_MIN   1.17549435082e-038
# FLOAT: PositiveNormalized_MAX   3.40282346639e+038
# FLOAT: Infinity                 1.#INF
# FLOAT: Signaling+NaN_MIN        1.#QNAN
# FLOAT: Signaling+NaN_MAX        1.#QNAN
# FLOAT: Quiet+NaN_MIN            1.#QNAN
# FLOAT: Quiet+NaN_MAX            1.#QNAN

# tcl_patchLevel = 8.5.9
# FLOAT: Quiet-NaN_MIN            -NaN(7ffffe0000000)
# FLOAT: Quiet-NaN_MAX            -NaN(20000000)
# FLOAT: Indeterminate            -NaN
# FLOAT: Signaling-NaN_MIN        -NaN(7ffffe0000000)
# FLOAT: Signaling-NaN_MAX        -NaN(20000000)
# FLOAT: -Infinity                -Inf
# FLOAT: NegativeNormalized_MIN   -3.4028234663852886e+38
# FLOAT: NegativeNormalized_MAX   -1.1754943508222875e-38
# FLOAT: NegativeDenormalized_MIN -1.1754942106924411e-38
# FLOAT: NegativeDenormalized_MAX -1.401298464324817e-45
# FLOAT: -0                       -0.0
# FLOAT: +0                       0.0
# FLOAT: PositiveDenormalized_MIN 1.401298464324817e-45
# FLOAT: PositiveDenormalized_MAX 1.1754942106924411e-38
# FLOAT: PositiveNormalized_MIN   1.1754943508222875e-38
# FLOAT: PositiveNormalized_MAX   3.4028234663852886e+38
# FLOAT: Infinity                 Inf
# FLOAT: Signaling+NaN_MIN        NaN(20000000)
# FLOAT: Signaling+NaN_MAX        NaN(7ffffe0000000)
# FLOAT: Quiet+NaN_MIN            NaN
# FLOAT: Quiet+NaN_MAX            NaN(7ffffe0000000)

# tcl_patchLevel = 8.6b1.2
# FLOAT: Quiet-NaN_MIN            -NaN(7ffffe0000000)
# FLOAT: Quiet-NaN_MAX            -NaN(20000000)
# FLOAT: Indeterminate            -NaN
# FLOAT: Signaling-NaN_MIN        -NaN(7ffffe0000000)
# FLOAT: Signaling-NaN_MAX        -NaN(20000000)
# FLOAT: -Infinity                -Inf
# FLOAT: NegativeNormalized_MIN   -3.4028234663852886e+38
# FLOAT: NegativeNormalized_MAX   -1.1754943508222875e-38
# FLOAT: NegativeDenormalized_MIN -1.1754942106924411e-38
# FLOAT: NegativeDenormalized_MAX -1.401298464324817e-45
# FLOAT: -0                       -0.0
# FLOAT: +0                       0.0
# FLOAT: PositiveDenormalized_MIN 1.401298464324817e-45
# FLOAT: PositiveDenormalized_MAX 1.1754942106924411e-38
# FLOAT: PositiveNormalized_MIN   1.1754943508222875e-38
# FLOAT: PositiveNormalized_MAX   3.4028234663852886e+38
# FLOAT: Infinity                 Inf
# FLOAT: Signaling+NaN_MIN        NaN(20000000)
# FLOAT: Signaling+NaN_MAX        NaN(7ffffe0000000)
# FLOAT: Quiet+NaN_MIN            NaN
# FLOAT: Quiet+NaN_MAX            NaN(7ffffe0000000)

ちなみにコマンドプロンプト上ではchcpで932以外のコードページを指定してからipconfigすれば英語の出力になる。
これをTclからもできればよかったんだけど、複数のコマンドをパイプ経由で渡すことはできないようだ。

cmd /c "chcp 437 & ipconfig/all"

というのをexecしたりしても、標準出力を受け取ることができなかった。
一時的にバッチファイルを作り、これを実行してもよいが、美しくない。

そういうわけで、上記のテクニックが今のところ一番いいと思う。

AssocQueryString

AssocQueryStringを使って拡張子に関連付けられた実行ファイルパスを取得するサンプル。
本当はTclで使うからFfidlとかでやりたかったけど、難しかったのでCのコンソールアプリケーションにしました。
勉強のため無駄にUNICODE対応にしています。

使い方は、
assoc_query_string.exe [.extension|extension|file_name.extension|file_path.extension]

拡張子を渡すか、ファイルパスを渡すと、「開く」アクションに関連付けられたEXEのフルパスを標準出力に出力します。

AssocQueryString screenshot
ソースコード: assoc_query_string.zip

In which case vwait never returns

If your Tcl script uses many afters and vwaits, sometimes it happens that vwait never returns even if its target variable is set. I didn't understand why my application get into such a situation.

Here is a sample script that causes first call of vwait never to return.
Run this script in wish.

console show
 
rename vwait original_vwait
proc vwait {var} {
	global last_vwait_var
	puts "enter vwait $var"
	set last_vwait_var $var
	original_vwait $var
	puts "leave vwait $var"
}
set last_vwait_var {}
 
proc set_var1 {val} {
	global var1
	puts "set var1 $val"
	set var1 $val
}
 
proc set_var2 {val} {
	global var2
	puts "set var2 $val"
	set var2 $val
}
 
after 1000 {
	after 1000 {
		set_var1 go
	}
	vwait ::var2
}
 
vwait ::var1
set_var2 go

I expect the following output;

enter vwait ::var1
enter vwait ::var2
set var1 go
leave vwait ::var1
set var2 go
leave vwait ::var2

Contrary to my expection, the actual console output is;

enter vwait ::var1
enter vwait ::var2
set var1 go

When I call "set_var2 go" interactively, the output follows,

% set var2 go
go
leave vwait ::var2
leave vwait ::var1
set var2 go

This means that if you call nested vwaits, the first call of a vwait blocks until all of the nested vwait returns.

Even if you understand this characteristics of vwait, you can run into trouble in network programs. I guess this is caused by the following characteristics of a network program.

  • It doesn't know when a response comes.
  • It uses recursive call of vwait when resending a command on a response timeout.
  • A polling loop interrupts normal sequence order.

Sorry to suck at explaining.

I made an application that communicates with multiple I/O devices which have UDP servers.
I designed it in an object-oriented way and encapsulated UDP client sockets in each of the device objects. As the UDP server device doesn't support push-notification, I had to ask their status at a certain intervals: a polling loop for each device. The polling loops are recursive call of afters. They interrupt into the normal command sequence. I guess this causes unexpected vwait call.

My design was successful in the other platforms like Ruby and C# which have built-in preemptive threads. But in Tcl, I had to place a single manager object for highly concurrent parts of the program.

Tcl has many OOP extensions. We can call Tcl as multi paradigm programming language. But as to this kind of thing, Tcl is not truely object-oriented; it forces tight dependencies between objects.

Tcl 8.6 will gain coroutines. I hope it will introduce a true OOP into Tcl.

Tcl/Tk 8.5 Programming Cookbook

初めてソフトを公開してみた。DateTimeConverter

他人に見せられるほどのプログラムでもないのですが、試しに公開してみました。

https://sourceforge.net/projects/dtconverter/

追記: Softpediaにも載せてもらいました。スクリーンショットのロケールが英語になってる。自分では試せなかったのでちゃんと動いているようでよかったです。

http://goo.gl/zZxFe

動作環境:  .NET Framework 4.0 Client Profile

WindowsからTortoiseSVNでプロジェクトのレポジトリにコミットする方法は以下を参考にしました。

https://sourceforge.net/apps/trac/sourceforge/wiki/TortoiseSVN%20instructions

なにをするソフトかというと、日時を指定してそのシリアル値を調べたり、逆にシリアル値から日時を調べたりするソフトです。

時刻のシリアル値というのは、エポック秒などとも呼ばれたりもしますが、時刻を表す整数や実数のことです。

コンピュータでは"2011年8月5日 14時3分21秒"とかいう文字列で時刻を記憶するのではなく、西暦何年1月1日の午前0時から数えて何秒目とかいう数え方をします。

シリアル値の始まり(時刻ゼロ)や時刻の刻みの細かさなどに関しては、処理系によってそれぞれ異なります。

このソフトを使うと、代表的な時刻のシリアル値を相互に変換できます。

DateTimeConverter screenshot 1 DateTimeConverter screenshot 2

代表的、というのは私がよく使うというだけなので、誰にとっても役立つものなのかは分かりません。今のところ、拡張性があるように作ってもいないので、需要があればなんか考えます。
公開した後で、名前がひんしゅくを買いそうな名前だなと気づいたものの、時間ないのでとりあえずそのまま。

ttk::comboboxのドロップダウンリストのフォントを変更する

ttk::comboboxには-fontオプションがあるのに、ドロップダウンリスト(popdown)のフォントを変えるオプションはありません。私の環境ではTkDefaultFontがTahoma 8ptになってるので、フォントが小さすぎて困ります。

Tcler's Wikiには全てのウィジェットで共通の設定をする方法が紹介されています。
Tcler's Wiki - ttk::combobox

# option add *TCombobox*Listbox.font MyComboboxFont

しかし、個別に大きさを変更したいこともあると思います。
comboboxのリストはウィジェットなのですが、通常、最初に表示するときに生成されます。
しかし、以下のコマンドを使って今すぐウィジェットを作成することもできます。
ttk::combobox::PopdownWindow cb
これを呼んでおけばフォントを変えてやることができます。

package require Tk
 
font create MyComboboxFont -family Arial -size 20
 
ttk::combobox .cb -font MyComboboxFont -values {ichiro jiro saburo}
set popdown [ttk::combobox::PopdownWindow .cb]
$popdown.f.l configure -font MyComboboxFont
 
place .cb -x 10 -y 10

ただ、ttk::comboboxだけがfontオプションを持っているのはスタイルのコンセプトに反するとか、fontを指定できるのにpopdownのフォントが指定できないとかいろいろ突っ込みどころの多いウィジェットなので、そのうち仕様が変更されるかもしれません。まあ、このくらいの労力ならむしろこのままでもいいんですが。。。

暗号化APIを有効にしたSQLite3のTclバインディングをコンパイルする

コンパイルに使用したもの
ActiveTcl 8.4.19.5 http://www.activestate.com/activetcl/downloads
wxSQLite3(wxsqlite3-2.1.2.zip) http://sourceforge.net/projects/wxcode/files/Components/wxSQLite3/
SQLite3ソースコード(sqlite-autoconf-3070701.tar.gz) http://www.sqlite.org/download.html
MinGW(mingw-get-inst-20110530.exe) http://sourceforge.net/projects/mingw/files/Automated%20MinGW%20Installer/mingw-get-inst/

作業フォルダはどこでもいいですが、今回はc:\srcとします。
そこにSQLite3ソースコードとwxSQLite3を展開します。
こんな感じ。

# option add *TCombobox*Listbox.font MyComboboxFont

以下のフォルダ内の全てのファイルを
C:\src\wxsqlite3-2.1.2\sqlite3\secure\src\codec-c

ここに上書きコピーします。
C:\src\sqlite-autoconf-3070701\tea\generic

そして、以下のソースコードをテキストエディタで開きます。
C:\src\sqlite-autoconf-3070701\tea\generic\tclsqlite3.c

4行目の"../../sqlite3.c"を

package require Tk
 
font create MyComboboxFont -family Arial -size 20
 
ttk::combobox .cb -font MyComboboxFont -values {ichiro jiro saburo}
set popdown [ttk::combobox::PopdownWindow .cb]
$popdown.f.l configure -font MyComboboxFont
 
place .cb -x 10 -y 10

"sqlite3secure.c"に書き換える。

#ifdef USE_SYSTEM_SQLITE
# include 
#else
#include "sqlite3secure.c"
#endif

これで、コンパイルの準備はできました。

さて、MinGW shellを起動しましょう。

ディレクトリを移動してコンパイルします。

cd /c/src/sqlite-autoconf-3070701/tea
$ ./configure CFLAGS="-DSQLITE_HAS_CODEC"
$ make

デフォルトではAES128コーデックが使われます。
試してませんが、AES256にしたければ、以下のようにすればよいと思います。

$ ./configure CFLAGS="-DSQLITE_HAS_CODEC -DCODEC_TYPE=CODEC_TYPE_AES256"

wxSQLite3 AES128ならGUIツールのSQLite2009 Proが対応していますが、AES256に対応しているツールはなさそうなので、セキュリティの強化以外の理由でAES256を選択する理由はないと思います。

これで以下のファイルができました。これ単体でTclのパッケージです。

C:\src\sqlite-autoconf-3070701\tea\sqlite3771.dll

直接tclshからloadするか、pkgIndex.tclとともにTcl/libにインストールしてpackage requireすることもできます。

さて、テストしてみましょう。

load sqlite3771.dll Sqlite3

# create plain database file
sqlite3 pdb plain.db

# create encrypted database file
sqlite3 sdb secret.db -key password

# SQL test script
# 1. create table
# 2. populate test data
# 3. execute query
set sql {
	create table users (
		id integer primary key autoincrement not null,
		name text,
		age integer
	);
	insert into users (name, age) values ("山田太郎", 30);
	select * from users;
}
 
pdb eval $sql
#=>; 1 山田太郎 30
pdb close
 
sdb eval $sql
#=>; 1 山田太郎 30
sdb close

# Re-open plain.db
sqlite3 pdb plain.db
pdb eval {
	select * from users;
}
#=>; 1 山田太郎 30
pdb close

# Re-open secret.db without a key
sqlite3 sdb secret.db
sdb eval {
	select * from users;
}
#=>; file is encrypted or is not a database
sdb close

既存のデータベースファイルを暗号化するには、rekeyするか、dumpを取得して
暗号化した新規データベースでrestoreすればOKです。
この作業には、wxSQLite3に付属するコンパイル済みのshellを使用してください。
C:\src\wxsqlite3-2.1.2\sqlite3\secure\aes128\sqlite3shell.exe

追記:Tclからもできます。

load sqlite3771.dll Sqlite3
# Encrypt plain database
sqlite3 pdb plain.db
pdb rekey "password"
pdb close

# Reopen as plain database
sqlite3 pdb plain.db
pdb eval {
	select * from users;
}
#=>; file is encrypted or is not a database

# Decript secret database
sqlite3 sdb plain.db -key password
sdb rekey ""; # specify null string as encryption key
sdb close

# Re-open plain.db
sqlite3 pdb plain.db
pdb eval {
	select * from users;
}
#=>; 1 山田太郎 30
pdb close

最後はちょっとはしょった説明になりましたが、できてしまえば結構簡単です。
tclsqliteの暗号化APIについてのドキュメントはないのですが、ネイティブの関数と基本的に変わりはないと思います。
間違ったコマンドを与えてやるとエラーメッセージに関数リストが出てきたり、
関数に間違った引数を与えてやることで使い方が出てきたりするので、いろいろ試してみるとよいと思います。

(tea) 1 % load sqlite3771.dll Sqlite3
(tea) 2 % sqlite3 sdb secret.db -key password
(tea) 3 % sdb ?
bad option "?": must be authorizer, backup, busy, cache, changes, close, collate, collation_needed, commit_hook, complete, copy, enable_load_extension, errorcode, eval, exists, function, incrblob, interrupt, last_insert_rowid, nullvalue, onecolumn, profile, progress, rekey, restore, rollback_hook, status, timeout, total_changes, trace, transaction, unlock_notify, update_hook, version, or wal_hook

なお、.NET Frameworkから使えるADOプロバイダとしてSystem.Data.SQLite.dllが暗号化に対応していますが、これはRSA-MS Cryptだそうです。名前しか分かりませんがとりあえずAESとは別物です。SQLite Encryption Extension($2,000)は4つのコーデックがコンパイル時に選択できるようです。

Visual Studio 2010でOpenSSLをビルドする

ここに行けばWindows用のDLLを入手することはできるんですが、
http://www.slproweb.com/products/Win32OpenSSL.html

今回はスタティックライブラリが欲しかったので、自分でビルドしてみました。

ここを参考にしました。
http://shishi.syuriken.jp/openssl.html

使ったプログラム
Visual Studio 2010 Professional SP1
Microsoft Windows SDK v7.1 : http://goo.gl/2sFTo
StrawberryPerl 5.12.3.0 : http://strawberryperl.com/
NASM : http://www.nasm.us/

準備
perl.exeとnasm.exeにはパスを通しておく。

OpenSSL本家から最新ソースをダウンロード。現在1.0.0dが最新。
http://www.openssl.org/source/

どこでもいいですが、ここでは以下に展開するとします。
c:srcopenssl-1.0.0d

コマンドプロンプトで作業します。
cd c:srcopenssl-1.0.0d

perl Configure VC-WIN32 --prefix=c:/openssl
※--prefix=でインストールディレクトリを指定。

nasmで最適化する。
msdo_nasm

Microsoft Windows SDK v7.1をインストールすると環境設定用のバッチスクリプトがついてくるので、
スタートメニュー>全てのプログラムから以下を実行する。
Microsoft Windows SDK v7.1Windows SDK 7.1 Command Prompt

開いたプロンプトで以下を実行すると、ビルドが始まります。
数分かかります。
nmake -f msntdll.mak

検証する。
nmake -f msntdll.mak test

最後に以下が表示されれば検証OK。
passed all tests

以下を実行すると、c:/opensslに実行ファイルやヘッダファイルがコピーされる。
nmake -f msntdll.mak install

こんな感じ。

C:>tree openssl
フォルダー パスの一覧: ボリューム OS
ボリューム シリアル番号は 00000002 54BE:1C45 です
C:OPENSSL
├─bin (dll, exe)
├─include
│ └─openssl (ヘッダ群)
├─lib (スタティックライブラリ)
│ └─engines (dll)
└─ssl (openssl.cnf)