Hatena::ブログ(Diary)

Windchase

2007.8.26

Ruby の浮動小数点リテラルの精度を直した

http://www.rubyist.net/~matz/20070803.html#p04

コメント欄ではshiroさんが、「How to Read Floating Point Numbers Accurately (1990)」 by William D. Clingerという論文を紹介していた。

せっかくもらった情報なんで、Rubyでもちゃんと実装したいんだが、 私がやっても(算数的素養の無さから)グダグダに してしまいそうなんで、だれか手をあげてくれるとうれしいなあ。他力本願モード。

と Matz さんが書いていたので、論文を読みながらいろいろと試行錯誤してみたが、現状のコードを元に必要な精度を出すのは無理という結論に。

そこで調べてみると、ベル研の David M. Gay という人がすでに実装してることがわかった

その実装を Ruby 本体に取り込むパッチを書いて送ったら、1.9に取り込んでもらえた

パッチ前

$ uname -mrs
Darwin 8.10.1 i386

$ ./ruby -v
ruby 1.9.0 (2007-08-21 patchlevel 0) [i686-darwin8.10.1]
$ ./ruby -e 'printf("%21.0f\n",36893488147419107329.0)'
 36893488147419103232

パッチ後

$ ./ruby -v
ruby 1.9.0 (2007-08-22 patchlevel 0) [i686-darwin8.10.1]
$ ./ruby -e 'printf("%21.0f\n",36893488147419107329.0)'
 36893488147419111424

RubyCocoa の構造体クラスを使いやすくした

いままで、RubyCocoa で Cocoa の構造体クラス (NSRect、NSPoint、NSSize、NSRange) を使うときに、

a = NSRect.new
b = NSRect.new
if NSIntersectsRect(a, b)
  # 矩形が交差してる場合
end

のように書いていたが、これでは使いづらいので、

a.intersect?(b)

みたいに書けるようにした。(r2002)

追加したメソッドは、以下の通り。

NSRect

center矩形の中央の点を返す
contain?(rect or point)rect か point が矩形に含まれるなら true
empty?矩形のサイズが 0 なら true
inflate(dx, dy)X方向に dx、Y方向に dy だけ矩形を拡張
inset(dx, dy)X方向に dx、Y方向に dy だけ矩形を縮める
integralfloat の座標と大きさを int に丸める
intersect?(rect)矩形が rect と交差していれば true
intersection(rect)矩形と rect の交差矩形を返す
offset(dx, dy)矩形の位置を X方向に dx、Y方向に dy だけずらす
union(rect)矩形と rect の包含矩形を返す

NSPoint

in?(rect) / inRect?(rect)点が rect に含まれるなら true

NSRange

contain?(range or location)range か location が範囲に含まれるなら true
empty?範囲の長さが 0 なら true
intersect?(range)範囲と range が重なる部分があれば true
intersection(range)範囲と range の交差領域を返す
union(range)範囲と range の包含領域を返す

他にも、

p NSRect.new(150, 150, 100, 100)
#<OSX::NSRect:0x62218c>
p NSPoint.new(200, 200)
#<OSX::NSPoint:0x6285a0>
p NSSize.new(150, 300)
#<OSX::NSSize:0x62c9d4>
p NSRange.new(10, 8)
#<OSX::NSRange:0x62bef8>

と表示されていたのを、

#<OSX::NSRect x=150.0, y=150.0, width=100.0, height=100.0>
#<OSX::NSPoint x=200.0, y=200.0>
#<OSX::NSSize width=150.0, height=300.0>
#<OSX::NSRange location=10, length=8>

のように、きちんと内部の状態を出力するようにした。(r2003)

細かいことだけど、これでだいぶデバッグがやりやすくなると思う。

2007.8.15

LimeChat for OSX 0.9 リリース

必要と思われる機能のうち、かなりの部分を実装できたので、リリースしておきます。

RubyCocoa 0.12.0 でないと動かないので、そちらも更新をお願いします。

追加した機能

  • 自動オペレータ
  • Growl での通知
  • イベント音
  • フラッドプロテクション
  • URLとアドレスの右クリックメニュー
  • ログイン時に実行するコマンド
  • CTCP

未実装の機能

  • フォント設定
  • ユーザグループ
  • 文字属性の送信
  • ログファイル
  • DCC チャット
  • DCC ファイル転送のレジューム
  • ユーザスクリプト

ダウンロード

http://limechat.sourceforge.net/index_ja.html

GUI のイベントスクリプティング

http://subtech.g.hatena.ne.jp/secondlife/20070813/1186999047 より

まずイベントドリブンなプログラミングに慣れてないのが一つで。Flex のイベントや自前イベントやをただ単に投げまくってると、とりあえずは動くけど後からメンテし辛いスパゲッティコードができあがる。このスパゲッティコードは goto 文が乱立するコードよりも酷く、goto だったら割と行き先は把握できるけど、イベントを投げまくってるだけだと、どこでどのオブジェクトがこのイベントを受け取るかが解らない。解りづらい。いちいちソースコード grep ですね、おめでたいですね。あのイベントが発生してから、そのイベントが終了したら発生するイベントが終了したらウィンドウ閉じて、その間は別のイベントはブロックして/発生しないようにして、とかもうわけわかんない。これも GUI プログラミングをしたこと無いからのような気もしなくもないけど。

たしかに、そこらへんが GUI プログラミングは難しい。

イベントハンドラの中でネットワーク待ちとか時間がかかる処理を書くと、UI 全体が固まってしまうから、処理を細切れにして非同期で呼び出してつなげていかないといけないところとか。

ここらへんは、C# + WindowsForms でも、Mac OS X + Cocoa でも、Flex でも一緒。

