意外と陥ってしまう単位変換の失敗で、思っている値と異なってしまうことがあります。
先日も、後輩からうまくいかないんです。と相談を受けて一緒に原因を探っていました。
結局、単位変換のところが悪くて、思っていた結果と違っていたということでしたが、これを解決するのに2時間もかかってしまいました。
どういうプログラムであったか?
まず、どういうプログラムを作っていたかというと、速度から移動距離を求めて、現在の位置を知るというものです。
エレベータのプログラムですから、速度や現在位置を把握するプログラムは必要ですし、その求め方もすでにノウハウは存在しています。
そうでなければ、世の中にあるエレベータはどうやって、制御しているの?
ってことになりますよね。
なので、私は後輩に「そのまま、プログラムをコピーしたらできるはずだよ。」とアドバイスしました。彼もその通りに実施していました。
なぜ、もともとあるのに、今更そのようなプログラムが必要なのかと疑問に思うかもしれませんが、システム上、速度を制御しているプログラムとは別のプログラムで必要なので、同じものを作ってもらっていました。
難易度的に言えば、コピーして少し手直しするだけなので、2,3時間でできるので、超簡単レベルです。
それでも、いざ動かしてみたら、実際に移動した距離とプログラムで計算した距離に大きな差があったのです。
もちろん、本物のエレベータでは実験できませんから、シミュレーションでの話ですので、安心してください。
プログラム内では速度や位置は単位が異なる
エレベータ制御の世界では1mmの距離を1mmとして扱わず、もっと大きな値にしています。たとえば、1mm = 2000とか。
なので、実際に0.1mm動いたら、プログラム内部では200変化するということになります。
なぜそうしているかというと、非常に高い精度を求められるからです。
5mmの段差があるだけでクレームになります。電車とホームの段差や隙間はあれだけ大きくてもクレームにならないのに、エレベータはたった数mmでクレームになるのです。大げさなのは日本人だけですが...
したがって、シビアな制御がいるわけです。
プログラムの実行周期も1/1000秒くらいの世界です。
考えられますか?時計の秒針が1つ進む間に、プログラムは1000回実行するのです。
それくらいでなければ、ミリ単位の制御はできません。なので、ミリを扱うのもプログラム内部では大きな値で扱うようにしているのです。
ここで、あなたは疑問に思うかもしれません。それなら、小数点で扱えばいいのではないかと。
パソコンのソフトの場合はそれでいいでしょう。しかし、エレベータのような組み込みソフトの場合は、基本的に処理速度が優先されるため、少数は扱いません。小数を扱うようにすると、1つの変数に使用する領域が増えるし、演算に時間がかかってしまうからです。
先ほどもお話ししたように、シビアな制御が求められますので、演算に時間がかかってしまってはいけないのですね。
最終的には、私たちが分かりやすいミリ単位に変換して、いろんな処理に使用します。
位置に関してお話ししましたが、速度に関しても同じように、プログラム内部では精度を出すために大きな値で扱っています。
それがゆえに、プログラミングしてきている人でも陥ってしまう間違いが発生したのです。
どのような間違いだったのか?
あなたは、以下のプログラムを見て、どこが間違っているかわかりますか?
long d_vel = vel + vel_diff;
long d_pos = d_vel >> TIMERATE;
POS_DISP = POS_DISP + DIST_TO_MILIMETER_SIGNED(d_pos);
pls_diff = d_vel - (d_pos << TIMERATE);
上記のプログラムが一定間隔で実行され、位置の演算を行っています。
ここで求めたいのは、POS_DISPでミリ単位の値です。
他の変数の説明は以下の通りで、型はすべてlongです。
vel :今回の速度
vel_diff:前回の速度余り
d_vel :今回扱う速度
d_pos :今回の移動距離
TIMERATE:正の定数
DIST_TO_MILIMETER_SIGNED:距離をミリ単位に変換する関数(d_pos / [1mm辺りの数値] の演算が行われる)
・
・
・
・
・
どうですか?わかりましたか?
今回の移動距離(d_pos)をミリに変換してから、現在のPOS_DISPと足して新たなPOS_DISPにしているところが間違っているのです。
なぜかというと、DIST_TO_MILIMETER_SIGNED関数内で割り算が行われています。1mm辺りの数値より小さい場合、値は0になってしまいます。
実行周期は先ほどお話ししたように、1/1000秒単位です。この間に進む距離が1mmもなければ、ずっと計算上0のままです。
しかし、実際にエレベータは動いているわけですから、プログラムの位置と実際の位置が異なってしまうのです。
このように一見したら、間違いはないように思いがちですが、実際には大きな間違いとなってしまいます。
では、後輩はすでに実績のあるプログラムをコピーしていなかったのかということになるのですが、コピーしていなかったということになります。
意図通りにしようと思うと、下記のようにする必要があります。
long d_vel = vel + vel_diff;
long d_pos = d_vel >> TIMERATE;
POTISION = POSITION + d_pos;
POS_DISP = DIST_TO_MILIMETER_SIGNED(POSITION);
pls_diff = d_vel - (d_pos << TIMERATE);
ここで、あらたにPOSITIONという単位変換前の変数を追加し、POSITIONに値を足しこみ、その結果をミリに変換して、POS_DISPに代入するという修正をすることによって、問題が解決しました。
前者のプログラムでは実行ごとに1ミリ以下の部分がすべて切り捨てられてしまうので、意図した距離が求められず、後者のプログラムでは実行ごとに1ミリ以下の部分も残した状態で、単位変換が行われるため、意図した距離が求めることができるのです。
なぜ、解決するまでに2時間かかったのか?
なぜ、上記プログラムの誤りに気付くのに2時間もかかったのかというと、思い込みが大きいと思います。
一見、間違ったところがないので、一度、問題ないと思ってしまうと、その部分は大丈夫と思ってしまうものです。
私も相談され、後輩のプログラムを見たとき、間違いがないように思いました。
前者のプログラムに対して、いろいろモニターする処理を追加し、試行錯誤しました。
そうすると、DIST_TO_MILIMETER_SIGNED(d_pos)のあたいが0か1にしか変化していないことに気づきました。
それまで、その部分には手を触れることをせずに、それまでの変数、定数の値が問題ないのかとかのほうに注意がいっていたのです。
割り算を先に行ってしまうと精度がなくなってしまうので、割り算はできる限り最後で行うのが鉄則であることを改めて知った内容でした。
もっと早く、気づいてあげられたらよかったのですが、なかなか気づくことができず、無駄に時間を費やしてしまいました。
プログラムの間違いは意外と単純なことが多かったりするのですが、それがわかるまでに時間がかかるものなのです。
今回の案件は2時間でしたが、もっと深みにはまると2,3日トンネルから抜け出せなかったりするものもあります。
しかし、あきらめてはいけません。原因は必ずどこかにあります。その日で解決できなくても、次の日にあっさり解決出来たりもします。
あまりに行き詰ったら、一旦、頭を冷やすためにも、気分転換して別のことをするのも一つの手です。
あなたも、整数の割り算には気を付けて、プログラミングしてもらえればと思います。