もっと簡単に超概算見積もりver2

以前、超概算見積もり を考えたのはすでに10年前になるので、この際だからバージョンアップ版をあげておこう。この超概算見積もりの手法は PMBOK で言うところの超概算見積もりとは違う。だが、おそらく現実に即しているはずだ。

ざっくり規模見積もりをしたいときに、機能から見積もり始めるのは間違いだ。特に受託開発の場合には、営業さんの云うところの「ざっくり見積もる」というのは、予算と期間になる。いわゆる、QCD で言うところのコストと期限(Delivery)になる。

このため、各種の見積もり手法では「規模見積もり」→「予算見積もり」→「スケジュール」の順番で見積もりをしていくのだが、実際のところ、

  • 予算が限られている(Cost)
  • リリース日などの期限が決まっている(Delivery)

ことが多いので、Cost と Delivery の制約を先に埋めてしまうほうがよい。

予算(コスト)が決まっている場合

お客や営業さんから「ざっくり予算はいくらでしょうか?」と聞かれることが多いだろうが、言っている人の中にはすでに「ざっくりとした予算」があることが多い。だから、「ざっくりとした予算は?」と聞かれたならば、「あれとこれとこれを組み合わせたら、最大で1億円ですね」と答えるとよい。当然「1億円なんて予算はないよ」という顔をするから(億円単位の大規模開発ならば別だろうけど)、「あれとこれを削って、ここだけ作れば100万円ですかね」と、あきらかに機能不足な話の低予算を提示するとよい。そうすると「そんなに機能が少ないんじゃあだめで、これとこれぐらいはないと」という回答が得られる。

つまり、最大限と最小限の間に「予算」は決まっているのは確かなことなのだ。

その間の部分で、仮決めで(相手の懐を予想しながら、あるいは直接「予算はどれくらいでしょうか?」と聞いても良い)、500万円ぐらいの予算で、と決めたとしよう。

人月計算をやりやすくするため単価100万円/月のソフトウェア開発者を割り当てるとすると、単純計算する超概算見積もりで「予算は500万円、開発期間は5か月」というプロジェクトが生まれる。開発期間は5か月が上限なので、これを超えるとプロジェクトは赤字だ。赤字なプロジェクトは受けてはいけない案件である(時と場合によるけど)。

これを自社で行っているソフトウェア開発手法で見積もってみる。

  • スクラムのスプリントを使った場合、スプリントが2週間であれば10スプリント
  • チケット駆動で、3チケット/日とすれば、3x20x5 = 300 チケット
  • WBS やタスクで週平均3程度を想定すれば、20/3 x 5 = 33 WBS

このように スプリント/チケット/WBS/タスク の上限が決まってくる。

チケットの内容をすべて埋めなくても良い。ある程度チケットの内容を埋めてしまえば、

  • 予算以内におさまりそうか(チケットが枯渇しなさそうか)
  • 予算以上になりそうか(チケットが大幅に上回るだろうか)

ということがわかる。超過する場合には、500万円という予算を優先して(お客がそれしか出せないのだから仕方がない)、

  • 機能(チケット)を減らす → 何か盛り込みすぎている
  • 単価を下げる → 並行プロジェクトにより、スケジュールに余裕を持たせる。薄く引き伸ばす

ことになる。うすく引き伸ばす方法は、並行プロジェクトを使ったリスク分散の方法だ。これはまた別の機会に話す。

スケジュール(期限)が決まっている場合

年度末までに開発を終えるとか、元号対応だとか別の理由でスケジュールが決まっている場合がある。このときも「ざっくりと予算と期間はどのくらいですか?」と聞かれるのだが、期限は後ろに倒せない。

例えば、半年後にリリースが決まっている6か月のプロジェクトを想定しよう。すると、単価100万円/月の場合は、600万円の予算となる。ここからチケット数を決めて、概算で割り振ってみる方法は、予算優先の場合と同じだ。

期限内におさまりそうにない(必要なチケットの数が予想よりも多い)場合は、

  • 人数を増やす → 単純に馬力を増加させる。当然、予算は増加する。
  • 機能を減らす → 期限優先なので、優先度が低い機能は落としてしまう。

リリース日を後ろ倒しにできないので、保険(2割増しあるいは5割増し)を入れておいてリリース日に確実に間に合うようにすることを忘れずに。

予算(コスト)や期限(スケジュール)で概算する

最初から機能(FP法やCOCOMO2など)を使って予算や期間を見積もりしてしまと、結局のところお客が想定している予算や期限と食い違いがでてしまって「機能」自体の見積もりしなおしになる。だから、予算や期限から機能の方を逆算して、規模見積もりが分かったところから再び予算や期間を見積もり直せばよい。特に、客相手の受託開発の場合にはここがポイントになる。

プロジェクト実行時の再見積もり

超概算見積もりをした後に再チェックをして、予算や期間ができたとしよう。このときの見積もりの根拠は「機能」がベースになっているので、プロジェクト実行時にこの機能(規模見積もり)が正しいかどうかを再チェックする。

さきに書いた通り、受託開発では予算と期限が優先なのだから、プロジェクト実行時に注目するポイントは以下になる。

  • 機能が増加していないか?
    → チケットが増えていないか?規模見積もりの前提条件がずれていないかをチェックする。
  • チケットの消化数は適切か?
    → バーンダウンチャートでチケットを消化する傾きをチェックする。
    → 期限に間に合うか?
  • 並行プロジェクトがある場合は、適切にチケットが消化されていないか?
    → 他方のプロジェクトに押されていないか?
    → 仕事の時間が増えていないか。生産効率が落ちていないか?

チケットの消化数を守るために、残業や休出をして補っている場合、勤務時間が増えているのだから「生産効率が落ちている」状態になる。生産量(この場合は消化するチケット数)は同じで、時間が掛かりすぎているのだから、仕事の効率が落ちているとみる。

これらをプロジェクト実行時にはチェックして、対処(人を増やす、予算を増やす、期限をずらす、機能を減らす)を行うのがプロジェクトマネジメントである。

計測のための数え上げに関しては、デマルコ氏の進捗管理やマコネル氏のWBS等の数え上げによる見積もり手法を参考にするとよい。

カテゴリー: OpenCCPM | もっと簡単に超概算見積もりver2 はコメントを受け付けていません

プロジェクトマネジメントの工学的アプローチのメモ書き

ソフトウェア開発におけるプロジェクトマネジメント手法を工学的にアプローチするメモを流しておこう。ちなみに、この記事には「モチベーション」という言葉は出てこないし「スーパープログラマ」も「カリスママネージャー」も出てこない。あくまで工学/自然科学的なアプローチで解決をしようという話である。ある意味「働き方改革」も出てこない。

前提条件

「プロジェクト」とは何かというところからスタートするほどでもないが、この手法には前提条件がある。

  • WBS/チケット/タスクが全て消化されれば、プロジェクトが終了する

という条件が必要になる。一見当たり前のように見えるけど、研究プロジェクトや目的達成型のプロジェクトの場合にはこのマネジメント手法では無理がある。研究プロジェクトは数々の試行錯誤が必要なので、チケットに対する時間効果が判別つかない。また一定の目的を達成する(製品販売数とかユーザー数目標とか)プロジェクトの場合も、達成するまでにいろいろな試行錯誤が出てくるので、形式的なプロジェクトマネジメント手法は向かない。この場合は、(おそらく)イノベーションマネジメントになると思う。こっちの方はまた別の機会に書くことにする。

WBSあるいはチケットあるいはタスクは、何らかの仕事の「粒」となる粒度ともいう。面倒なので以後「チケット」で用語を統一するが、それぞれの違いは

  • PMBOK的にトップダウンで仕事を出す場合は「WBS」
  • ボトムアップ的に仕事を出す場合は「タスク」
  • チケット駆動の場合は「チケット」

ということになるが、ひとまとめにして「チケット」と呼ぶ。
このチケットの数や大きさ(作業期間や外注金額など)などは、プロジェクト開始以降から変化してもよいものとする。PMBOKの場合は事前にWBSを出し切る要請があったりするが、ここでは後からWBSを追加可能とする。タスクの場合も同じ。あとから追加要望とか見落としとかでタスクが増える場合も考えられる。

主に受託案件を考える

適用範囲は主に受託案件を考える。社内製品開発でもよいのだが、社内の場合はもう少し制約が緩い場合が多いので、厳しいほうで試してみるのがよいだろう。受託案件の場合、顧客から予算と期間が区切られることが多く、単純なアジャイル開発による「交渉」機能がうまく働かないことが場合が多い。大幅に機能が増加されていれば予算枠を増やす交渉をすることも可能なのだが、ちょっとした機能追加とか受託側の都合による機能追加/見落としなどでの「仕事」の追加では追加予算が出ないのが普通だ。よって、

