2017年11月7日火曜日

Shell scriptで計算する際の注意

Raspberry Pi 3 Model BでLED照明をPWM制御するshell scriptを書く為にちょっとした計算が必要になった。

このscriptに与えるのは0% (消灯)〜100% (フルパワー)の数値 (0-100)で、これを実際のPWM制御に使っている0-255に変換したい。つまり、percentage → 実際の数値の変換である。この時、percentageもPWMの数値も整数とする。

Shell scriptで計算するには主に次の3つの選択肢がある:

* expr
* bash/zshなどのbuiltin → $(( EXPR )) ※shebang注意 (/bin/shだと使えなかったりする)
* bc

exprは整数演算しかできないし、shell builtinは整数への丸め方が分からないのでbcを使っていたのだが、最終的には「小数点以下の切り捨てを回避できればどれでもok」だと分かった。

うまくいったやりかた


例題: 255の76%は193.8 (193)

expr

 % expr 76 \* 255 \/ 100
 193

exprに与える式はescapeしてある (*とか/とか)

shell builtin

zshで実験した:

 % echo $((76 * 255 / 100))
 193

exprと違いescapeする必要はない。

bc

 % echo "76 * 255 / 100" | bc
 193

最初試した間違っているやりかた


percentage = A / B * 100

という式から:

A = percentage / 100 * B

と変形してそのままimplementすると、percentage / 100の結果が小数点以下の数値になり、小数点以下の演算を扱えない場合に切り捨てられて0になる。例えばbcで:

 % echo "scale=0; 3 / 255 * 100" | bc -l
 0

となるのはこのため。無論、3 / 255 * 100 = 1.1765... なので (数学的には) この結果はおかしい (少なくとも意図した結果ではない)。

bcの場合はexprと違って小数点以下の数も扱えるのだが、この場合はscale=0の効果で 3 / 255 = 0.0039...が0になるので、その後から幾ら数を掛けようと0にしかならない訳だ。

そこで、乗算と除算は (実数の範囲では) 順序を変えても計算結果が変わらない性質を利用する。

A = percentage * B / 100

つまり、3 * 100 = 300 を計算してから 255で割れば、無理なく整数部分を取り出せる:

 % echo "scale=0; 3 * 100 / 255" | bc -l
 1

これが望んだ結果である。

蛇足


乗算の順序に拘るような学校教育を真に受けた子供達が、結果の同じ演算の順序を変えることを思い付かずにこの手の問題を解決できなかった、なんて理由でプログラミング教育や実際のコーディングに悪影響が出なければ良いなとつくづく思う。

まあ、大抵のプログラミング言語は小数点以下も扱えるし問題ないか……。

0 件のコメント:

コメントを投稿