File:  [Isaki's NoNo m68k/m88k emulator] / nono / lib / exttostr.cpp
Revision 1.1.1.2 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:05:39 2026 UTC (2 months, 3 weeks ago) by root
Branches: MAIN, Isaki
CVS tags: v027, v026, v025, v024, v023, HEAD
nono 1.3.0

//
// nono
// Copyright (C) 2024 nono project
// Licensed under nono-license.txt
//

//
// MC68881 の Extended フォーマットを文字列にする
//

#include "exttostr.h"
#include "mystring.h"

#define BCD_N (3)
#define BCD_M (1'000'000'000)

// 5 の n 乗を返す。n は 0..13。
static uint32
pow5(int n)
{
	static const uint32 table[] = {
		1,
		5,
		25,
		125,
		625,
		3125,
		15625,
		78125,
		390625,
		1953125,
		9765625,
		48828125,
		244140625,
		1220703125,
	};
	return table[n];
}

// 10進右シフト (というか 10 ずつの除算)。
// n には桁数ではなく 10のべき数のみ渡すこと。
static void
bcdshr(uint32 *digit, uint32 n)
{
	uint64 t;
	t = 0;
	for (int i = BCD_N - 1; i >= 0; i--) {
		t += (uint64)digit[i];
		digit[i] = t / n;
		t = (t % n) * (BCD_M);
	}
}

// digit に v を乗算する。
// 戻り値は 10進で右シフトした桁数。
static uint32
bcdmul(uint32 *digit, uint32 v)
{
	uint64 cy = 0;
	for (int i = 0; i < BCD_N; i++) {
		uint64 t = (uint64)digit[i] * v + cy;
		digit[i] = (uint32)(t % BCD_M);
		cy = t / BCD_M;
	}

	uint32 d = 0;
	while (cy != 0) {
		bcdshr(digit, 10);
		digit[BCD_N - 1] += (cy % 10) * (BCD_M / 10);
		cy /= 10;
		d++;
	}

	return d;
}

// digit に v を加算する。
static uint32
bcdadd(uint32 *digit, uint32 v)
{
	uint64 cy = v;
	for (int i = 0; i < BCD_N; i++) {
		uint64 t = (uint64)digit[i] + cy;
		digit[i] = (uint32)(t % BCD_M);
		cy = t / BCD_M;
	}
	return cy;
}

// MC68881 の Extended 形式を 10進数文字列に変換する。
std::string
ExtToStr(const uint32 *data)
{
	std::string rv;

	int s = data[0] >> 31;
	int32 e = (int32)((data[0] >> 16) & 0x7fff);
	uint64 m = ((uint64)data[1] << 32) | data[2];

	if (e == 0x7fff) {
		if (m != 0) {
			if (m & (1ULL << 62)) {
				return "QNAN";
			} else {
				return "SNAN";
			}
		} else {
			if (s == 0) {
				return "+INF";
			} else {
				return "-INF";
			}
		}
	}

	if (s == 0) {
		rv = '+';
	} else {
		rv = '-';
	}

	if (e == 0 && (m & (1ULL << 63)) == 0) {
		// denormal
		e = -16383;
	} else {
		e -= 0x3fff;
	}

	// unnormal zero も 0 として返す。
	if (m == 0) {
		rv += '0';
		return rv;
	}

	// BCD に変換。
	uint32 a[BCD_N];
	for (int i = 0; i < BCD_N; i++) {
		a[i] = m % BCD_M;
		m /= BCD_M;
	}
	e -= 63;

	int d = 0;
	uint32 x;

	// 2進指数を 10進指数化する。
	if (e > 0) {
		// 2**e を 2**31 以下単位で掛けていく。
		while (e > 0) {
			x = e;
			if (x >= 31) {
				x = 31;
			}
			e -= x;
			d += bcdmul(a, 1U << x);
		}
	} else {
		// 2**abs(e) で割らないといけないが、10以外での除算は大変なので、
		// m / 10**abs(e) * 5**abs(e) する。
		d = e;
		e = -e;
		while (e > 0) {
			x = e;
			if (x >= 13) {
				x = 13;
			}
			e -= x;
			d += bcdmul(a, pow5(x));
		}
	}

	// 上詰めにする。
	while (a[BCD_N - 1] < BCD_M / 10) {
		bcdmul(a, 10);
		d--;
	}
	d += BCD_N * 9 - 1;

	// 四捨五入。ここは 10進変換の丸めで、FPU の丸めモードとは関係ない。
#define BCD_ROUNDER	(500'000)
	bcdadd(a, BCD_ROUNDER);

	// E形式、小数点以下20桁を返す。
	rv += string_format("%u.%08u%09u%03u",
		a[BCD_N - 1] / (BCD_M / 10),
		a[BCD_N - 1] % (BCD_M / 10),
		a[BCD_N - 2],
		a[BCD_N - 3] / (BCD_ROUNDER * 2));

	if (d != 0) {
		rv += string_format("E%d", d);
	}
	return rv;
}

#if defined(TEST)
//
// How to use:
//  % make test_exttostr
//  % ./test_exttostr
//
#include <stdio.h>

int
main(int ac, char *av[])
{
#define E(M1,M2,M3) { 0x##M1, 0x##M2, 0x##M3 }
	struct {
		uint32 inp[3];
		const char *exp;
	} table[] = {
		{ E(00000000, 00000000, 00000000), "+0" },
		{ E(80000000, 00000000, 00000000), "-0" },
		{ E(7fff0000, 00000000, 00000000), "+INF" },
		{ E(ffff0000, 00000000, 00000000), "-INF" },
		{ E(7fff0000, ffffffff, ffffffff), "QNAN" },
		{ E(ffff0000, ffffffff, ffffffff), "QNAN" },
		{ E(7fff0000, bfffffff, ffffffff), "SNAN" },
		{ E(ffff0000, bfffffff, ffffffff), "SNAN" },
		{ E(3fff0000, 80000000, 00000000), "+1.00000000000000000000" },
		{ E(bfff0000, 80000000, 00000000), "-1.00000000000000000000" },
		{ E(40050000, c8000000, 00000000), "+1.00000000000000000000E2" },
		{ E(400e0000, ffff0000, 00000000), "+6.55350000000000000000E4" },

		// 1 より大きい最小の区別可能な数
		{ E(3fff0000, 80000000, 00000001), "+1.00000000000000000011" },

		// From XEiJ/EFPBox.java
		// 正規化数の最大値
		{ E(7ffe0000, ffffffff, ffffffff), "+1.18973149535723176502E4932" },
		// 正規化数の最小値
		{ E(00000000, 80000000, 00000000), "+1.68105157155604675313E-4932" },
		// 非正規化数の最大値
		{ E(00000000, 7fffffff, ffffffff), "+1.68105157155604675295E-4932" },
		// 非正規化数の最小値
		{ E(00000000, 00000000, 00000001), "+1.82259976594123730126E-4951" },
	};

	int total = countof(table);
	int failed = 0;
	for (int i = 0; i < total; i++) {
		const uint32 *inp = table[i].inp;
		const char *exp = table[i].exp;

		std::string act = ExtToStr(inp);
		if (act != exp) {
			printf("[%u] %08x_%08x_%08x expects \"%s\" but \"%s\"\n",
				i, inp[0], inp[1], inp[2], exp, act.c_str());
			failed++;
		}
	}
	if (failed > 0) {
		printf("%u tests, %u failed.\n", total, failed);
	} else {
		printf("%u tests, all passed.\n", total);
	}
	return 0;
}
#endif

unix.superglobalmegacorp.com

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