「チケット」が増えたにも関わらず、「予算」や「納期」が動かないことが多い。

むしろこちら側であるはずの「営業サイド」から値下げ要求や期間短縮、コストダウンなどを要求される場合が多いというのが受託開発でのソフトウェア開発での特徴だ。
この場合「マネージャー」もあちら側に回ることもできるのだが、今回はこちら側に回ることにする。あちら側にまわるやり方は色々出ているので試してみるといい。お勧めはしないが。

これにより、受託開発で案件を受けるときの条件として

  • 開発期間、納期が決まっている。

→ よって、無理なスケジュールを強制されたときは、最初から拒否する。

  • 予算の上限がある。

→ よって、無理な予算を強制されたときは、最初から拒否する。

  • 開発すべき機能が決まっている。

→ よって、無理な機能追加/拡張が要求されたときは、最初から拒否する。

ということにする。「無理な」というのは、あきらかに開発期間が短かったり、低予算すぎたり、夢のような機能がてんこもり、という場合のことだ。最初から失敗するプロジェクトから逃げるというのは有能なマネージャやリーダーの鉄則だ。もちろん、あちら側に回ればそういう案件を受けることも可能であるのだが、それは(以下略

さて、達成できそうな受託案件が目の前にできたとしよう。「達成できそうな受託案件」というのは、ほどよく顧客からの要望がでてきて、QCD(機能、コスト、納期)の概算ができたときに限るという訳だ。ここで問題になるところだが、これは2つの問題を内包している。

  • 受託開発案件における妥当なQCDをどうやって出すのか?
  • 妥当なQCDの開発プロジェクトを「成功」させるためには、どうしたらよいのか?

まるで、鶏と卵の問題のようだが、この2つは同時に解決する。というか同時に解決しないといけない。

計画と工程管理を一度に検証する

具体的な手順を示そう。

1.初期の条件として「無理のない」QCD を考える。ここは初期値なので、勘で適当に決めて良い。あるいは、顧客の状態を考えて、ざっくりと予算と納期(開発期間)を決めてしまうのがよい。よく営業さんが言う「ざっくりと」で良い。

2.社内で開発するチームの「チケット」の消費スピードを決める。理想は1チケット2,3時間程度なのだが、1チケットが1日でもよい。1日以上のチケットを考えるとプロジェクト実行時に誤差が多くなるので、1日以内におさめたほうがよい。

例えば、1日3チケット、期間が半年、メンバが5人と考えれば、
3x20x6x5=1800チケット
ということになる。

3.総チケットのうち、20%から30%ぐらいが「予備」のチケットとなる。予備というのは不確定要素を吸収するためのチケットで、プロジェクトを進行したときの変更要求とか機能が膨らんだときの仕事を吸収するためのチケットである。20%から30%ってのは経験上のもので、これはよく言われる「プロジェクトの予算を1.2倍から1.5倍するとよい」と同じことになる。いわゆる逆数だ。0.2/1.2 ≒ 17%、0.5/1.5 ≒ 33% となる。

残りの仕事のためのチケットを使って1で見積もったときの機能実装などを割り振る。WBS的にトップダウンで割り振ってもよいし、ボトムアップ的にタスクとして割り振ってもよい。それぞれの期間が揃っているほうがいいのだが、あまり気にしなくてもよい。重要なのは最初から割り振られているチケットの数になる。最初から割り振るチケットは「必ずやらなければいけない仕事」なので、優先度が高い。
ここでチケットが足りなくなったり、予備チケットを使い始めたらダメだ。最初の1の「ざっくり開発期間」が間違っていることになる。ざっくりの部分をざっくりと直して、2のチケット数を増やしていく。

逆に、すべての仕事チケットを埋める必要はない。概算の概算として設計工程/実装工程/試験工程で3つの山に分けてしまって、設計工程のチケットだけ書いてみるという方法でもよい。空白チケットがあってもよい。

計画段階ではこれでおしまい。ここで、おおむねチケット駆動のシミュレーションがおわってる。

  • トータルのチケット数(予備を含む)
  • 開発期間
  • 人数

が分かったので、1日(あるいは1週間)で消化するチケット数が計算できる。これが「生産量」になる。

4.プロジェクトが進行している間は順次チケットを消化する。チケット駆動のツールを使ってもよいし、ひとりプロジェクトならば Excel だけでもよい。
仕事チケットを日々消化すると同時に、予期しない仕事があれば予備チケットを使う。顧客からの要望が増えれば予備チケットを使う。営業からの要求があれば予備チケットを使う。増えたら予備チケットを使う。

仕事チケットが大幅に膨らんでしまった場合は、複数の予備チケットに分割する。当然、予備チケットではない空白チケットがあればそれを使う。考慮不足を補うのが空白チケットや予備チケットの役目だ。空白チケットはもともと予定されているけど内容が書かれなかったもの(面倒だったとか、書く時間がなかったとか)、予備チケットはいわゆる「プロジェクトバッファ」分のチケットのことになる。

5.進捗管理(工程管理)は、実際のチケット消化数と3で計算した事前のチケット消化状態と比較する。順調であれば傾き(バーンダウンチャート)は同じぐらいになる。実際の消化数が多い場合は特に問題はない。ちょっとぐらい遅れていても問題はない。総チケットの上に行かなければよい、かつバーンダウンチャートの予想直線を引いたときに総チケットのラインよりも傾きが浅くならなければよい。

予備チケットの分だけ保険が入っているので、進捗はこの間をうろうろすることになる。
バーンダウンチャートを引いたときに、赤い線より上に出そうであれば、プロジェクト進行を見直すことになる。
「生産量」を上げることはできないので(突如として「生産量」があがるという幻想は捨て去るべきだ)、

  • プロジェクトメンバを増やす
  • 仕事チケットを減らす(開発機能を減らす)
  • 仕事をする時間を増やす=休日出勤

ことになる。当然、開発予算を増やす交渉や納期を後ろに倒す交渉もし始める。休日出勤は緊急避難的なのでカンフル剤としてか使えない。一瞬しか進捗は良くならない。受託案件の場合、予算や納期が変わらないことが多いので、先の2点が対処になるだろう。

以上の5ステップで、計画と進捗管理はおしまい。

利点

このプロジェクト管理での利点は、先に書いた通りメンバのモチベーションとかやる気とかコミュニケーションとかの不確定要素が一切出てこない、それに頼らないことだ。それらはチーム開発では必要かもしれないが、必ずしも高度なものである必要はない。一般的にできれば十分だ。少なくとも、受託案件のような場合には「標準的な生産量」の測定が重要になるので、不確定要素に頼らないほうがよい。

欠点

これは利点と同時に欠点でもあるのだが、この方式にはミクロな視点が入らない。チケット同士の関連やなんらかの要因による「生産量」(チケットの消化スピード)の低下を予測できない。スケジュール管理自体がメンバ任せになってしまうので、効率の悪いチケットの消化をしてしまう可能性がある。

当然のことながら、規模が大きくなる場合は、

  • チケット同士の関連性(PERT図)
  • マイルストーン(ガントチャート)

が必要になってくる。この2つの図はおいおい活用していくとして、ガントチャートは「チケットの増加に素早く対応できない」という大きな欠点がある。チケットやタスク、WBSを増やしたときにガントチャートを修正するのが難しいため「ガントチャートが直せないから、チケットを増やさずにガントチャートのスケジュール通りに行動する」という本末転倒な心理が働いてしまう。これでは駄目だ。これはツールによる制約があるので OpenCCPM で解消していきたい点である。

おわりに

メモなのでまとめはいらないのだが、作業見積もりの20%増しとか50%増しとかで顧客に提出するとき、その保険が削られて営業サイドで勝手に値引きされてしまうことがある。お客の前で「値引き」すると案件が取りやすいからね。営業テクニックである。仕方がない。なので、見積もりを出す側(マネージャでもリーダーでも誰でも良い)では営業の値引きが明らかになっている場合は、あらかじめ「値引き」の値段だけ予算に加えておけばよい。値引き自体に根拠はないのだから。

また、予備チケットや保険の分が「顧客」に理解されないときは、計画段階や進捗管理で「二重帳簿」を作るとよい。二重帳簿は Excel などを使って自動計算すると簡単に作れる。

  • 顧客や営業にとって安心されやすい「進捗」グラフ
  • プロジェクトメンバがリスク管理できる「進捗」グラフ

を二種類用意するわけだ。超概算見積もりも同じパターンで作ることができる。その話は別の機会に。

カテゴリー: 開発, OpenCCPM | プロジェクトマネジメントの工学的アプローチのメモ書き はコメントを受け付けていません

読み取り専用の値オブジェクトがWPFのTextBoxにバインドされない(ように見える)件

最終的に、WPFのバグを踏んだのか!?と思ったけど、よく見ればきちんと例外が出ていたという話なので、今後注意するという備忘録的な記事です。

現象

こんな風な値オブジェクトを作っておいて、WPFのウィンドウにバインドします。

public class ViewModel
{
    public int ID { get; set; }
    public string Name { get; set; }
    public string NameSan { get => Name + "-san"; } 
}

NameSan プロパティは加工して表示するだけの読み取り専用のプロパティです。金額の合計値を出すとか、なにか計算結果をだすとかそういう ReadOnly な表示はよくやるパターンですね。

ViewModel _vm;
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
    _vm = new ViewModel();
    _vm.ID = 100;
    _vm.Name = "Masuda";
    this.DataContext = _vm;
}

