先日の記事
IchigoJam でBMIを求める試み - Qiita
では貪欲法を用いて 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 用のコードである。
' 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
めぐる式二分探索|株式会社コアテック]]
今回は、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
で解析した結果、1.0.0 では USR
で指定したアドレスの2バイト前から実行されることがわかっている。
そこで、最初の2バイトを実行されてもされなくても結果に影響しない命令にし、USR
で2バイト目 (0-origin) を指定して実行するようにした。
IchigoJam web
IchigoJam web のマシン語の実行のバグの解析 - Qiita
で解析した結果、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はjig.jpの登録商標です。