|
|
1.1 root 1: <?php
2: //
3: // XM6i
1.1.1.2 ! root 4: // Copyright (C) 2016 Tetsuya Isaki
1.1 root 5: //
6: // XEiJ の fputest.x の結果ファイルを都合よく調整する。
7: // FMOVECR は実機の ROM に合わせる。
8: // 超越関数系の照合精度を実機並みに下げる。
9: //
10:
11: // 丸め文字列のソート順を決めるテーブル。
12: // XXX single/double と extended がどっちの順かはよく知らない。
13: $round_table = array(
14: 0 => "single RN",
15: 1 => "single RZ",
16: 2 => "single RP",
17: 3 => "single RM",
18: 4 => "double RN",
19: 5 => "double RZ",
20: 6 => "double RP",
21: 7 => "double RM",
22: 8 => "extended RN",
23: 9 => "extended RZ",
24: 10 => "extended RP",
25: 11 => "extended RM",
26: );
27:
28: if ($argc < 3) {
29: fprintf(STDERR, "usage: real.log fputest.log\n");
30: exit(1);
31: }
32:
33: // 実機での結果読み込み
34: $real = read_log($argv[1]);
35:
36: // ターゲット結果読み込み
37: $target = read_log($argv[2]);
38:
39: // セクションごとに比較方法が違う
40: adj_fmovecr();
41: pass("FMOVE");
42: pass("FINT");
43: adjs("FSINH");
44: pass("FINTRZ");
45: pass("FSQRT");
46: adjs("FLOGNP1");
47: adjs("FETOXM1");
48: adjs("FTANH");
49: adjs("FATAN");
50: adjs("FASIN");
51: adjs("FATANH");
52: adjs("FSIN");
53: adjs("FTAN");
54: adjs("FETOX");
55: adjs("FTWOTOX");
56: adjs("FTENTOX");
57: adjs("FLOGN");
58: adjs("FLOG10");
59: adjs("FLOG2");
60: pass("FABS");
61: adjs("FCOSH");
62: pass("FNEG");
63: adjs("FACOS");
64: adjs("FCOS");
65: pass("FGETEXP");
66: pass("FGETMAN");
67: pass("FDIV");
68: pass("FMOD");
69: pass("FADD");
70: pass("FMUL");
71: adjr("FSGLDIV (MC68882)");
72: pass("FREM");
73: pass("FSCALE");
74: adjr("FSGLMUL (MC68882)");
75: pass("FSUB");
76: adj2("FSINCOS");
77: pass("FCMP");
78: pass("FTST");
79: adj_fbcc();
80: print_total_result();
81: ?>
82: <?php
83: class fmp
84: {
85: public $sign; // 符号。0なら正、1なら負
86: public $exp; // 指数。ゲタなし(0 なら 2^0)
87: public $mant; // 仮数。GMP
88:
89: function __construct($w1, $w2, $w3)
90: {
91: $this->sign = 0;
92: $this->exp = 0;
93: $this->mant = 0;
94:
95: $w1 = hexdec(substr($w1, 0, 4));
96: if ($w1 & 0x8000)
97: $this->sign = 1;
98: $this->exp = ($w1 & 0x7fff) - 16383;
99: $this->mant = gmp_init("0x{$w2}{$w3}");
100: }
101: }
102:
103: // ログファイルを読み込んでオンメモリデータベースを作成。
104: // SQLite3 ハンドルを返す。
105: function read_log($filename)
106: {
107: global $round_table;
108:
109: $fp = fopen($filename, "r");
110: $db = new SQLite3(":memory:");
111:
112: $db->exec("create table t_summary ("
113: . "op string,"
114: . "summary string)");
115: $db->exec("create table t_result ("
116: . "op string,"
117: . "num int,"
118: . "name string,"
119: . "round int,"
120: . "text string)");
121:
122: $state = "";
123: while (($buf = fgets($fp)) !== false) {
124: $buf = rtrim($buf) . "\n";
125: switch ($state) {
126: case "":
127: if (preg_match("/^Testing (.*)/", $buf, $m)) {
128: $op = $m[1];
129: if ($op == "FBcc") {
130: $state = "fbcc";
131: // ここしか初期化のタイミングがない
132: $body = "";
133: } else {
134: $state = "testing";
135: }
136: }
137: break;
138:
139: case "testing":
140: if (preg_match("/^tested:/", $buf)) {
141: $state = "";
142: $buf = rtrim($buf);
143: $db->exec("insert into t_summary values "
144: . "('{$op}', '{$buf}');");
145: } else if (preg_match("/^#(\d+) (.*)/", $buf, $m)) {
146: $num = $m[1];
147: $testname = "#{$num} {$m[2]}";
148: } else if (preg_match("/expected result/", $buf)) {
149: $body .= $buf;
150: } else if (preg_match("/actual result/", $buf)) {
151: // テキストの最後の改行は取り除いておく
152: $body .= rtrim($buf);
153: $db->exec("insert into t_result values "
154: . "('{$op}', {$num}, '{$testname}', '${round}', '$body')");
155: } else if (preg_match("/^\t.dc.l.*;(.*)/", $buf, $m)) {
156: $round = array_search($m[1], $round_table);
157: $body = $buf;
158: }
159: break;
160:
161: case "fbcc":
162: // FBcc だけ形式が異なる。
163: if (preg_match("/^tested:/", $buf)) {
164:
165: // ここで body を追加。
166: $body = rtrim($body);
167: $db->exec("insert into t_result values "
168: . "('{$op}', 0, '', '', '{$body}')");
169:
170: // 続いてサマリ。
171: $buf = rtrim($buf);
172: $db->exec("insert into t_summary values "
173: . "('{$op}', '{$buf}');");
174:
175: $state = "";
176: } else if (preg_match("/expected result/", $buf)) {
177: $body .= $buf;
178: } else if (preg_match("/actual result/", $buf)) {
179: $body .= $buf;
180: }
181: }
182: }
183: fclose($fp);
184:
185: return $db;
186: }
187:
188: // NG なテスト結果を出力する。
189: // 大域変数 $failed をインクリメントする。(リセットするのは呼び出し側の責任)
190: function print_data($data)
191: {
192: global $failed;
193: global $prev_name;
194:
195: // テスト名ヘッダを表示
196: if ($data['name'] != $prev_name) {
197: $prev_name = $data['name'];
198: print "{$data['name']}\n";
199: }
200:
201: // 本文表示
202: print "{$data['text']}\n";
203:
204: $failed++;
205: }
206:
207: // 小計を出力する。
208: // $op はテスト関数名。
209: // adj_failed は調整後の失敗数、調整自体を行ってなければ false。
210: function print_result($op, $adj_failed)
211: {
212: global $target;
213: global $total_tested;
214: global $total_passed;
215: global $total_adj_passed;
216:
217: // サマリ行取得
218: $res = $target->query("select * from t_summary where op = '{$op}'");
219: $data = $res->fetchArray(SQLITE3_ASSOC);
220: print "{$data['summary']}\n";
221:
222: // 計算
223: preg_match("/tested:(\d+)(, passed:(\d+))?(, failed.*)?/",
224: $data['summary'], $m);
225: if ($m === false) {
226: print "summary cannot be parsed\n";
227: print "\n";
228: return;
229: }
230:
231: $tested = $m[1];
232: $passed = intval($m[3]);
233: $adj_passed = $passed;
234:
235: if ($adj_failed !== false) {
236: $adj_passed = $tested - $adj_failed;
237: printf("adjusted -> passed:%d(%4.1f%%)",
238: $adj_passed, $adj_passed * 100 / $tested);
239: if ($tested != $adj_passed) {
240: printf(", failed:%d(%4.1f%%)",
241: $adj_failed, $adj_failed * 100 / $tested);
242: }
243: print "\n";
244: }
245:
246: // 一行空ける
247: print "\n";
248:
249: // 大域変数に積算
250: $total_tested += $tested;
251: $total_passed += $passed;
252: $total_adj_passed += $adj_passed;
253: }
254:
255: // トータルの合計を出力する。
256: function print_total_result()
257: {
258: global $total_tested;
259: global $total_passed;
260: global $total_adj_passed;
261:
262: print "Total\n";
263: printf("tested:%d, passed:%d(%4.1f%%)",
264: $total_tested, $total_passed, $total_passed * 100 / $total_tested);
265: if ($total_tested != $total_passed) {
266: $total_failed = $total_tested - $total_passed;
267: printf(", failed:%d(%4.1f%%)",
268: $total_failed, $total_failed * 100 / $total_tested);
269: }
270: print "\n";
271:
272: printf("adjusted -> passed:%d(%4.1f%%)",
273: $total_adj_passed, $total_adj_passed * 100 / $total_tested);
274: if ($total_tested != $total_adj_passed) {
275: $total_adj_failed = $total_tested - $total_adj_passed;
276: printf(", failed:%d(%4.1f%%)",
277: $total_adj_failed, $total_adj_failed * 100 / $total_tested);
278: }
279: print "\n";
280: }
281:
282: // FMOVECR の結果を比較する。
283: // ROM と数学的真値とが異なっているところは ROM を正解とする。
284: // 予約オフセット部分については比較しない。
285: function adj_fmovecr()
286: {
287: global $real;
288: global $target;
289: global $failed;
290:
291: $op = "FMOVECR";
292: $failed = 0;
293:
294: print "Testing {$op}\n";
295: for ($i = 0; $i <= 0x3f; $i++) {
296: switch ($i) {
297: case 0:
298: case 13:
299: case 15:
300: // 公開オフセット。ROM と真値が一致している。
301: // つまり差分が出てればそのまま誤りとして表示。
302: $tres = $target->query("select * from t_result "
303: . " where op = '{$op}' and num = {$i} order by round");
304: while (($data = $tres->fetchArray(SQLITE3_ASSOC)) !== false) {
305: print_data($data);
306: }
307: break;
308:
309: case 11:
310: case 12:
311: case 14:
312: // 公開オフセット。ROM と真値が一致しないケースがある。
313: // 実機の結果と完全一致してれば一致とみなす。
314: $tres = $target->query("select * from t_result "
315: . "where op = '{$op}' and num = {$i} order by round");
316: while (($data = $tres->fetchArray(SQLITE3_ASSOC)) !== false) {
317: // 実機データの同じところ
318: $rres = $real->query("select * from t_result "
319: . "where op = '{$op}' and num = {$i} and "
320: . "round = {$data['round']}");
321: $rdata = $rres->fetchArray(SQLITE3_ASSOC);
322:
323: $tlines = explode("\n", $data['text']);
324: $input = $tlines[0];
325: $tactual = $tlines[2];
326:
327: if ($rdata === false) {
328: $ractual = $tlines[1];
329: } else {
330: $rlines = explode("\n", $rdata['text']);
331: $ractual = $rlines[2];
332: }
333: // 違ってれば、表示
334: if ($tactual != $ractual) {
335: // 表示用に expected を差し替える
336: $newlines = "{$input}\n"
337: . "{$ractual}\n" // expected は real actual
338: . "{$tactual}"; // actual は target actual
339: $data['text'] = $newlines;
340: print_data($data);
341: }
342: }
343: break;
344:
345: case 1:
346: case 2:
347: case 3:
348: case 4:
349: case 5:
350: case 6:
351: case 7:
352: case 8:
353: case 9:
354: case 10:
355: break;
356: }
357: }
358:
359: print_result($op, $failed);
360: }
361:
362: // FBcc の結果を比較する。
363: // これだけ出力フォーマットが全く異なる。
364: function adj_fbcc()
365: {
366: global $real;
367: global $target;
368: global $failed;
369:
370: $op = "FBcc";
371: $failed = 0;
372:
373: print "Testing {$op}\n";
374: // テスト名ヘッダはないので改行だけ表示されるのを防ぐ。
375: $prev_name = "";
376:
377: $tres = $target->query("select * from t_result where op = '{$op}'");
378: $data = $tres->fetchArray(SQLITE3_ASSOC);
379: if ($data !== false) {
380: print_data($data);
381: }
382:
383: print_result($op, false);
384: }
385:
386:
387: // XEiJ の期待値と比較する。つまりここでは何もしない。
388: function pass($op)
389: {
390: global $real;
391: global $target;
392: global $failed;
393:
394: $failed = 0;
395:
396: print "Testing {$op}\n";
397: $tres = $target->query("select * from t_result "
398: . "where op = '{$op}' order by num, round");
399: while (($data = $tres->fetchArray(SQLITE3_ASSOC)) !== false) {
400: print_data($data);
401: }
402:
403: print_result($op, false);
404: }
405:
406: // 超越関数とか用の比較。
407: // 実機値より誤差が小さければよしとするか。
408: function adjs($op)
409: {
410: global $real;
411: global $target;
412: global $failed;
413:
414: $failed = 0;
415:
416: print "Testing {$op}\n";
417: $tres = $target->query("select * from t_result "
418: . "where op = '{$op}' order by num, round");
419: while (($data = $tres->fetchArray(SQLITE3_ASSOC)) !== false) {
420: // 実機データの同じところ
421: $rres = $real->query("select * from t_result "
422: . "where op = '{$op}' and num = {$data['num']} "
423: . "and round = {$data['round']}");
424: $rdata = $rres->fetchArray(SQLITE3_ASSOC);
425:
426: // expected: XEiJ の期待値
427: // tactual: ターゲットログの actual
428: // ractual: 実機の actual
429: $tlines = explode("\n", $data['text']);
430: $xeij_expected = $tlines[1];
431: $actual = $tlines[2];
432: if ($rdata === false) {
433: // 実機データ側にエントリがないということは
434: // 実機の結果が XEiJ 期待と同じだったということ。
435: $real_expected = $xeij_expected;
436: } else {
437: // 実機データ側にエントリがあれば、
438: // それの actual が実機期待値。
439: $rlines = explode("\n", $rdata['text']);
440: $real_expected = $rlines[2];
441: }
442:
443: list($xm, $xeij_flag) = split_ext_flag($xeij_expected);
444: list($rm, $real_flag) = split_ext_flag($real_expected);
445: list($am, $act_flag) = split_ext_flag($actual);
446: // 判定
447: if ($xeij_flag == $real_flag && // フラグが一致
448: $xeij_flag == $act_flag &&
449: fmp_judge($xm, $rm, $am)) // 値が一致
450: {
451: continue;
452: }
453:
454: // 違っていたので、表示
455:
456: // 表示用に加工
457: // input, XEiJ expected, real expected, actual の4行にしてみる
458: $line1 = preg_replace("/expected/", "XEiJ expected",
459: $xeij_expected);
460: $line2 = preg_replace("/(expected|actual)/", "Real expected",
461: $real_expected);
462: $newlines = "{$tlines[0]}\n" // input
463: . "{$line1}\n" // XEiJ expected
464: . "{$line2}\n" // Real expected
465: . "{$actual}";
466: $data['text'] = $newlines;
467: print_data($data);
468: }
469:
470: print_result($op, $failed);
471: }
472:
473: // FSINCOS など、戻り値が2つある関数の比較。
474: // それぞれ実機値より誤差が小さければよしとするか。
475: function adj2($op)
476: {
477: global $real;
478: global $target;
479: global $failed;
480:
481: $failed = 0;
482:
483: print "Testing {$op}\n";
484: $tres = $target->query("select * from t_result "
485: . "where op = '{$op}' order by num, round");
486: while (($data = $tres->fetchArray(SQLITE3_ASSOC)) !== false) {
487: // 実機データの同じところ
488: $rres = $real->query("select * from t_result "
489: . "where op = '{$op}' and num = {$data['num']} "
490: . "and round = {$data['round']}");
491: $rdata = $rres->fetchArray(SQLITE3_ASSOC);
492:
493: // expected: XEiJ の期待値
494: // tactual: ターゲットログの actual
495: // ractual: 実機の actual
496: $tlines = explode("\n", $data['text']);
497: $xeij_expected = $tlines[1];
498: $actual = $tlines[2];
499: if ($rdata === false) {
500: // 実機データ側にエントリがないということは
501: // 実機の結果が XEiJ 期待と同じだったということ。
502: $real_expected = $xeij_expected;
503: } else {
504: // 実機データ側にエントリがあれば、
505: // それの actual が実機期待値。
506: $rlines = explode("\n", $rdata['text']);
507: $real_expected = $rlines[2];
508: }
509:
510: list($xm1, $xm2, $xeij_flag) = split_ext2_flag($xeij_expected);
511: list($rm1, $rm2, $real_flag) = split_ext2_flag($real_expected);
512: list($am1, $am2, $act_flag) = split_ext2_flag($actual);
513: // 判定
514: if ($xeij_flag == $real_flag && // フラグが一致
515: $xeij_flag == $act_flag &&
516: fmp_judge($xm1, $rm1, $am1) && // 1つ目の値が一致
517: fmp_judge($xm2, $rm2, $am2)) // 2つ目の値が一致
518: {
519: continue;
520: }
521:
522: // 違っていたので、表示
523:
524: // 表示用に加工
525: // input, XEiJ expected, real expected, actual の4行にしてみる
526: $line1 = preg_replace("/expected/", "XEiJ expected",
527: $xeij_expected);
528: $line2 = preg_replace("/(expected|actual)/", "Real expected",
529: $real_expected);
530: $newlines = "{$tlines[0]}\n" // input
531: . "{$line1}\n" // XEiJ expected
532: . "{$line2}\n" // Real expected
533: . "{$actual}";
534: $data['text'] = $newlines;
535: print_data($data);
536: }
537:
538: print_result($op, $failed);
539: }
540:
541: // FSGLDIV など、実機値との完全一致をテストするもの。
542: function adjr($op)
543: {
544: global $real;
545: global $target;
546: global $failed;
547:
548: $failed = 0;
549:
550: print "Testing {$op}\n";
551: $tres = $target->query("select * from t_result "
552: . "where op = '{$op}' order by num, round");
553: while (($data = $tres->fetchArray(SQLITE3_ASSOC)) !== false) {
554: // 実機データの同じところ
555: $rres = $real->query("select * from t_result "
556: . "where op = '{$op}' and num = {$data['num']} "
557: . "and round = {$data['round']}");
558: $rdata = $rres->fetchArray(SQLITE3_ASSOC);
559:
560: $tlines = explode("\n", $data['text']);
561: $actual = $tlines[2];
562: if ($rdata == false) {
563: // 実機データ側にエントリがないということは
564: // 実機の結果が XEiJ 期待と同じだったということ。
565: $expected = $tlines[1];
566: } else {
567: // 実機データ側にエントリがあれば、
568: // それの actual が実機期待値。
569: $rlines = explode("\n", $rdata['text']);
570: $expected = $rlines[2];
571: }
572:
573: list($rm, $real_flag) = split_ext_flag($expected);
574: list($am, $act_flag) = split_ext_flag($actual);
575: // 判定 (完全一致)
576: if ($real_flag == $act_flag &&
577: fmp_equ($rm, $am))
578: {
579: continue;
580: }
581:
582: // 違っていたので、表示
583:
584: // 表示用に加工
585: // 期待値が XEiJ と実機で同じなら expected のまま、
586: // 実機由来のものなら Real expected にしてみる。
587: $line2 = preg_replace("/actual/", "Real expected",
588: $expected);
589: $newlines = "{$tlines[0]}\n" // input
590: . "{$line2}\n" // Real expected
591: . "{$actual}";
592: $data['text'] = $newlines;
593: print_data($data);
594: }
595:
596: print_result($op, $failed);
597: }
598:
599: // 値が正しいかどうかを判定する。
600: // 判定条件は、真値と実機値との中間値を中心として、真値と実機値の距離を
601: // 半径とする直径の中に測定値が入っていること。
602: function fmp_judge($xm, $rm, $am)
603: {
604: // 3者で符号が一致してないと、false
605: if ($xm->sign != $rm->sign || $xm->sign != $am->sign) {
606: return false;
607: }
608:
609: // 3つとも指数を揃える
610: // どれか1つでも指数が64以上差がある場合は false
611: $max_exp = max($xm->exp, $rm->exp, $am->exp);
612: if (fmp_shr($xm, $max_exp - $xm->exp) === false)
613: return false;
614: if (fmp_shr($rm, $max_exp - $rm->exp) === false)
615: return false;
616: if (fmp_shr($am, $max_exp - $am->exp) === false)
617: return false;
618:
619: // 真値と実機値の距離 d
620: $d = gmp_sub($rm->mant, $xm->mant);
621: // 大小を揃える
622: if (gmp_sign($d) >= 0) {
623: $h = $rm->mant;
624: $l = $xm->mant;
625: } else {
626: $h = $xm->mant;
627: $l = $rm->mant;
628: $d = gmp_neg($d);
629: }
630:
631: // 距離 d の半分を上と下に広げたのが正答範囲
632: // ただしなんとなく距離 3 以下なら半分にせず適用
633: if (gmp_cmp($d, 3) <= 0) {
634: $d2 = $d;
635: } else {
636: $d2 = gmp_div($d, 2);
637: }
638: $low = gmp_sub($l, $d2);
639: $high = gmp_add($h, $d2);
640:
641: // 測定値がこの中に入っているか
642: // am >= low && am <= high
643: if (gmp_cmp($am->mant, $low) >= 0 && gmp_cmp($am->mant, $high) <= 0) {
644: // 一致とみなす
645: return true;
646: }
647:
648: return false;
649: }
650:
651: // 値が一致するか調べる。
652: function fmp_equ($f1, $f2)
653: {
654: if ($f1->sign == $f2->sign &&
655: $f1->exp == $f2->exp &&
656: gmp_cmp($f1->mant, $f2->mant) == 0)
657: {
658: return true;
659: }
660: return false;
661: }
662:
663: // $fmp->mant を $shift ビット右シフトして、
664: // $fmp->exp に $shift を加算する。(要は桁合わせ)
665: // $shift が 1 未満なら何もしない。
666: // $shift が 64 以上の場合は false を返す。
667: function fmp_shr(&$fmp, $shift)
668: {
669: if ($shift < 1)
670: return true;
671:
672: if ($shift > 63)
673: return false;
674:
675: $fmp->exp += $shift;
676: $two = gmp_init("2");
677: for (; $shift > 0; $shift--) {
678: $fmp->mant = gmp_div($fmp->mant, $two);
679: }
680: return true;
681: }
682:
683: // 拡張精度1つとフラグがある行を分解して返す。
684: function split_ext_flag($text)
685: {
686: preg_match("/\\$(.{8}),\\$(.{8}),\\$(.{8}),(\S*)\s+;/", $text, $m);
687: $fmp = new fmp($m[1], $m[2], $m[3]);
688: return array($fmp, $m[4]);
689: }
690:
691: // 拡張精度2つとフラグがある行を分解して返す。
692: function split_ext2_flag($text)
693: {
694: preg_match(
695: "/\\$(.{8}),\\$(.{8}),\\$(.{8}),\\$(.{8}),\\$(.{8}),\\$(.{8}),(\S*)\s+;/",
696: $text, $m);
697: $fm1 = new fmp($m[1], $m[2], $m[3]);
698: $fm2 = new fmp($m[4], $m[5], $m[6]);
699: return array($fm1, $fm2, $m[7]);
700: }
701: ?>
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.