ここで NameSan プロパティを表示するときに、TextBlock タグを使えばよいのですが、TextBox を使って ReadOnly=”True” にします。TextBox を使う理由としては、表示している文字列のコピーが Ctrl-C でできるからです。けれど、読み取りにしたいから、ReadOnly を付けておきます。

<TextBox Grid.Column=&quot;1&quot; Grid.Row=&quot;0&quot; Text=&quot;{Binding ID}&quot; />
<TextBox Grid.Column=&quot;1&quot; Grid.Row=&quot;1&quot; Text=&quot;{Binding Name}&quot; />
<TextBox Grid.Column=&quot;1&quot; Grid.Row=&quot;2&quot; Text=&quot;{Binding NameSan}&quot; IsReadOnly=&quot;True&quot; />

これをビルドして動かそうとすると実行時に例外が発生します。

System.InvalidOperationException
HResult=0x80131509
Message=TwoWay または OneWayToSource バインドは、型 ‘WpfApp4.ViewModel’ の読み取り専用プロパティ ‘NameSan’ では動作できません。
Source=PresentationFramework

どうやら、TextBox の Text プロパティは読み書き同時にできることが前提となっているので NameSan のように get しかない読み取り専用プロパティではエラーが発生するのです。IsReadOnly を付けていても駄目ですね。これは、WPFのModeがデフォルトで「TwoWay」になっており双方向になっているのが原因です。ここを明示的に「Mode=OneWay」にして表示のみにすると実行時エラーはでなくなります。

<TextBox Grid.Column=&quot;1&quot; Grid.Row=&quot;0&quot; Text=&quot;{Binding ID}&quot; />
<TextBox Grid.Column=&quot;1&quot; Grid.Row=&quot;1&quot; Text=&quot;{Binding Name}&quot; />
<TextBox Grid.Column=&quot;1&quot; Grid.Row=&quot;2&quot; Text=&quot;{Binding NameSan, Mode=OneWay}&quot; IsReadOnly=&quot;True&quot; />

ちなみに、UWPの場合はデフォルトが「OneWay」になっているので動作が違います。これがややこしい。

実験

あらかじめ、この現象を知っておけば Mode=OneWay をちまちまつけるのですが、どうやら手元でたくさんサブウィンドウを作っているときにうっかり OneWay をつけ忘れたのです。

すると、サブ画面ではこんな風に NameSan プロパティをバインドしている TextBox の初期値がでなくなります。

サブ画面を開くときは、こんな風に ViewModel を渡して(本当はコンストラクタのほうがいいけど)、ShowDialogメソッドで開きます。

private void clickOpen(object sender, RoutedEventArgs e)
{
    var sub = new SubWindow();
    sub.VM = _vm;
    sub.ShowDialog();
}

実行に落ちることはなく、単にバインドに失敗します。

これ、実行時の出力を注意深くみると、実は「例外」が発生しています。

メイン画面のときに失敗したとき同じように、バインド時に例外が発生しているのですが、ShowDialogメソッド内で例外を握りつぶしているので、初期値が表示できない(バインドできない)現象だけが出てサブウィンドウ自体は表示されるという現象です。

所感

EF で作った自動生成のオブジェクトは get/set が揃っているので、この現象は発生しません。LINQ で読み込んだリストとかをグリッドにバインドするとか TextBox にバインドしても get/set があるので助かるのだけど。
計算結果の表示とか、なんらかの文字列を加工した後に表示するときに get だけの読み取り専用プロパティを作ったときにはまります。しかも、これ TextBlock の場合はもともと読み取り専用(表示専用)なので代用部なんだけど、TextBox のような読み書き用のコントールだけに当てはまる現象だという、結構レアなケースですが、覚えておくとハマりにくい穴かもしれません。

カテゴリー: 開発, C#, WPF | 読み取り専用の値オブジェクトがWPFのTextBoxにバインドされない(ように見える)件 はコメントを受け付けていません

PINE64 と Xamarin.Forms の組み合わせを考える

この記事は、Xamarin Advent Calendar 2018 17日目の記事です。

PINE64 ってのは 3,000 円級で買えるラズパイ互換の組み込みボードです。ラズパイ互換というといささか語弊があるのですが、メモリが1GB タイプならば秋月で買えるし Raspberry Pi 3 Model B よりも高機能なところがあります。

image

Android 7.1 を入れる

最初に PINE64 を買ってみたのは Windows IoT Core を動作させたいためだったのですが、最初の頃は LAN などが上手く動かなくて諦め状態だったのです。今のバージョンは試していないので、それはまた今度。

でも、Android がほどよく動きます。Raspberry Pi 3B に Android を入れるとちょっと重たくて使い物にならないのですが、PINE64 に入れるとまずまずのスピードで動くようになります。さすがにアクションゲームは難しいのですが、カードゲームっぽい RPG ものならば大丈夫だし、この記事のように Xamarin.Forms のようなテキストであれば十分なスピードがでます。このあたり GPU が乗っているのが大きいとは思うのですが、詳しいことは不明。

それでも、普通の Android スマホとはちょっと変わった形で Android が使えるのがミソです。

デメリットの解説を

先に PINE64 + Android の組み合わせのデメリットの話をしておきましょう。

  • ラズパイより情報が少ないので構築に苦労する
    → ふつうに Ubuntu で使うにしても苦労するし、まあ差額考えてもラズパイのほうが楽
  • タッチパネルが使えない
    → そもそもモニタがないので、別途液晶モニタがないと何もできないです。
    → USB にマウスを付けないと使えないです
  • バッテリーがないので、電源を引っこ抜くと消える
    → 電源を USB 経由で供給しないと駄目なので、引っこ抜くと Android ともども落ちます。モバイルバッテリーだといけるかもしれません。
    → Android を動かすのに、5V2A が必要です。これはパソコンからの USB 給電では動きません。

あと、Android から GPIO を操作するのは root が必要で微妙に面倒です。面倒というかまだ試していないので、試さないと。すでに買ってから1年以上経っていますが。

ちょっとだけメリットを

実運用するにはデメリットが多いのですが、ちょっとだけメリットがあります。

  • 液晶モニタを HDMI 接続できる
  • 超安価な Android 機として扱える。
    → 中古でも1万円するので、2GB タイプでも 5,000円弱です。
    → Android 7.1 が用意されています。SDK からビルドすれば 8.1 も動きそうな気がするのですが試していません。
  • Android を microSD として入れられる。
    → 初期データを入れたり、データをコピーしたりできます。
    → 最初から root です。
  • Google Play が使えます。
    → PINE64 + Android では Google Play が使えます。

そんなこんなで試しに動かしてみると面白いかもしれません。Android SDK から自前ビルドをして動作させることができるので、それなりにハックはできるんですが、SDK のビルド環境を整えるのが結構大変で。もともと構築済みの人だといいんだと思うのだけど、初手からやるのはなかなか辛いかもしれません。それだけのメリットが得られるのか?というとかなり疑問で、だったらふつうにラズパイ+Raspbian で動かしたほうが無難かも、とおもったりもしますが。

それでも、実験的に PINE64 + Android + Xamrin.Forms というのをたまに試してみたりします。

環境構築

