File:  [Isaki's NoNo m68k/m88k emulator] / nono / exp / fputestadj.php
Revision 1.1.1.2 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:05:59 2026 UTC (2 months, 3 weeks ago) by root
Branches: MAIN, Isaki
CVS tags: v027, HEAD
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]);
}
?>

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.