A,B,C のデータ三つのロードが終わったら処理をしたい場合のパターンとか。もちろん安易に直列でやれば楽なんだろうけど、直列も並列もどちらでも同じように書いて動かしたいときや、統一された書き方で書きたいときのパターンが解らない。

自分もそういうことやりたいときあるなぁと思ったので、ruby で作ってみた。

http://limechat.net/sample/eventscript.rb

自前でイベントループ回してるので、イベントループの部分を捨てて ActionScript で書きなおせば Flex でも使えるパターンになってると思う。

直列に実行したい場合には、

scenario = sequence(self) << [
  get('http://www.google.com/'),
  get('http://www.yahoo.com/'),
  get('http://www.apple.com/'),
  get('http://www.mozilla.org/'),
]
scenario.start

という感じで、並行実行したいなら、

scenario = concurrent(self) << [
  get('http://www.google.com/'),
  get('http://www.yahoo.com/'),
  get('http://www.apple.com/'),
  get('http://www.mozilla.org/'),
]
scenario.start

こんな感じに書ける。

実行結果を見ると、

scenario start
  url get start: http://www.google.com/
  url get success: http://www.google.com/
  url get start: http://www.yahoo.com/
  url get success: http://www.yahoo.com/
  url get start: http://www.apple.com/
  url get success: http://www.apple.com/
  url get start: http://www.mozilla.org/
  url get success: http://www.mozilla.org/
scenario end
concurrent scenario start
  url get start: http://www.google.com/
  url get start: http://www.yahoo.com/
  url get start: http://www.apple.com/
  url get start: http://www.mozilla.org/
  url get success: http://www.google.com/
  url get success: http://www.apple.com/
  url get success: http://www.yahoo.com/
  url get success: http://www.mozilla.org/
scenario end

concurrent のほうは並行実行できてることがわかる。

シナリオを入れ子にすることもできる。

scenario = sequence(self) << [
  lambda { puts '* start' },
  concurrent << [
    get('http://www.google.com/'),
    get('http://www.yahoo.com/'),
    get('http://www.apple.com/'),
    get('http://www.mozilla.org/'),
  ],
  sequence << [
    get('http://www.cnn.com/'),
    get('http://del.icio.us/'),
    lambda { puts '* waiting for 2 seconds' },
    wait(2),
    concurrent << [
      get('http://tumblr.com/'),
      get('http://flickr.com/'),
    ],
  ],
  lambda { puts '* end' },
]
scenario.start

実行結果は、以下のような感じ。(見やすいように整形済み)

scenario start
  * start
  concurrent scenario start
    url get start: http://www.google.com/
    url get start: http://www.yahoo.com/
    url get start: http://www.apple.com/
    url get start: http://www.mozilla.org/
    url get success: http://www.google.com/
    url get success: http://www.apple.com/
    url get success: http://www.yahoo.com/
    url get success: http://www.mozilla.org/
  scenario end
  scenario start
    url get start: http://www.cnn.com/
    url get success: http://www.cnn.com/
    url get start: http://del.icio.us/
    url get success: http://del.icio.us/
    * waiting for 2 seconds
    concurrent scenario start
      url get start: http://tumblr.com/
      url get start: http://flickr.com/
      url get success: http://tumblr.com/
      url get success: http://flickr.com/
    scenario end
  scenario end
  * end
scenario end

全部非同期で、内部ではスレッドを起こして実行してるので、

scenario = sequence(self) << [
  lambda { sleep 100000 },
]
scenario.start

とか書いても、イベントループは回ったままなので、UI は固まったりしない。

あと、直列で実行するときには、前の処理の結果を使いたい場合が多いと思うので、

scenario = sequence(self) << [
  lambda { 2 + 3 },
  lambda {|v| puts v },
  get('http://tumblr.com/'),
  lambda {|v| puts v },
]
scenario.start

みたいに書けるようにしておいた。

scenario start
  5
  url get start: http://tumblr.com/
  url get success: http://tumblr.com/
  <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
  <html><head>
  ...
scenario end

(追記: 8.18)

flashrod さんが AS3 版を作られた。

http://d.hatena.ne.jp/flashrod/20070815#1187191083

2007.8.2

Rails じゃいけない理由

Rails じゃなくて Perl ベースのものを使う理由ってなんだろう。

route が遅いのはともかくとして、DB の負荷分散にそこそこ手間がかかるからだろうか。Ruby より Perl をよく知っているからだろうか。

37 signals のサービスにはソーシャルな機能がなく、基本的にユーザごとに空間が分かれている。だから、DB の負荷分散をやろうと思えば、単純にユーザごとに DB クラスタを分割すればいいことになる。

たぶん、それが Rails にテーブルごとに別の DB サーバを見に行くタイプの負荷分散機能がなかった理由だと思う。

その問題が解決したとして、他に何か理由があるのかな?

2007.8.1

del.icio.us の onkeyup 問題

del.icio.us の edit フォームで日本語入力がうまくいかない問題を直して、レポートしておいた。

http://limechat.net/sample/delicious_keyup_fix/

これは、onkeyup で enter を拾ってすぐに post しているのが原因で、本来は日本語変換中には発生しない onkeypress を使うべきところなんだけど、英語圏ではそれでも別に困らないのだろう。

Greasemonkey スクリプトも作ってみた。

http://limechat.net/sample/delicious_keyup_fix/delicious_keyup_fix.user.js

(追記)

すでに kzys さんが作っていた。

http://blog.8-p.info/articles/2006/12/20/del-icio-us-ime

そちらのほうが、他の部分も直るのでいいと思う。

(追記: 8/3)

del.icio.us の人から返事が来た。fix する方法を検討中とのこと。

(追記: 2008/8/4)

delicious.com にリニューアルされたときに、この問題も直っている。