環境構築をざっと書いておきます。

  • PINE A64-LTS ボードを購入。左上の Store から直販で $32 で買えます
  • microSD のイメージは SOPINE Software Release – PINE64 の Android 7.1 Community Build Image をダウンロードする。
  • PINE64 + Android 7.1 を起動したら WiFi ADB を入れて起動。Android 側の adbd が起動して、PC から adb connect できるようになります。

adb から PINE64+Android で接続できるようになったら、準備完了です。

image

ちなみに、半年前に動かしたアズレンがこれです。今やキャッシュが壊れているのか画像がうまくでないんですが。再構築したら出るかも。

image

Xamarin.Forms を動かしてみよう

Android が動くのだから、Xamarin.Android も動くだろうし、Xamarin.Forms も動くだろうとう話です。Xamarin.Forms のメリットは、Android, iOS の両方のスマホを一気に作れるというものが大きいのと、もうひとつ C# でさっくりとした画面をさっくりと作れるというメリットがあります。私が Android IDE に慣れていないからというせいもあるかもしれませんが、スマホのような小さな画面ではなくて液晶モニタのように大きな画面でレイアウトを作る場合は、Xamarin.Forms で使われている Grid が効率的です。

なので、大き目のタブレット画面 + Android という組み合わせで業務アプリを作る場合に、Xamarin.Forms の Grid と MVVM がベストだろう、と考えているわけですが…世の中、大き目の Android タブレットという動きにはならなかったんですよね。今後はどうなんでしょう?

それはさておき、Xamarin.Forms のサンプルを動かしたのがこれです。

image

image

これ自体は、Visual Studio で Xamarin.Forms のテンプレートを使っているだけなので、説明は省略。

image

image

これだけだといまいちなので、github から xamarin.forms のサンプルを動かしてみましょう。

xamarin-forms-samples/CatClock
https://github.com/xamarin/xamarin-forms-samples/tree/master/CatClock

image

ほかのサンプルも

image

image

image

元が Android なので、UI はそのまま XAML を使えます。スマホの場合には画面が小さいので全画面で作ることになるのですが、23インチ液晶位になるともう少しレイアウトを考える必要がありますよね。そのあたりにうまくハマるのではにかな、と考えています。

カテゴリー: Android, Xamarin | PINE64 と Xamarin.Forms の組み合わせを考える はコメントを受け付けていません

M5Stack から Slack に Yo! する

この記事は、M5Stack Advent Calendar 2018 – Qiita  の 10日目の記事です。
以前つくってみた M5Stack から Slack へ投稿する記事の焼き直しなのですが、もう少し詳しく書きます。というか、以前作ったやつをコピペしたら動かないということで、ちょっと書き直し。

仕組み

Slack へ投稿する方法として、Web APIを使って直接投稿することもできるのですが、なかなか面倒ので WebHook を使います。HTTP で手軽にアクセスできる API を用意しておいてアクセスする方法ですね。WebHook 内で複雑なアクセスを肩代わりしてくれるので、組み込み側に専用のライブラリを用意する必要がなくなります。

というわけで、

  • SlackのWebHookを作る
  • 作った WebHook に HTTPS で送る

2手順で実現します。

SlackのWebHookを作る

ブラウザで slack にログインしている状態で左上の「Manage」をクリックします。

検索ボックスで「WebHook」を入力して「Incoming WebHooks」を選択。

「Add Configuration」をクリックして WebHook を作ります。

投稿先のチャンネルを指定して、WebHook の URL を作成します。

投稿するデータは JSON 形式で送信して、宛先の URL が https://hooks.slack.com/services/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 な感じになります(このURL自体はすでに無効にしてあるので大丈夫)。

投稿するのがテキストだけであれば、次のように text だけに設定

{"text": "This is a line of text in a channel.\nAnd this is another line of text."}

投稿時のアイコンやユーザー名も指定できます。実はチャネル名も指定できるので、WebHook 自体はチャンネルごとに作らなくてもひとつだけ作っておけばOKです。

{
 "text":"From M5Stack Yo!",
 "icon_emoji":":ghost:",
 "username":"m5stackpost"
}

これで WebHook の準備は完了

証明書をハードコードする

色々調べると M5Stackから HTTP 接続する場合に HTTPClient を使うのですが、Slack の WebHook は HTTPS 接続になっています。セキュリティ接続になる場合、公開鍵の証明書が必要になってきます。

実は例のうんこボタンやエリクソンの証明書の期限切れの問題もここに関わる訳ですが、証明書の使い方が「暗号化」と「ライセンス期限」との2つを混在させてしまっている問題があります。この部分、暗号化だけ使うのであれば有効期限は無視してよいし、ライセンス期限の制限だけなれば証明書の有効期限だけをチェックすればよいわけです。

さて、今回は Slack のサーバーとの暗号化通信となるので、サーバー側の証明書を M5Stack 側に埋め込んで暗号化できるようにします。

Google Chrome で slack のサイトを開いた状態でサーバーの証明書を表示させます。

「証明書のパス」のタブを開いてルート証明書か中間証明書を選択して「証明書の表示」をさせます。どちらの証明書を使っても構いません。

証明書をテキストファイルに保存します。

C++ のコードに埋め込んでしまうので、BASE64 形式で保存します。

中身をメモ帳で開くと、こんな感じになっています。

この証明書を M5Slack のコードに埋め込んで、HTTPS 接続するときに使います。

M5Stackのコード

Slack に Yo するコードはこんな感じです。
WiFi に接続するための SSID とパスワードは適宜変えてください。
Slack に送るための WebHook のところ「/services/XXXXX/YYYYY」も書き換えます。

#include <Arduino.h>
#include <WiFi.h>
#include <WiFiMulti.h>
#include <HTTPClient.h>
#include <M5Stack.h>

WiFiMulti wifi;
HTTPClient http;

#define WIFI_SSID "<WiFiのSSID>"
#define WIFI_PASS "<WiFiのパスワード>"
const char *server = "hooks.slack.com";
const char *json = "{\"text\":\"From M5Stack Yo!\",\"icon_emoji\":\":ghost:\",\"username\":\"m5stackpost\"}";
const char *json2 = "{\"text\":\"From M5Stack Hello!!!\",\"icon_emoji\":\":ghost:\",\"username\":\"m5stackpost\"}";

// ルート証明書
const char* slack_root_ca= \
"-----BEGIN CERTIFICATE-----\n" \
"MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh\n" \
"MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n" \
"d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\n" \
"QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT\n" \
"MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\n" \
"b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG\n" \
"9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB\n" \
"CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97\n" \
"nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt\n" \
"43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P\n" \
"T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4\n" \
"gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO\n" \
"BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR\n" \
"TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw\n" \
"DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr\n" \
"hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg\n" \
"06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF\n" \
"PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls\n" \
"YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk\n" \
"CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=\n" \
"-----END CERTIFICATE-----\n" ;
  
// 中間証明書
const char * slack_sub_ca = 
"-----BEGIN CERTIFICATE-----\n" 
"MIIElDCCA3ygAwIBAgIQAf2j627KdciIQ4tyS8+8kTANBgkqhkiG9w0BAQsFADBh\n" 
"MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n" 
"d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\n" 
"QTAeFw0xMzAzMDgxMjAwMDBaFw0yMzAzMDgxMjAwMDBaME0xCzAJBgNVBAYTAlVT\n" 
"MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJzAlBgNVBAMTHkRpZ2lDZXJ0IFNIQTIg\n" 
"U2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\n" 
"ANyuWJBNwcQwFZA1W248ghX1LFy949v/cUP6ZCWA1O4Yok3wZtAKc24RmDYXZK83\n" 
"nf36QYSvx6+M/hpzTc8zl5CilodTgyu5pnVILR1WN3vaMTIa16yrBvSqXUu3R0bd\n" 
"KpPDkC55gIDvEwRqFDu1m5K+wgdlTvza/P96rtxcflUxDOg5B6TXvi/TC2rSsd9f\n" 
"/ld0Uzs1gN2ujkSYs58O09rg1/RrKatEp0tYhG2SS4HD2nOLEpdIkARFdRrdNzGX\n" 
"kujNVA075ME/OV4uuPNcfhCOhkEAjUVmR7ChZc6gqikJTvOX6+guqw9ypzAO+sf0\n" 
"/RR3w6RbKFfCs/mC/bdFWJsCAwEAAaOCAVowggFWMBIGA1UdEwEB/wQIMAYBAf8C\n" 
"AQAwDgYDVR0PAQH/BAQDAgGGMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYY\n" 
"aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6\n" 
"Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RDQS5jcmwwN6A1\n" 
"oDOGMWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RD\n" 
"QS5jcmwwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v\n" 
"d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwHQYDVR0OBBYEFA+AYRyCMWHVLyjnjUY4tCzh\n" 
"xtniMB8GA1UdIwQYMBaAFAPeUDVW0Uy7ZvCj4hsbw5eyPdFVMA0GCSqGSIb3DQEB\n" 
"CwUAA4IBAQAjPt9L0jFCpbZ+QlwaRMxp0Wi0XUvgBCFsS+JtzLHgl4+mUwnNqipl\n" 
"5TlPHoOlblyYoiQm5vuh7ZPHLgLGTUq/sELfeNqzqPlt/yGFUzZgTHbO7Djc1lGA\n" 
"8MXW5dRNJ2Srm8c+cftIl7gzbckTB+6WohsYFfZcTEDts8Ls/3HB40f/1LkAtDdC\n" 
"2iDJ6m6K7hQGrn2iWZiIqBtvLfTyyRRfJs8sjX7tN8Cp1Tm5gr8ZDOo0rwAhaPit\n" 
"c+LJMto4JQtV05od8GiG7S5BNO98pVAdvzr508EIDObtHopYJeS4d60tbvVS3bR0\n" 
"j6tJLp07kzQoH3jOlOrHvdPJbRzeXDLz\n" 
"-----END CERTIFICATE-----\n" ;

