|
|
nono 1.7.0
<?php
//
// XM6i
// Copyright (C) 2016 Tetsuya Isaki
//
// XEiJ の fputest.x の結果ファイルを都合よく調整する。
// FMOVECR は実機の ROM に合わせる。
// 超越関数系の照合精度を実機並みに下げる。
//
// 丸め文字列のソート順を決めるテーブル。
// XXX single/double と extended がどっちの順かはよく知らない。
$round_table = array(
0 => "single RN",
1 => "single RZ",
2 => "single RP",
3 => "single RM",
4 => "double RN",
5 => "double RZ",
6 => "double RP",
7 => "double RM",
8 => "extended RN",
9 => "extended RZ",
10 => "extended RP",
11 => "extended RM",
);
if ($argc < 3) {
fprintf(STDERR, "usage: real.log fputest.log\n");
exit(1);
}
// 実機での結果読み込み
$real = read_log($argv[1]);
// ターゲット結果読み込み
$target = read_log($argv[2]);
// セクションごとに比較方法が違う
adj_fmovecr();
pass("FMOVE");
pass("FINT");
adjs("FSINH");
pass("FINTRZ");
pass("FSQRT");
adjs("FLOGNP1");
adjs("FETOXM1");
adjs("FTANH");
adjs("FATAN");
adjs("FASIN");
adjs("FATANH");
adjs("FSIN");
adjs("FTAN");
adjs("FETOX");
adjs("FTWOTOX");
adjs("FTENTOX");
adjs("FLOGN");
adjs("FLOG10");
adjs("FLOG2");
pass("FABS");
adjs("FCOSH");
pass("FNEG");
adjs("FACOS");
adjs("FCOS");
pass("FGETEXP");
pass("FGETMAN");
pass("FDIV");
pass("FMOD");
pass("FADD");
pass("FMUL");
adjr("FSGLDIV (MC68882)");
pass("FREM");
pass("FSCALE");
adjr("FSGLMUL (MC68882)");
pass("FSUB");
adj2("FSINCOS");
pass("FCMP");
pass("FTST");
adj_fbcc();
print_total_result();
?>
<?php
class fmp
{
public $sign; // 符号。0なら正、1なら負
public $exp; // 指数。ゲタなし(0 なら 2^0)
public $mant; // 仮数。GMP
function __construct($w1, $w2, $w3)
{
$this->sign = 0;
$this->exp = 0;
$this->mant = 0;
$w1 = hexdec(substr($w1, 0, 4));
if ($w1 & 0x8000)
$this->sign = 1;
$this->exp = ($w1 & 0x7fff) - 16383;
$this->mant = gmp_init("0x{$w2}{$w3}");
}
}
// ログファイルを読み込んでオンメモリデータベースを作成。
// SQLite3 ハンドルを返す。
function read_log($filename)
{
global $round_table;
$fp = fopen($filename, "r");
$db = new SQLite3(":memory:");
$db->exec("create table t_summary ("
. "op string,"
. "summary string)");
$db->exec("create table t_result ("
. "op string,"
. "num int,"
. "name string,"
. "round int,"
. "text string)");
$state = "";
while (($buf = fgets($fp)) !== false) {
$buf = rtrim($buf) . "\n";
switch ($state) {
case "":
if (preg_match("/^Testing (.*)/", $buf, $m)) {
$op = $m[1];
if ($op == "FBcc") {
$state = "fbcc";
// ここしか初期化のタイミングがない
$body = "";
} else {
$state = "testing";
}
}
break;
case "testing":
if (preg_match("/^tested:/", $buf)) {
$state = "";
$buf = rtrim($buf);
$db->exec("insert into t_summary values "
. "('{$op}', '{$buf}');");
} else if (preg_match("/^#(\d+) (.*)/", $buf, $m)) {
$num = $m[1];
$testname = "#{$num} {$m[2]}";
} else if (preg_match("/expected result/", $buf)) {
$body .= $buf;
} else if (preg_match("/actual result/", $buf)) {
// テキストの最後の改行は取り除いておく
$body .= rtrim($buf);
$db->exec("insert into t_result values "
. "('{$op}', {$num}, '{$testname}', '${round}', '$body')");
} else if (preg_match("/^\t.dc.l.*;(.*)/", $buf, $m)) {
$round = array_search($m[1], $round_table);
$body = $buf;
}
break;
case "fbcc":
// FBcc だけ形式が異なる。
if (preg_match("/^tested:/", $buf)) {
// ここで body を追加。
$body = rtrim($body);
$db->exec("insert into t_result values "
. "('{$op}', 0, '', '', '{$body}')");
// 続いてサマリ。
$buf = rtrim($buf);
$db->exec("insert into t_summary values "
. "('{$op}', '{$buf}');");
$state = "";
} else if (preg_match("/expected result/", $buf)) {
$body .= $buf;
} else if (preg_match("/actual result/", $buf)) {
$body .= $buf;
}
}
}
fclose($fp);
return $db;
}
// NG なテスト結果を出力する。
// 大域変数 $failed をインクリメントする。(リセットするのは呼び出し側の責任)
function print_data($data)
{
global $failed;
global $prev_name;
// テスト名ヘッダを表示
if ($data['name'] != $prev_name) {
$prev_name = $data['name'];
print "{$data['name']}\n";
}
// 本文表示
print "{$data['text']}\n";
$failed++;
}
// 小計を出力する。
// $op はテスト関数名。
// adj_failed は調整後の失敗数、調整自体を行ってなければ false。
function print_result($op, $adj_failed)
{
global $target;
global $total_tested;
global $total_passed;
global $total_adj_passed;
// サマリ行取得
$res = $target->query("select * from t_summary where op = '{$op}'");
$data = $res->fetchArray(SQLITE3_ASSOC);
print "{$data['summary']}\n";
// 計算
preg_match("/tested:(\d+)(, passed:(\d+))?(, failed.*)?/",
$data['summary'], $m);
if ($m === false) {
print "summary cannot be parsed\n";
print "\n";
return;
}
$tested = $m[1];
$passed = intval($m[3]);
$adj_passed = $passed;
if ($adj_failed !== false) {
$adj_passed = $tested - $adj_failed;
printf("adjusted -> passed:%d(%4.1f%%)",
$adj_passed, $adj_passed * 100 / $tested);
if ($tested != $adj_passed) {
printf(", failed:%d(%4.1f%%)",
$adj_failed, $adj_failed * 100 / $tested);
}
print "\n";
}
// 一行空ける
print "\n";
// 大域変数に積算
$total_tested += $tested;
$total_passed += $passed;
$total_adj_passed += $adj_passed;
}
// トータルの合計を出力する。
function print_total_result()
{
global $total_tested;
global $total_passed;
global $total_adj_passed;
print "Total\n";
printf("tested:%d, passed:%d(%4.1f%%)",
$total_tested, $total_passed, $total_passed * 100 / $total_tested);
if ($total_tested != $total_passed) {
$total_failed = $total_tested - $total_passed;
printf(", failed:%d(%4.1f%%)",
$total_failed, $total_failed * 100 / $total_tested);
}
print "\n";
printf("adjusted -> passed:%d(%4.1f%%)",
$total_adj_passed, $total_adj_passed * 100 / $total_tested);
if ($total_tested != $total_adj_passed) {
$total_adj_failed = $total_tested - $total_adj_passed;
printf(", failed:%d(%4.1f%%)",
$total_adj_failed, $total_adj_failed * 100 / $total_tested);
}
print "\n";
}
// FMOVECR の結果を比較する。
// ROM と数学的真値とが異なっているところは ROM を正解とする。
// 予約オフセット部分については比較しない。
function adj_fmovecr()
{
global $real;
global $target;
global $failed;
$op = "FMOVECR";
$failed = 0;
print "Testing {$op}\n";
for ($i = 0; $i <= 0x3f; $i++) {
switch ($i) {
case 0:
case 13:
case 15:
// 公開オフセット。ROM と真値が一致している。
// つまり差分が出てればそのまま誤りとして表示。
$tres = $target->query("select * from t_result "
. " where op = '{$op}' and num = {$i} order by round");
while (($data = $tres->fetchArray(SQLITE3_ASSOC)) !== false) {
print_data($data);
}
break;
case 11:
case 12:
case 14:
// 公開オフセット。ROM と真値が一致しないケースがある。
// 実機の結果と完全一致してれば一致とみなす。
$tres = $target->query("select * from t_result "
. "where op = '{$op}' and num = {$i} order by round");
while (($data = $tres->fetchArray(SQLITE3_ASSOC)) !== false) {
// 実機データの同じところ
$rres = $real->query("select * from t_result "
. "where op = '{$op}' and num = {$i} and "
. "round = {$data['round']}");
$rdata = $rres->fetchArray(SQLITE3_ASSOC);
$tlines = explode("\n", $data['text']);
$input = $tlines[0];
$tactual = $tlines[2];
if ($rdata === false) {
$ractual = $tlines[1];
} else {
$rlines = explode("\n", $rdata['text']);
$ractual = $rlines[2];
}
// 違ってれば、表示
if ($tactual != $ractual) {
// 表示用に expected を差し替える
$newlines = "{$input}\n"
. "{$ractual}\n" // expected は real actual
. "{$tactual}"; // actual は target actual
$data['text'] = $newlines;
print_data($data);
}
}
break;
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
case 9:
case 10:
break;
}
}
print_result($op, $failed);
}
// FBcc の結果を比較する。
// これだけ出力フォーマットが全く異なる。
function adj_fbcc()
{
global $real;
global $target;
global $failed;
$op = "FBcc";
$failed = 0;
print "Testing {$op}\n";
// テスト名ヘッダはないので改行だけ表示されるのを防ぐ。
$prev_name = "";
$tres = $target->query("select * from t_result where op = '{$op}'");
$data = $tres->fetchArray(SQLITE3_ASSOC);
if ($data !== false) {
print_data($data);
}
print_result($op, false);
}
// XEiJ の期待値と比較する。つまりここでは何もしない。
function pass($op)
{
global $real;
global $target;
global $failed;
$failed = 0;
print "Testing {$op}\n";
$tres = $target->query("select * from t_result "
. "where op = '{$op}' order by num, round");
while (($data = $tres->fetchArray(SQLITE3_ASSOC)) !== false) {
print_data($data);
}
print_result($op, false);
}
// 超越関数とか用の比較。
// 実機値より誤差が小さければよしとするか。
function adjs($op)
{
global $real;
global $target;
global $failed;
$failed = 0;
print "Testing {$op}\n";
$tres = $target->query("select * from t_result "
. "where op = '{$op}' order by num, round");
while (($data = $tres->fetchArray(SQLITE3_ASSOC)) !== false) {
// 実機データの同じところ
$rres = $real->query("select * from t_result "
. "where op = '{$op}' and num = {$data['num']} "
. "and round = {$data['round']}");
$rdata = $rres->fetchArray(SQLITE3_ASSOC);
// expected: XEiJ の期待値
// tactual: ターゲットログの actual
// ractual: 実機の actual
$tlines = explode("\n", $data['text']);
$xeij_expected = $tlines[1];
$actual = $tlines[2];
if ($rdata === false) {
// 実機データ側にエントリがないということは
// 実機の結果が XEiJ 期待と同じだったということ。
$real_expected = $xeij_expected;
} else {
// 実機データ側にエントリがあれば、
// それの actual が実機期待値。
$rlines = explode("\n", $rdata['text']);
$real_expected = $rlines[2];
}
list($xm, $xeij_flag) = split_ext_flag($xeij_expected);
list($rm, $real_flag) = split_ext_flag($real_expected);
list($am, $act_flag) = split_ext_flag($actual);
// 判定
if ($xeij_flag == $real_flag && // フラグが一致
$xeij_flag == $act_flag &&
fmp_judge($xm, $rm, $am)) // 値が一致
{
continue;
}
// 違っていたので、表示
// 表示用に加工
// input, XEiJ expected, real expected, actual の4行にしてみる
$line1 = preg_replace("/expected/", "XEiJ expected",
$xeij_expected);
$line2 = preg_replace("/(expected|actual)/", "Real expected",
$real_expected);
$newlines = "{$tlines[0]}\n" // input
. "{$line1}\n" // XEiJ expected
. "{$line2}\n" // Real expected
. "{$actual}";
$data['text'] = $newlines;
print_data($data);
}
print_result($op, $failed);
}
// FSINCOS など、戻り値が2つある関数の比較。
// それぞれ実機値より誤差が小さければよしとするか。
function adj2($op)
{
global $real;
global $target;
global $failed;
$failed = 0;
print "Testing {$op}\n";
$tres = $target->query("select * from t_result "
. "where op = '{$op}' order by num, round");
while (($data = $tres->fetchArray(SQLITE3_ASSOC)) !== false) {
// 実機データの同じところ
$rres = $real->query("select * from t_result "
. "where op = '{$op}' and num = {$data['num']} "
. "and round = {$data['round']}");
$rdata = $rres->fetchArray(SQLITE3_ASSOC);
// expected: XEiJ の期待値
// tactual: ターゲットログの actual
// ractual: 実機の actual
$tlines = explode("\n", $data['text']);
$xeij_expected = $tlines[1];
$actual = $tlines[2];
if ($rdata === false) {
// 実機データ側にエントリがないということは
// 実機の結果が XEiJ 期待と同じだったということ。
$real_expected = $xeij_expected;
} else {
// 実機データ側にエントリがあれば、
// それの actual が実機期待値。
$rlines = explode("\n", $rdata['text']);
$real_expected = $rlines[2];
}
list($xm1, $xm2, $xeij_flag) = split_ext2_flag($xeij_expected);
list($rm1, $rm2, $real_flag) = split_ext2_flag($real_expected);
list($am1, $am2, $act_flag) = split_ext2_flag($actual);
// 判定
if ($xeij_flag == $real_flag && // フラグが一致
$xeij_flag == $act_flag &&
fmp_judge($xm1, $rm1, $am1) && // 1つ目の値が一致
fmp_judge($xm2, $rm2, $am2)) // 2つ目の値が一致
{
continue;
}
// 違っていたので、表示
// 表示用に加工
// input, XEiJ expected, real expected, actual の4行にしてみる
$line1 = preg_replace("/expected/", "XEiJ expected",
$xeij_expected);
$line2 = preg_replace("/(expected|actual)/", "Real expected",
$real_expected);
$newlines = "{$tlines[0]}\n" // input
. "{$line1}\n" // XEiJ expected
. "{$line2}\n" // Real expected
. "{$actual}";
$data['text'] = $newlines;
print_data($data);
}
print_result($op, $failed);
}
// FSGLDIV など、実機値との完全一致をテストするもの。
function adjr($op)
{
global $real;
global $target;
global $failed;
$failed = 0;
print "Testing {$op}\n";
$tres = $target->query("select * from t_result "
. "where op = '{$op}' order by num, round");
while (($data = $tres->fetchArray(SQLITE3_ASSOC)) !== false) {
// 実機データの同じところ
$rres = $real->query("select * from t_result "
. "where op = '{$op}' and num = {$data['num']} "
. "and round = {$data['round']}");
$rdata = $rres->fetchArray(SQLITE3_ASSOC);
$tlines = explode("\n", $data['text']);
$actual = $tlines[2];
if ($rdata == false) {
// 実機データ側にエントリがないということは
// 実機の結果が XEiJ 期待と同じだったということ。
$expected = $tlines[1];
} else {
// 実機データ側にエントリがあれば、
// それの actual が実機期待値。
$rlines = explode("\n", $rdata['text']);
$expected = $rlines[2];
}
list($rm, $real_flag) = split_ext_flag($expected);
list($am, $act_flag) = split_ext_flag($actual);
// 判定 (完全一致)
if ($real_flag == $act_flag &&
fmp_equ($rm, $am))
{
continue;
}
// 違っていたので、表示
// 表示用に加工
// 期待値が XEiJ と実機で同じなら expected のまま、
// 実機由来のものなら Real expected にしてみる。
$line2 = preg_replace("/actual/", "Real expected",
$expected);
$newlines = "{$tlines[0]}\n" // input
. "{$line2}\n" // Real expected
. "{$actual}";
$data['text'] = $newlines;
print_data($data);
}
print_result($op, $failed);
}
// 値が正しいかどうかを判定する。
// 判定条件は、真値と実機値との中間値を中心として、真値と実機値の距離を
// 半径とする直径の中に測定値が入っていること。
function fmp_judge($xm, $rm, $am)
{
// 3者で符号が一致してないと、false
if ($xm->sign != $rm->sign || $xm->sign != $am->sign) {
return false;
}
// 3つとも指数を揃える
// どれか1つでも指数が64以上差がある場合は false
$max_exp = max($xm->exp, $rm->exp, $am->exp);
if (fmp_shr($xm, $max_exp - $xm->exp) === false)
return false;
if (fmp_shr($rm, $max_exp - $rm->exp) === false)
return false;
if (fmp_shr($am, $max_exp - $am->exp) === false)
return false;
// 真値と実機値の距離 d
$d = gmp_sub($rm->mant, $xm->mant);
// 大小を揃える
if (gmp_sign($d) >= 0) {
$h = $rm->mant;
$l = $xm->mant;
} else {
$h = $xm->mant;
$l = $rm->mant;
$d = gmp_neg($d);
}
// 距離 d の半分を上と下に広げたのが正答範囲
// ただしなんとなく距離 3 以下なら半分にせず適用
if (gmp_cmp($d, 3) <= 0) {
$d2 = $d;
} else {
$d2 = gmp_div($d, 2);
}
$low = gmp_sub($l, $d2);
$high = gmp_add($h, $d2);
// 測定値がこの中に入っているか
// am >= low && am <= high
if (gmp_cmp($am->mant, $low) >= 0 && gmp_cmp($am->mant, $high) <= 0) {
// 一致とみなす
return true;
}
return false;
}
// 値が一致するか調べる。
function fmp_equ($f1, $f2)
{
if ($f1->sign == $f2->sign &&
$f1->exp == $f2->exp &&
gmp_cmp($f1->mant, $f2->mant) == 0)
{
return true;
}
return false;
}
// $fmp->mant を $shift ビット右シフトして、
// $fmp->exp に $shift を加算する。(要は桁合わせ)
// $shift が 1 未満なら何もしない。
// $shift が 64 以上の場合は false を返す。
function fmp_shr(&$fmp, $shift)
{
if ($shift < 1)
return true;
if ($shift > 63)
return false;
$fmp->exp += $shift;
$two = gmp_init("2");
for (; $shift > 0; $shift--) {
$fmp->mant = gmp_div($fmp->mant, $two);
}
return true;
}
// 拡張精度1つとフラグがある行を分解して返す。
function split_ext_flag($text)
{
preg_match("/\\$(.{8}),\\$(.{8}),\\$(.{8}),(\S*)\s+;/", $text, $m);
$fmp = new fmp($m[1], $m[2], $m[3]);
return array($fmp, $m[4]);
}
// 拡張精度2つとフラグがある行を分解して返す。
function split_ext2_flag($text)
{
preg_match(
"/\\$(.{8}),\\$(.{8}),\\$(.{8}),\\$(.{8}),\\$(.{8}),\\$(.{8}),(\S*)\s+;/",
$text, $m);
$fm1 = new fmp($m[1], $m[2], $m[3]);
$fm2 = new fmp($m[4], $m[5], $m[6]);
return array($fm1, $fm2, $m[7]);
}
?>
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.