145 lines
5.3 KiB
C
145 lines
5.3 KiB
C
/* SPDX-License-Identifier: AGPL-3.0-or-later */
|
|
/**
|
|
* @file test_softfloat.c
|
|
* @brief Proves fw/ft_softfloat.c against the host's hardware binary32 (round to nearest
|
|
* even, no flush-to-zero): every operation on edge-value pairs, many millions of random
|
|
* and near-equal-exponent pairs, and int -> float over a stride of all 2^32 inputs
|
|
* (FULL=1 in the environment: every input). NaN results must be exactly 0x7FC00000.
|
|
*
|
|
* make -f Makefile.softfloat check (red: make -f Makefile.softfloat red)
|
|
*
|
|
* @status Added 2026-10-08.
|
|
* @issues None
|
|
* @todo None
|
|
*/
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
float ft_test___addsf3(float a, float b);
|
|
float ft_test___subsf3(float a, float b);
|
|
float ft_test___mulsf3(float a, float b);
|
|
float ft_test___divsf3(float a, float b);
|
|
int ft_test___eqsf2(float a, float b);
|
|
int ft_test___nesf2(float a, float b);
|
|
int ft_test___ltsf2(float a, float b);
|
|
int ft_test___lesf2(float a, float b);
|
|
int ft_test___gtsf2(float a, float b);
|
|
int ft_test___gesf2(float a, float b);
|
|
int32_t ft_test___fixsfsi(float a);
|
|
float ft_test___floatsisf(int32_t a);
|
|
float ft_test___floatunsisf(uint32_t a);
|
|
|
|
static uint64_t checks;
|
|
static uint64_t failures;
|
|
|
|
static uint32_t bits(float f) { uint32_t b; memcpy(&b, &f, 4); return b; }
|
|
static float flt(uint32_t b) { float f; memcpy(&f, &b, 4); return f; }
|
|
|
|
static void report(const char *what, uint32_t a, uint32_t b, uint32_t got, uint32_t want)
|
|
{
|
|
if (failures++ < 20)
|
|
printf(" FAIL %s(%08x, %08x) = %08x, hardware %08x\n", what, a, b, got, want);
|
|
}
|
|
|
|
/** Same bits, or both NaN with ours canonical. */
|
|
static void same(const char *what, uint32_t a, uint32_t b, float got, float want)
|
|
{
|
|
uint32_t g = bits(got), w = bits(want);
|
|
checks++;
|
|
if (want != want) {
|
|
if (g != 0x7FC00000u)
|
|
report(what, a, b, g, w);
|
|
} else if (g != w) {
|
|
report(what, a, b, g, w);
|
|
}
|
|
}
|
|
|
|
static volatile float sink_a, sink_b; /* keep the hardware op at run time */
|
|
|
|
static void pair(uint32_t a_bits, uint32_t b_bits)
|
|
{
|
|
float a, b;
|
|
sink_a = flt(a_bits);
|
|
sink_b = flt(b_bits);
|
|
a = sink_a;
|
|
b = sink_b;
|
|
same("add", a_bits, b_bits, ft_test___addsf3(a, b), a + b);
|
|
same("sub", a_bits, b_bits, ft_test___subsf3(a, b), a - b);
|
|
same("mul", a_bits, b_bits, ft_test___mulsf3(a, b), a * b);
|
|
same("div", a_bits, b_bits, ft_test___divsf3(a, b), a / b);
|
|
checks += 6;
|
|
if ((ft_test___eqsf2(a, b) == 0) != (a == b)) report("eq", a_bits, b_bits, 0, 0);
|
|
if ((ft_test___nesf2(a, b) != 0) != (a != b)) report("ne", a_bits, b_bits, 0, 0);
|
|
if ((ft_test___ltsf2(a, b) < 0) != (a < b)) report("lt", a_bits, b_bits, 0, 0);
|
|
if ((ft_test___lesf2(a, b) <= 0) != (a <= b)) report("le", a_bits, b_bits, 0, 0);
|
|
if ((ft_test___gtsf2(a, b) > 0) != (a > b)) report("gt", a_bits, b_bits, 0, 0);
|
|
if ((ft_test___gesf2(a, b) >= 0) != (a >= b)) report("ge", a_bits, b_bits, 0, 0);
|
|
}
|
|
|
|
static void to_int(uint32_t a_bits)
|
|
{
|
|
float a = flt(a_bits);
|
|
int32_t got = ft_test___fixsfsi(a);
|
|
int32_t want;
|
|
checks++;
|
|
if (a != a)
|
|
want = (a_bits & 0x80000000u) ? INT32_MIN : INT32_MAX;
|
|
else if (a >= 2147483648.0f)
|
|
want = INT32_MAX;
|
|
else if (a < -2147483648.0f)
|
|
want = INT32_MIN;
|
|
else
|
|
want = (int32_t)a;
|
|
if (got != want)
|
|
report("fix", a_bits, 0, (uint32_t)got, (uint32_t)want);
|
|
}
|
|
|
|
static uint64_t state = 0x243F6A8885A308D3ull;
|
|
static uint32_t next(void)
|
|
{
|
|
state = state * 6364136223846793005ull + 1442695040888963407ull;
|
|
return (uint32_t)(state >> 32);
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
static const uint32_t edges[] = {
|
|
0x00000000u, 0x80000000u, 0x00000001u, 0x80000001u, 0x00000002u, 0x007FFFFFu,
|
|
0x807FFFFFu, 0x00800000u, 0x80800000u, 0x00800001u, 0x00FFFFFFu, 0x3F800000u,
|
|
0xBF800000u, 0x3F800001u, 0x3F7FFFFFu, 0x40000000u, 0x3F000000u, 0x33800000u,
|
|
0x34000000u, 0x4B000000u, 0x4B7FFFFFu, 0x4F000000u, 0xCF000000u, 0x4EFFFFFFu,
|
|
0x7F000000u, 0x7F7FFFFFu, 0xFF7FFFFFu, 0x7F800000u, 0xFF800000u, 0x7FC00000u,
|
|
0xFFC00000u, 0x7F800001u, 0x7FBFFFFFu, 0x1F800000u, 0x5F800000u, 0x20000000u,
|
|
};
|
|
const size_t edge_count = sizeof(edges) / sizeof(edges[0]);
|
|
const char *full = getenv("FULL");
|
|
uint64_t stride = (full && *full == '1') ? 1u : 4099u;
|
|
|
|
for (size_t i = 0; i < edge_count; i++) {
|
|
to_int(edges[i]);
|
|
for (size_t j = 0; j < edge_count; j++)
|
|
pair(edges[i], edges[j]);
|
|
}
|
|
for (uint32_t n = 0; n < 20000000u; n++) {
|
|
uint32_t a = next(), b = next();
|
|
switch (n % 4u) {
|
|
case 0: pair(a, b); break;
|
|
case 1: pair(a, (b & 0x80FFFFFFu) | (a & 0x7F000000u)); break; /* close exponents */
|
|
case 2: pair(a & 0x80FFFFFFu, b & 0x80FFFFFFu); break; /* subnormal-heavy */
|
|
default: pair(a, a ^ (b & 0x8000001Fu)); break; /* near-cancellation */
|
|
}
|
|
to_int(a);
|
|
}
|
|
for (uint64_t v = 0; v <= 0xFFFFFFFFull; v += stride) {
|
|
uint32_t u = (uint32_t)v;
|
|
same("floatsi", u, 0, ft_test___floatsisf((int32_t)u), (float)(int32_t)u);
|
|
same("floatunsi", u, 0, ft_test___floatunsisf(u), (float)u);
|
|
to_int(u);
|
|
}
|
|
printf("%llu checks, %llu failed\n", (unsigned long long)checks,
|
|
(unsigned long long)failures);
|
|
return failures != 0;
|
|
}
|