void init() {
  M5.begin(true, false, true);
  M5.Lcd.clear(BLACK);
  M5.Lcd.setTextColor(YELLOW);
  M5.Lcd.setTextSize(2);
  M5.Lcd.setCursor(65, 10);
  M5.Lcd.println("Slack example");
  M5.Lcd.setCursor(3, 35);
  M5.Lcd.println("Press A button");
}

void setup() {
  init();
}

void sendYo() 
{
  // put your setup code here, to run once:
  wifi.addAP(WIFI_SSID, WIFI_PASS);
  M5.begin();
  M5.Lcd.println("send slack");
  
  while (wifi.run() != WL_CONNECTED) {
    delay(500);
    M5.Lcd.printf(".");
  }
  M5.Lcd.println("wifi connect ok");
  // post slack
  // https://hooks.slack.com/services/XXXXX/YYYYY
  M5.Lcd.println("connect slack.com");
  http.begin( server, 443, "/services/XXXXX/YYYYY", slack_sub_ca );
  http.addHeader("Content-Type", "application/json" );
  http.POST((uint8_t*)json, strlen(json));
  M5.Lcd.println("post hooks.slack.com");
}

void sendYa() 
{
  // put your setup code here, to run once:
  wifi.addAP(WIFI_SSID, WIFI_PASS);
  M5.begin();
  M5.Lcd.println("send slack");
  
  while (wifi.run() != WL_CONNECTED) {
    delay(500);
    M5.Lcd.printf(".");
  }
  M5.Lcd.println("wifi connect ok");
  // post slack
  // https://hooks.slack.com/services/XXXXX/YYYYY
  M5.Lcd.println("connect slack.com");
  http.begin( server, 443, "/services/XXXXX/YYYYY", slack_sub_ca );
  http.addHeader("Content-Type", "application/json" );
  http.POST((uint8_t*)json2, strlen(json2));
  M5.Lcd.println("post hooks.slack.com");
}

void loop() {
  M5.update();
  if (M5.BtnA.wasReleased()) {
    sendYo();
  } else if (M5.BtnB.wasReleased()) {
    sendYa();
  } else if (M5.BtnC.wasReleased()) {
    init();
  }
}

Aボタンで「Yo」を送って、Bボタンで「Yha!」を送ります。
Cボタンで画面クリアですね。

いちいち WiFi にログインしているのが悪いのか(たぶん接続を切っていないから、WiFiMulti がクローズされていないのだと思う)、ボタンを2回押さないと Yo が送れません。そのあたりは今後改良ということで。

実行結果

M5Stack の表示

Slack クライアントの表示

カテゴリー: 開発 | M5Stack から Slack に Yo! する はコメントを受け付けていません

SQL Server を SUM/AVG でオーバーフローさせてみよう

ちょっと誤差関係で気になったことを試してみる。

統計学では平均は分散を使うことが多いのだけど、標本の数が多い場合にデータベースの sum や avg は有効に働くのだろうか?という疑問がでてくる。単純に言えば、SQL Server などのデータベースで扱える数値はどのくらい大きいあるいは精度が出るのだろうか?

以下は、SQL Server 2016 で試した結果

計算結果は38桁まで有効

単純に有効数値を確認するためにはオーバーフローさせればよい。例の0.01をどこまで掛けられるかを「0」を打ちながら試してみる。

こんな感じで、入力するときに38桁でエラーになる。

同じように、計算結果が38桁を超えるようにすると、やっぱりエラーになる。

どうやら、内部での計算は numeric 型の38桁の計算(これはint型よりもずっと大きい)でなされているようだということが分かる。

numeric( 8,3 ) で制限して10万回足したらどうなる?

numeric ってのは、有効桁数と小数点以下の桁数で指定するので、numeric( 8,3 ) は、有効桁数が8桁で小数点以下が3桁になる。99999.999 までが有効になる。

これで、1.999 のようなランダムな数を作って 10 万回足したらどうなるだろうか?と思ってやってみると、以下のようにふつうに sum の結果がでる。合計値は 535469.446 となるので、 numeric( 8,3 ) の範囲を超えているが、特に問題ないらしい。カラムの精度と計算結果の精度とは違うことがわかる。

image

たぶん、sum の計算結果は numeric の 38桁に拡張されているはずだ。

フルで 38 桁の精度を持たせた場合はどうなるのか?

では、カラムの設定を numeric( 38, 28 ) のようにして、有効桁数38桁、小数点以下28桁が有効にするとどうなるだろうか?

データとしては、1999999999.0000000000000000000000000001 を 20個ほど入れておいて、加算するとオーバーフローするように仕掛けておく。

insert into TT ( v, vv ) values ( 1, 1999999999.0000000000000000000000000001 );

こんな風にデータを入れておく。

image

そして sum する。

image

結果は予想通り、numeric の 38桁の精度を超えてしまうので計算できない。

じゃあ平均(AVG)の計算は?

平均を計算してみよう。同じ値しか入れていないので、平均は 1999999999.0000000000000000000000000001 で明白なんだけど、SQL Server の AVG 関数を使うと、以下のようにオーバーフローになる。

image

これ、前回の記事にも書いたが平均を計算する対象の最大値/最小値を適当に取ってきて、類推できる平均な値を使えば、オーバーフローにならずに計算することができる(当然、幅によってできないこともある)。だから、SQL Server の AVG 関数は、(おそらく)内部で SUM を使っていて個数で割ることをしているのだろう。だから、計算途中でオーバーフローしてしまうのだ。

「ドメイン知識」大切ですね、にもつながる。

カテゴリー: 雑談 | SQL Server を SUM/AVG でオーバーフローさせてみよう はコメントを受け付けていません

0.01を10000回足したら100.00にならない話

元ネタの引用リツイートが3件ほど立て続けてに回ってきたのと、「俺たちはテストコードにいつまで ε を書かないといけないのだろうか?」というツイートがあったので、それを絡めて。

最初に TDD の話だけ書いておこう(一連のツイートには書かなかったので)。

xUnit のテストコードに誤差範囲を指定する

自動単体テストでは、Assert.AreEqual( 期待値, 実測値 ) な形で、数値や文字列を比較するけど、これ実数(double)の場合は、Assert.AreEqual( 期待値, 実測値, 誤差 ) という形で誤差の許容範囲を指定しないといけない。これ、文字列や整数値に場合にはピッタリ一致するのだけど、実数の場合はそうはいかない。科学計算のユニットテストを書くとわかるが、計算順序や計算の精度などを上げると、微妙に値がずれる。そのたびにいちいち期待値を変えていられない、というか期待値を変えてしまってはテストコードにならないので、許容範囲を指定する。大抵の場合は有効桁数を書くことになるので、

Assert.AreEqual( 100.00, 実測値, 0.01)

な形で有効桁数を示すことが多い。この場合は、99.99001(99.99より大きい) から 100.00999…(100.01より小さい) を 100.00 と扱うことができる。不等号を使えば、

99.99 < 実測値 < 100.01

の範囲となる。両端を含まない「開区間」になるんだが、まあ細かいことはどうでもいい(本当は重要だけど)。こんな感じで真の値から幅のある値を取る。

コンピュータで扱う実数とは何か?

