IchigoJam のマシン語でBMIを求める

先日の記事
[IchigoJam でBMIを求める試み - Qiita](https://qiita.com/mikecat_mixc/items/b06f2665b9fa9a24b6bb)
では貪欲法を用いて IchigoJam BASIC でBMIを求めたが、誤差が出てしまった。
そこで、今回はマシン語を用い、より正確なBMIを求める。

# 仕様

前回同様、身長はcm単位の値、体重はkg単位の値の10倍を入力してもらう。
なるべく多くの環境をサポートする。

# プログラム

## IchigoJam BASIC

```
10 ' BMI ｹｲｻﾝ (ﾏｼﾝ ｺﾞ)
20 POKE#700,1,0,183,47,18,224,151,6,0,0,147,134,230,15,131,213,6,0,3,214,38,0,179,133,181,2,19,5,16,39,18,5,51,6,166,2,51,85,182,2,130,128,0,0,53,163,25,136,90,136,73,67,100,35,91,67,90,67,48,180
30 POKE#73C,1,35,12,70,1,32,192,7,100,0,91,0,4,66,251,208,0,32,68,28,156,66,10,210,28,26,100,8,36,24,37,70,77,67,149,66,1,217,35,70,243,231,32,70,241,231,48,188,112,71
40 INPUT "ｼﾝﾁｮｳ(cm):",H
50 INPUT "ﾀｲｼﾞｭｳ(kg ﾉ 10 ﾊﾞｲ):",W
60 [0]=H:[1]=W:[2]=H:[3]=W:B=USR(#702,0)
70 ?"BMI:";B/10;".";B%10
```

## マシン語

[改造版 asm15](https://mikecat.github.io/asm15/) 用のコードである。

```
	' 1.0.0 で2バイト前から実行されるバグ対策
	R1 = R0 << 0
	IF M0 GOTO @M0CODE
MODE RV32C
	' @HDATA の位置を求める
	R13 = PC + 0
	R13 = R13 + 254
	' R11 = H * H
	' R12 = W * 10000
	R11 = [R13 + 0]W
	R12 = [R13 + 2]W
	R11 = R11 * R11
	R10 = R0 + #271
	R10 <<= 4
	R12 = R12 * R10
	' return (W * 10000) / (H * H)
	R10 = DIVU(R12, R11)
	RET

ALIGN 4, 0, 0
MODE M0
@M0CODE
	' R1 = H * H
	' R2 = W * 10000
	R3 = @HDATA
	R1 = [R3]W
	R2 = [R3 + 1]W
	R1 *= R1
	R3 = 100
	R3 *= R3
	R2 *= R3
	' x * R1 <= R2 となる最大の x を求めたい
	PUSH {R4, R5}
	R3 = 1
	R4 = R1
	R0 = 1
	R0 = R0 << 31
@GETNO
	R4 = R4 << 1
	R3 = R3 << 1
	R4 & R0
	IF 0 GOTO @GETNO
	R0 = 0
	' R0:yes, R3:no で二分探索
@BINSEARCH
	' while (yes + 1 < no)
	R4 = R0 + 1
	R4 - R3
	IF CS GOTO @BINSEARCH_END
	' m = yes + (no - yes) / 2
	R4 = R3 - R0
	R4 = R4 >> 1
	R4 = R4 + R0
	' if (m * R1 <= R2) { yes = m } else { no = m }
	R5 = R4
	R5 *= R1
	R5 - R2
	IF LS GOTO @BINSEARCH_YES
	R3 = R4
	GOTO @BINSEARCH
@BINSEARCH_YES
	R0 = R4
	GOTO @BINSEARCH
@BINSEARCH_END
	POP {R4, R5}
	RET

	' IchigoJam web で4バイト前から読まれるバグ対策
ORGR #104
@HDATA
	SPACE 2
@WDATA
```

# 解説

前回の記事より、入力された身長の値を `H`、体重の値を `W` として、`(W*10000) / (H*H)` を求めたい。

## RV32C

RV32C (IchigoJam R) では、掛け算も割り算も命令があるので、それを使えばよい。
(正確にはRV32CではなくRV32Mの範囲だが)

## M0

M0 (従来型 IchigoJam) では、掛け算の命令はあるが、割り算の命令は無い。
そこで、割り算の商を**めぐる式二分探索**を用いて求めることにした。
[二分探索アルゴリズムを一般化 〜 めぐる式二分探索法のススメ 〜 - Qiita](https://qiita.com/drken/items/97e37dd6143e33a64c8c)
[めぐる式二分探索｜株式会社コアテック](https://core-tech.jp/blog/tech_log/2028/)]]

今回は、`x * (H*H) <= W*10000` となる最大の整数 `x` を求める。
(`H, W` は `0 < H < 32768, 0 <= W < 32768` を満たす整数とする)
まず、`x = 0` のときは、必ず条件を満たす。
また、`W * 10000 <= 32767 * 10000 = 0x1387d8f0` なので、
`H*H` に掛けた時の商が `0x80000000` 以上になるような `x` は、必ず条件を満たさない。
よって、`H*H` を左に `n` ビットシフトすると `0x80000000` 以上になるような最小の整数 `n`について、
`0` 以上 `1 << n` 以下の範囲で二分探索を行えばよい。

## 既知のバグ対策

### 1.0.0

[IchigoJam BASIC 1.0.0 のUSRバグ - Qiita](https://qiita.com/mikecat_mixc/items/995e18241e0e14be9508)
で解析した結果、1.0.0 では `USR` で指定したアドレスの2バイト前から実行されることがわかっている。
そこで、最初の2バイトを実行されてもされなくても結果に影響しない命令にし、`USR` で2バイト目 (0-origin) を指定して実行するようにした。

### IchigoJam web

[IchigoJam web のマシン語の実行のバグの解析 - Qiita](https://qiita.com/mikecat_mixc/items/125e065e3c81aa61e4a2)
で解析した結果、IchigoJam web では `Rd = PC + u8` 命令で本来より4バイト手前のアドレスが取得されることがわかっている。
そこで、指定した位置の4バイト前にも同じデータ (身長と体重) を配置し、どっちを参照してもいいようにした。
さらに、今回の実装中、新たに `Rd = PC + u8` 命令があるアドレスが4の倍数でない場合、本来より2バイトしか手前にならない(可能性がある)ことがわかった。
そこで、`Rd = PC + u8` 命令を4バイト境界にアラインメントした。

# 実行結果

* IchigoJam U (1.0.0)
* IchigoJam R (1.5b)
* IchigoJam web (1.4.3web)

において、前回より正確な大石泉さんのBMIの値が求まった。
なお、小数第2位以降は四捨五入ではなく切り捨てである。

![IchigoJam U での実行結果](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/e4312e00-f5aa-2dba-facb-7b529eb90bf2.png)
![IchigoJam R での実行結果](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/798cc444-9373-1eae-dad2-3bc26e4fa664.png)
![IchigoJam web での実行結果](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/b1a87223-494e-a7c1-1f37-94ddde973bc8.png)

# おわりに

※IchigoJamはjig.jpの登録商標です。