UbuntuとTiny Core Linuxのシェルで計算してみる

シェルで計算した時のメモです。Ubuntu16とTiny Core LinuxのCoreのインストール直後（と思う）状態で試しています。ここでいう標準シェルとはUbuntuならbash、Tiny Core Linuxならashを想定しています。

## まずは整数の計算
`expr`コマンドで計算することができます。

```console:exprコマンドを計算
$ echo $(expr 1 + 2)
3
```

`$(())`でも計算できます。

```console:$(())で計算
$ echo $((1 + 2))
3
```

### Tiny Core Linuxでの注意点
Tiny Core Linuxで`$(())`を使う際は書き方に注意が必要のようです。以下はマイナスの数値を引き算する例ですが、書き方によってはarithmetic syntax errorになってしまうようです。

```console:TinyCoreLinuxでマイナス計算
$ a=1;b=-2
$ echo $(($a-$b))
-sh: arithmetic syntax error
$ echo $(($a - $b))
3
$ echo $((a-b))
3
```

また10桁を超えたあたりから結果がおかしくなるようです。Tiny Core Linuxで10桁を超える場合は`expr`コマンドを使う必要がありそうです。

```console:TinyCoreLinuxで11桁の数値を計算
$ echo $(expr 12345678901 + 2)
12345678903
$ echo $((12345678901 + 2))
2
```

### 実数は使えません
残念ながら`expr`も`$(())`も小数点を含むとエラーになります。これはUbuntuでもTiny Core Linuxでも同じです。

```console:
$ echo $(expr 1.5 + 2)
expr: non-integer argument
$ echo $((1.5 + 2))
bash: 1.5 + 2: syntax error: invalid arithmetic operator (error token is ".5 + 2")
```

## 小数点を含んだもののも計算

### bcコマンド
`bc`コマンドを使えば小数点を含む数値も計算できます。Tiny Core LinuxのCoreと呼ばれる最小構成の素の状態には`bc`コマンドは含まれませんので、この方法はTiny Core LinuxのCoreでは使えません。※もちろんbcパッケージをインストールすれば使えます。


```console:bcコマンド
$ echo $(echo '1.5 + 2' | bc)
3.5
```

`-l`オプションをつければsin、cos関数も使えます。

```console:bcコマンドでsinとcos関数を使う
$ echo $(echo "s(80*3.14/180)" | bc -l)
.98468459013058331641
$ echo $(echo "c(80*3.14/180)" | bc -l)
.17434522637389628631
```

### awkコマンド
`awk`コマンドのprintは計算式をサポートているだけでなく、小数点を含んだ数値もsin、cosといった関数も使えて、Tiny Core Linuxでも使えます。

```console:awkコマンドで計算
$ echo $(echo '1.5 2' | awk '{print $1 + $2}')
3.5
```

```console:awkコマンドでsinとcos関数を使う
$ echo $(echo "80" | awk '{print sin($1*3.14/180)}')
0.984685
$ echo $(echo "80" | awk '{print cos($1*3.14/180)}')
0.174345
```

`awk`の場合入力が必要なんですが、以下のようにヌル文字を渡せば計算式がそのまま書けます。個人的には`echo`で値を渡す方が関数ぽく書けて好きですが…

```console:awkコマンドでsinとcos関数を使う(2)
$ echo $(echo "" | awk '{print sin(80*3.14/180)}')
0.984685
$ echo $(echo "" | awk '{print cos(80*3.14/180)}')
0.174345
```

## 実行速度の違い
簡単なスクリプトを作って実行速度を測ってみました。

|テスト|Ubuntu16|TinyCoreLinux v7|
|:--|:--|:--|
|整数(expr)|0.168544sec|0.133583sec|
|整数($(()))|0.00392652sec|0.00325775sec|
|整数(bc)|0.255331sec|error|
|整数(awk)|0.228399sec|0.20771sec|
|実数(bc)|0.269739sec|error|
|実数(awk)|0.230662sec|0.205647sec|
|関数(bc)|0.32672sec|error|
|関数(awk)|0.230426sec|0.209495sec|

結果として整数の場合なら`expr`よりも`$(())`の方が速くて、実数の場合なら`bc`コマンドより`awk`コマンドの方が速いようです。
整数計算が10桁以下なら`$(())`、10桁を超えるなら`expr`、実数計算なら`awk`コマンドを使うようにすればUbuntuでもTiny Core Linuxでも良い結果が出そうです。

### 速度測定スクリプトのソース
以下が今回使用した速度測定スクリプトです。このスクリプトではナノ秒を取得するために`date`コマンドの`%N`を使っているのですが、Tiny Core Linux v8では`%N`が使えないようですのでv8では使えません。

```bash:速度測定スクリプト
#!/bin/sh
int_expr() {
  a=0
  for i in $(seq 0 99);do
    a=$(expr $a + $i)
  done
  echo $a
}

int_brackets() {
  a=0
  for i in $(seq 0 99);do
    a=$(($a + $i))
  done
  echo $a
}
int_bc() {
  a=0
  for i in $(seq 0 99);do
    a=$(echo "$a + $i" | bc)
  done
  echo $a
}
int_awk() {
  a=0
  for i in $(seq 0 99);do
    a=$(echo "$a $i" | awk '{print $1+$2}')
  done
  echo $a
}
real_bc() {
  a=0
  for i in $(seq 0 99);do
    a=$(echo "$a+$i*0.5" | bc -l)
  done
  echo $a
}
real_awk() {
  a=0
  for i in $(seq 0 99);do
    a=$(echo "$a $i" | awk '{print $1+$2*0.5}')
  done
  echo $a
}
func_bc() {
  a=0
  for i in $(seq 0 99);do
    a=$(echo "$a+s($i*3.14/180)+c($i*3.14/180)" | bc -l)
  done
  echo $a
}
func_awk() {
  a=0
  for i in $(seq 0 99);do
    a=$(echo "$a $i" | awk '{print $1+sin($2*3.14/180)+cos($2*3.14/180)}')
  done
  echo $a
}
set_time() {
	start_time=$(date +'%s.%N')
}
echo_time() {
	end_time=$(date +'%s.%N')
	echo $(echo "$start_time $end_time"|awk '{print $2-$1}')'sec'
}
set_time;int_expr;echo_time
set_time;int_brackets;echo_time
set_time;int_bc;echo_time
set_time;int_awk;echo_time
set_time;real_bc;echo_time
set_time;real_awk;echo_time
set_time;func_bc;echo_time
set_time;func_awk;echo_time

```