知識として整数値(int型)と実数値(doubleやfloat)を分けてコーディングをしないといけない、というのは知っている人が多いと思うのだけど、じゃあ double や float を使う場合、コンピュータ上でどう扱っているのか?というのを知っている人は…理/工学系には「教養」だよ!という話だったりする。

倍精度浮動小数点数 – Wikipedia https://ja.wikipedia.org/wiki/%E5%80%8D%E7%B2%BE%E5%BA%A6%E6%B5%AE%E5%8B%95%E5%B0%8F%E6%95%B0%E7%82%B9%E6%95%B0

IEEE 754 Double Floating Point Format.svg

倍精度の「倍」の意味は、単精度の倍だから。なので、単精度が32ビット、倍精度が64ビットになる。8の倍数になっているのはコンピュータで扱いやすいからで、ここに当然ながら誤差が存在する。ざっと覚えておくとよいのは倍精度で8桁位まで有効に働く。10進数の有効数字としては、12桁位まであるんだけど、科学計算したりすると桁落ちが発生するので、経験上8桁までは有効になる。

元ネタが30年前の本を参照しているので、1990年代ぐらい。ちょうど情報科学学科ができ始めた頃で、C 言語が徐々に広まっていたころ。科学計算だと Fortran が主流でまだ一般的なプログラムとしては BASIC が主流だった頃の話だ。90年代としてひとくくりにしてしまうとかなり面倒なんだけど(インターネット接続も含まれるから)、Excel VBA が広まり始めた時期でもある。

メモリとバス幅の制約

ちょうど1990年代は、コンピュータ言語がいろいろできた時期でもあるので、このあたりの背景を説明すると、

  • 浮動小数点付きの計算は「高価」で、専用のコプロセッサで動かしていた
  • 高価だったから、普通の CPU はライブラリで浮動小数点計算をしていた。ので遅かった。
  • まだ、32ビットマシンが出たばかりで、レジスタやバス幅は 32 ビットだった
  • バス幅の関係上、単精度(32ビット)は早かったが、倍精度(64ビット)は遅かった。
  • ストレージ(フロッピー、HDD)が小さかったので、64ビットを保存するのは勿体なかった

という関係上、実数の計算は主に単精度(float)が使われていた。科学計算を専門に扱う Fortranはデフォルトが倍精度…と思っていたけど real(単精度)ですね。Fortran 入門: 定数と変数 http://www.nag-j.co.jp/fortran/FI_4.html を参照。自分の場合、炉心計算で倍精度にしていたらしい。

BASIC は 90年代よりも前からあるので、実数の場合「単精度」が使われている。なので、表現上は8桁位でるのだが、計算を繰り返しているうちに有効桁数は4桁位になってしまう。

これが1万回くりかえすと…の話になる。

ちなみに、Excel 2013 の VBA を使って計算をすると以下の計算は、100.000000000014 になる。デフォルトで倍精度(double)になっている。

image

実運用上問題はないけど、これを 100.00 と比較すると「偽」になる。当然だ。実数の比較するときには、先の xUnit の例で述べた通り有効範囲を指定しないとダメだからだ。

加算の繰り返しで誤差が累積する

先の計算で、手っ取り早く誤差をうまないためには「実数を整数の範囲に直す」という計算方法が使われる。科学計算の場合は有効桁数で済ませられるのだが、金融関係ではそれではダメだったりする。お金の問題は最後の1円まで有効になる。

お金の計算はいくつか特殊で、

  • 消費税で円以下は切り捨て
  • 為替計算では銭単位まで計算(だったとはず)
  • 利息では銭まで計算
  • 利子は円で切り捨て
  • 四捨五入ではなく銀行まるめを使う

という特有なルールがある。なので、0.01ドル(1セント)のような小数点以下2桁までは重要だったりする。これが1万回加算されたとき(積み立てたとき?)に100.00ドルぴったりにならないと困るわけだ。

最近のシステムでは、decimal や money などのお金を扱うための型が用意されているが(中身も10進数で計算する)、90年代にはそういうものがなかったので、COBOL のように10進数計算ができるプログラム言語を使うか、自前で作るしかなかった。

ただし、0.01 単位というのが分かっていれば、手っ取り早く 100 倍して整数に直し、結果を100で割ればよい。

image

な感じに書き換えると、答えは「100.00」ぴったりになる。

浮動小数点をコンピュータで扱うと有効桁以下のところの誤差が当然でてくる。これを100回加算すれば2桁誤差が増えるし、1万回加算すれば4桁誤差が上がってくる。なので、実数をあつかうときに注意しないといけないのは、

  • 有効桁数
  • 小数点以下の固定の桁数

とは違うということだ。ソフトウェア工学の教科書の最初に出てくる問題なので「教養」の範疇だと思うし、統計をコンピュータ処理するときの常識=「知っておくべき知識」だと思うのだが、どうだろうか?

有効桁数はよく聞かれるので、有効桁数4桁といえば「1234000」な形や「1.234」な形で示される数字の部分だ。小数点以下でゼロをつけるて明示的に「1.000」として有効桁数を表す場合も多い。

これと混同しやすいのは、小数点以下の固定の桁数で「100.234」みたいな形のもの。有効桁数は6桁と考ええても良いけど、この場合は小数点以下の桁数のほうが重要で、固定小数点で小数点以下3桁有効、という言い方をする。浮動小数点は指数表示で表してもいいけど(有効桁数のほうが重要なので)、固定小数点の場合は小数点以下の桁数が重要になるので指数表現するとまずい場合が多い。

100.234 の表現の場合、100 の回りに値が散らばっていて 101.000 とか、98.123 とかの値があるということだ。この場合、測定値から 100.00 を引いてやれば、桁あふれの危険がすくなくなる。0.234 とか 1.000 とか –2.123 とかにならうからだ。これだと有効桁数もわかりやすい。

統計を計算するときに、平均とか分散を計算するのだが、単純に測定値を加算していくつ桁あふれをしてしまう。1万位なら大したことがないとおもうだろけど、100万件のデータの平均を出すときに単純に加算してしまうと桁あふれ(オーバーフロー)を起こしてしまうのは明白だ。だから、先のように 100 の周辺に値が集まっている(身長などはそれ)場合には、だいたい平均っぽい値を先に引いてしまう。これを「ドメイン知識」という(というのを心理統計法で最近知った)。いわゆる、統計をとったときの特性(ドメイン知識)がないと単純に加算してオーバフローを起こすか誤差を累積させてしまうが、適度な知識があれば、誤差を抑えて精度のよい計算ができる。

物理的な有効桁数は意外と少ない

キログラム原器から プランク定数 をもとに計算するように変わったわけだが、プランク定数 6.626 070 040(81)×10-34 J s の桁数がそのまま計測値に適用できるわけではない。というか、たくさんの桁数があっても意味はない(測定誤差があるので)。

なので、測定誤差も含めて材料力学では2,3桁の有効数字で十分だし(安全係数が、1.2 のように実質小数点以下1桁ぐらいなのから推測できる)、天文学に至っては log で計算してしまうので1桁あれば十分だったりする。そもそも、物理の世界では、値が倍ぐらい違ってもびくともしないので、有効桁数は0.5桁という、有効桁数が小数点以下という感覚もある。電子工学でも抵抗の値は10%位の誤差を許容する(誤差自体が書いてある)し、温度や湿度、電流によって特性グラフがデータシートとしてあるので、ぴったりとした値が決められる訳ではない。むしろ、誤差なり特性なりを含む許容範囲が必要となる。

こんな感じに、値が分布していて許容できる上限下限を決める。部品や測定値は真ん中に集中する(正規分布など)グラフになる。

image

なので、計算誤差が累積したり測定誤差があったりするのを前提として、この範囲内で「正しい計算」を使用とするのが科学計算である。だから、実数を扱う場合と整数を扱う場合はちょっと違うことを意識しないといけない。

乗除算でも誤差を減らすためのテクニックがあって、

  • A / B * C
  • A * C / B

この2つの答えは同じように見えるが、後者の「A * C / B」のほうが精度がよく計算できる。前者のように割り算を先にやってしまうと、割り切れない場合があるので誤差がでる。それを次の C を掛けることで増幅させているのだ。だから、数値のオーバーフローに注意してという前提はあるが、A * C で誤算なく乗算をした後に、B で割る(割ったとき割り切れなくてよい)という式変形をする。

数式上(無限に有効桁数があると考えらる)では同じだが、コンピュータ上の有限な有効桁数があると制限がでてくるという話である。

p値(有意確率)と誤差伝播

一気に統計値の話になって、AI な話に振っていきたいのだが、この誤差の部分と分布をうまく扱っていかないと、AI で言われるところの「検定」がうまく働かない。学会や論文的には p 値が重要になってくるのだが、測定誤差とか標本分布の確率の話がないのに、一気に p 値にはいってしまうのはどうかと思う…という話を書きたいところなのだけど、まだそこまで勉強が至っていないので今回は割愛。誤差伝播に関しては、隠れマルコフモデル(HMM)で画像解析をやろうとした時期があって、ベイズは少し慣れているんだが。

プロジェクト管理手法の優位性検定とか不具合検出の標本分布とかは計算しておきたいところ。それはまた後日。

カテゴリー: 雑談 | 0.01を10000回足したら100.00にならない話 はコメントを受け付けていません

小学生の自由研究に最適…かもしれない中央卸売市場の卸売数量をMySQLで扱う

築地市場から豊洲市場への移転に関して、諸々を調べていた時にちょうどよさそうな大量データをみつけたので、紹介がてら。

中央卸売市場の卸売数量は公開されている

いわゆる築地とか豊洲とかの卸売市場は都が経営をしていて、もろもろの情報は以下で公開されています。

東京都中央卸売市場日報
http://www.shijou-nippo.metro.tokyo.jp/

2004年から記録があるので10年以上のデータを取得できます。が、あまり多くても仕方がないのでひとまず5年間分(2013年1月から2018年11月)までの青果と水産のデータをとってきました。

ページを探っていくと、こんな形でテーブルで表示されています。これをCSV形式でダウンロードができます。

CSV形式とはいえ、こんな風にテーブルからとってきたような形のCSV形式なので(一応カンマ区切りにはなっている)ちょっと扱いにくいのです。

公開されているデータ整形する

このままではデータとして扱いづらいので、データベースに挿入できるように本当のCSV形式に直してしまいます。

open System
open System.IO

// 1行目: 販売結果(青果・全市場) → 棟
// 2行目: 平成30年08月01日(水曜日) → 日付
// &quot;(単位:キロ)&quot; で → 分類
// &quot;品名...&quot; で → 市場
// 以降、1行ごとに 品物, 販売方法, 卸売数量(市場ごと)
// ただし、小計、全分類合計は取り込まない

// 出力フォーマット
// 日付, 棟, 分類, 市場, 品名, 販売方法, 卸売数量

let rec readtable (sr:System.IO.StreamReader) 棟 (日付:DateTime) 分類 (市場:string[]) 品名_ =
    let 数量 = sr.ReadLine().Split(&quot;,&quot;)
    let 品名 = if 数量.[0] <> &quot;&quot; then 数量.[0] else 品名_
    if 数量.[0] <> &quot;小計&quot; then
        for i in [4..市場.Length-1] do
            let 市場名 = 市場.[i]
            let 卸売数量 = if 数量.[i] = &quot;−&quot; then &quot;0&quot; else 数量.[i]
            let 販売方法 = 数量.[2]
            printfn &quot;%s&quot; (
                String.Format(&quot;{0},{1},{2},{3},{4},{5},{6}&quot;, 
                    日付.ToString(&quot;yyyy/MM/dd&quot;), 
                    棟,
                    分類,
                    市場名,
                    品名,
                    販売方法,
                    卸売数量  ))
        readtable sr 棟 日付 分類 市場 品名
    ()

let readblock (sr:System.IO.StreamReader) 棟 日付 =
    let 分類 = sr.ReadLine().Replace(&quot;(単位:キロ)&quot;,&quot;&quot;)
    let 市場 = sr.ReadLine().Split(&quot;,&quot;)
    if 分類 <> &quot;全分類合計&quot; then
        readtable sr 棟 日付 分類 市場 &quot;&quot;
    ()

let readcsv (sr:System.IO.StreamReader) =

    let 棟 = sr.ReadLine().Replace(&quot;販売結果(&quot;,&quot;&quot;).Replace(&quot;・全市場)&quot;,&quot;&quot;)
    let ci = new System.Globalization.CultureInfo(&quot;ja-JP&quot;)
    let 日付 = DateTime.Parse( 
                sr.ReadLine().Substring(0,11), 
                new System.Globalization.CultureInfo(&quot;ja-JP&quot;), 
                System.Globalization.DateTimeStyles.AssumeLocal)
    sr.ReadLine() |> ignore // 空行
    while sr.EndOfStream = false do
        readblock sr 棟 日付
        // 空行まで読み捨て
        while sr.EndOfStream = false && sr.ReadLine() <> &quot;&quot; do
            () 
    ()

[<EntryPoint>]
let main argv =
    if argv.Length = 0 then
        printfn &quot;ex. tukizi sui_20181105.csv&quot;
    else
        // printfn &quot;%s&quot; argv.[0]
        for fname in argv do
            let sr = new System.IO.StreamReader( fname )
            readcsv sr
            sr.Close()
    0

コードがF#になっているのはいつものことですね。この手の整形をやるのはC#よりもF#で書いたほうがやりやすかったりします。一番内側の readtable 関数内で再帰を使って品名の部分を読み込んでいます。もっと頑張れば for ループを無くせるはずなのでしょうが、ひとまずこれ動いたのでokということで。

何ができるのか?

データ量はざっと200万件近くあります。200万件あると検索が重たくなって大変なのでは?と思っていたのですが、MySQL に突っ込めば全然平気ですね。ひとつのテーブルしか扱わないというのもあるのですが(面倒なので敢えて正規化していません)、一千万件ぐらいあると検索に時間が掛かるのかもしれませんが、200万件位だったら sum を使って集計しても全然あっという間に終わります。

実は200万件の実データを集めるのは結構大変なのです。調査するにしてもデータベースの勉強をするにしても、これだけのデータを扱えるのは結構貴重ではないかな?と思うのです。

5年間分のデータは https://1drv.ms/u/s!AmXmBbuizQkXgpUYeqUVYd7WhAl4uw からダウンロードしてください。MySQLに入れてからエクスポートしたデータと、先のツールで成形したCSV形式のデータが入っています。

テーブル構造以下の通りです。面倒だったので列名は日本語のままw

CREATE TABLE `tt` (
  `日付` datetime NOT NULL,
  `棟` varchar(45) NOT NULL,
  `分類` varchar(45) NOT NULL,
  `市場` varchar(45) NOT NULL,
  `品名` varchar(45) NOT NULL,
  `販売方法` varchar(45) NOT NULL,
  `卸売数量` double NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

去年のさんまの卸売数量を調べてみよう

例えば、去年1年間の「さんま」の取扱量をみてみましょう。

select 日付, sum(卸売数量) from TT 
where 品名 = 'さんま'
  and 日付 between '2017/01/01' and '2017/12/31'
group by 日付
order by 日付
;

こんなクエリを書いて日付でソートすれば、日単位の卸売数量がわかります。

結果をExcelに貼り付けてグラフを追加すれば、ほら自由研究のできあがり。

ちなみにフォントは「全児童フォント(フェルトペン)」 http://tanukifont.com/zenjido/ を使っています。お試しあれ。

カテゴリー: 開発 | 小学生の自由研究に最適…かもしれない中央卸売市場の卸売数量をMySQLで扱う はコメントを受け付けていません

プロジェクトマネジメントに科学的根拠を付加する仮説

PMBOK に科学的根拠がない、というのは「知識体系」として網羅はしているけれど、そこに数学的な根拠とか確率の話がでてこないからだ。何をもってして「プロジェクトの成功」を決めるのかは実はそれぞれなんだけど、プロジェクトの最初に QCD(機能、コスト、納期)を決めておいて、

  • プロジェクト終了時に要件や機能を満たしている
  • 予算/コストをオーバーしていない。
  • 納期が守られている

ということをプロジェクトの「成功」と仮定してよいだろう。実際のプロジェクトは時間とともに変化することが多いから、プロジェクトの最初に決めた「要件」がプロジェクト終了時に満たされていたとしても、時間の差からニーズにあっているかどうかは分からない。これは建築業界でもあることで、計画時に綿密に設計をしたとしても建築物自体ができあがるのはタイムラグがあるから出来上がったときには時代のニーズから外れている(あるいは流行おくれになる)というパターンも多い。公共物を建てるときはどうしてもその矛盾に立ち向かわないといけないし、ニーズ自体が進行中に代わっていくことに追随するためのアジャイル開発手法もあれば計画に沿うかたちで進める計画駆動もあれば(特に建築物は途中で変更が効かないことが多い)それは業界や最終的な利用者(ステークホルダー)に合わせて変化するしかない。

プロジェクト完成時に顧客のニーズを完全に解決することはできない

極端な話、プロジェクトというのは時間の概念から離れることはできないので、計画時の利用者ニーズと完成時の利用者ニーズは変わる可能性がある。車のように購入してすぐに使う、既製服のように吊るしを買う、という場合には買ってから使うまでのタイムラグが短いから差はあまりでないのだけれど(それでも、使ってみたら「思ってたのと違った」パターンがあるのだけど)、プロジェクトの場合は一回性のものでもあるから、ぴったりとはいかない。逆にいえば、「完全に解決する」ことは不可能なので、「おおむね解決できる」という矛盾に立ち向かわないといけない。

プロジェクト開始時の要件や機能(プロジェクト途中の変更も含めて)の規定は、顧客との契約と同時に、作る側としては開発規模を確認/確保するために必須な作業であったりする。が、契約という行為がプロジェクト開始前にある以上ずれが必ずでる。

  • 顧客視点からすれば、プロジェクト開始前に契約して予算が分かることが望ましい
  • 開発視点でいえば、プロジェクト開始時(要件、機能の定義前)に規模はわからない

という話もあり、先のプロジェクトの QCD を決めようとしても矛盾だらけになる。それでも何とかするのがプロジェクトマネジメントの役目だったりする。逆にえば、この「なんとかする知識体系」が PMBOK の根底にはある。

極端な話をすれば、プロジェクトの予算や納期は、プロジェクト終了時点で明確になる。

image

イメージを描くとこんな感じになる。プロジェクト開始時には予算/コストは、上下大幅なずれがあり分布している、当然のことながらプロジェクト終了時には、コストは確定しており納期も確定している。

この上下のブレは、もしプロジェクトが複数あったらという確率の話であるが、実際はプロジェクトは一回こっきりなので、この予算や納期をどうやって予測するのか?というのが PMBOK のプロジェクト計画になる

プロジェクトの予算や納期は分布している

個人にとっては1回こっきりのプロジェクトなのだが、会社にとって複数のプロジェクトが動いていれば、赤字もあり黒字もある、納期遅れもあれ納期より早いものもある。コストが増えたものあれば予算内でおさまったものもある。つまり、複数のプロジェクトでみれば、コストと納期は分布していると言える。

image

プロジェクトが納期/コスト通りに終わる確率は、正規分布なのかカイ二乗分布なのか色々憶測があると思うが「分布」があることが変わりない。少なくとも目標値の前後に広がりがある。

実際のところ納期よりも前にプロジェクトが終わることは稀(これには理由がある)なので、納期遅れが頻発するので適当な指標が必要になるが、大雑把いえば、次の図のように「本来の納期」から遅れが生じたとしても8割の確率で納期遅れが発生しない時点を、プロジェクトの開始時に「納期」として提示すればよい。

image

この部分を80%ラインに置くのか、95%ラインに置くのかは色々だが。納期遅れがシビアな場合は95%が望ましいし、会社の赤黒だけを考えれば8割方プロジェクトが成功するならばまずまずの成績と言える。

この「本来の納期」と80%ラインである顧客に提示する「納期」との差は「保険」と呼ばれ、プロジェクト計画時のマージンとなる。よく言われる「計画時の1.5倍すると大丈夫」というのがそれになる。この確率の話は、CCPM には書いてあるんだけど、PMBOK には出てこない。逆に言えば、どんなに WBS をしっかり作ったとしても納期やコストは分布(ひろがり)を持つので、納期ぴったりに終わる確率は極めて少なく、納期前に終わる確率は50%となる。

じゃあ、なんでプロジェクトは納期通りに終わるのか?というと、納品日に合わせて人が努力をしているからだ、と言える。遅れ気味になったら努力をして残業をして納品日にあわせようとするし、逆に早すぎる場合は待ちの時間を入れて納品日にあわせようとする(早めに納品したりしない)。極端な話、努力の余地がないサイコロを振って出た目だけ進むプロジェクト(実際に CCPM の解説として使われる)の場合は、納品日が前後する。当たり前だ。調節の余地がないからだ。

なので、プロジェクトは実際のところコストや納期に関しては分布を持つので、ぴったりとした値にはならない。というのが、科学的な根拠である。

プロジェクトの QCD はどのように分布するのか?

Q(要件や機能)は分布するのか?というと、まあアジャイル開発の例もあるし、先に書いた時間とともに顧客のニーズ自体も変化するので、実は QCD そのものが分布し、広がりを持つ。

さて、ここの80%成功ラインだが、最初のグラフを参照すると「プロジェクト終了時」には納期が決定している(納品しているから当たり前だ)。当然、コストも一意に決まる。

image

これを確率のグラフと重ね合わせると、時間とともに青線の分布が、オレンジ色の線の分布と変わってくる。80%ラインも時間とともに中央に寄って来る。つまり「本来の納期」に収束するように動く。

これが何を意味するかと言うと、プロジェクトが進行する(時間が経つ)に従って、未確定なものが確定となるので不確定部分は減り、分布が狭くなることを意味している。当たり前といえば当たり前。前に進めば進んだだけゴールに近づいている、ということを示している(なんだかよくわからない例えかもしれないが)。プロジェクト開始時点では未確定であったものが、プロジェクトを進めることにより確定に代わるので、分布が絞れるという形になる。

これを逆に発想すると、プロジェクト開始時に未確定な部分に対して「仮に確定事項」を与えてやれば、分布は絞られる、という話になる。そう、事後分布の話と同じだ。PMBOK とか品質システムで言えば、事前にリスクを抽出しておいてそのリスクが発生したときいどうなるのか?対処はどうするのか?ということを考えておくことにあたる。

そこで、もう少し話を進めると、この未確定事項(リスク管理でもいいし QCD の各項目でもよい)が決定したとしてシミュレーションをすれば、プロジェクトの成功確率を逆算できるのではないか?というが仮説である。また、プロジェクトが進行するうちに未確定事項やリスク項目が減ってきたら、それを確定として再びシミュレーションすれば、プロジェクトの成功率を再計算することが可能になるだろう。

このあたりの発想は、もともと CCPM にあるのだけど、ベイズの定理をあてはめて後はシミュレーションによって求めようというのが自説。実際、プロジェクトシミュレーションの話は、ワインバーグ氏やデマルコ氏のシステムダイナミクスの話にも出てくるし、TOC でも出てくるので、古くからある考え方だと言えるだろう。この続きは、また後日。

カテゴリー: 開発, OpenCCPM | プロジェクトマネジメントに科学的根拠を付加する仮説 はコメントを受け付けていません

PHP7 上に WordPress を入れたときは php-xmlrpc を有効にする

タイトル通りですが、半日ほどハマったのでメモ書き

WordPress から XML-RPC を使って投稿するとエラーになる

以前、作っていた WpPost https://github.com/moonmile/WpPost を markdown に対応しようかと思って、新しく建てた openccpm.com につなげようとすると、エラーになりました。このブログ moonmile.net/blog は XML-RPC で投稿できているので大丈夫なんですが、なぜか新しく立てたほうにはできない。

試しに Open Live Writer を使ってつなげようとするも、アカウント作成のときや URL を指定するときに「parse error. not well formed」と出て「-32700」なエラーコードが返されます。どうやら、送っている XML のフォーマットがダメということなんですが、どういう風にダメなのか分からず。

試しに WordPress の英語版を入れたりバージョンを変えたりしてみたのですが、XML-RPC 経由で投稿できない、なぜ?最近の WordPress は XML-RPC が初期値で有効になっているはずなのに。

PHP7 の初期値では XML-RPC が有効になっていない

WordPress 側のコード(class-IXR-message.php や class-IXR-server.php)にログを入れつつ確認していったところ原因が分かりました。

apache2 + PHP7 を入れているときに、PHP 拡張の XML-RPC が初期値では有効になっていません。このために、IXR_Message::parse() でエラーになっているらしいですね。xml_parser_create が呼び出せないようです。

openccpm.com は Ubuntu 上で動いているので、

sudo apt-get install php-xml php-xmlrpc

でインストールした後に、/etc/php/7.0/apache2/php.ini を開いて

extension=php_xmlrpc.dll

のように XML-RPC を有効にします。

すると、以下のように mt.supportedMethods で有効なメソッド一覧などが取れるようになります。

カテゴリー: 開発 | PHP7 上に WordPress を入れたときは php-xmlrpc を有効にする はコメントを受け付けていません