skip to content
==> tests/ftz.c <==
1/* mesh-ref ftz.rs's tests, then golden digests from the Rust over every class of input: the special
2 * values crossed with each other, four sweeps of a million random pairs (any bits, tiny exponents,
3 * products and quotients near the subnormal boundary), and f64 -> f32 converts. Each digest is
4 * FNV-1a 64 over every operation's result bits, and the flushes counted must match too. */
5#include "mesh/ftz.h"
6
7#include <stdio.h>
8
9static int failures;
10#define CHECK(c)                                                                                   \
11    do {                                                                                           \
12        if (!(c)) {                                                                                \
13            fprintf(stderr, "%s:%d: CHECK(%s) failed\n", __FILE__, __LINE__, #c);                 \
14            failures++;                                                                            \
15        }                                                                                          \
16    } while (0)
17
18static const float SUB = 1e-40f; /* subnormal */
19static const float MIN = 1.17549435e-38f;
20
21static float f(uint32_t b) { return mesh_ftz_from_bits(b); }
22static uint32_t bits(float x) { return mesh_ftz_bits(x); }
23
24static void rust_inputs_and_results_flush_sign_preserving(void) {
25    CHECK(bits(mesh_ftz_add(SUB, 0.0f)) == 0);
26    CHECK(bits(mesh_ftz_add(-SUB, -0.0f)) == 0x80000000u);
27    CHECK(mesh_ftz_mul(SUB, 1e30f) == 0.0f);
28    CHECK(bits(mesh_ftz_mul(MIN, 0.5f)) == 0);
29    CHECK(bits(mesh_ftz_mul(-MIN, 0.5f)) == 0x80000000u);
30    CHECK(bits(mesh_ftz_sub(MIN, MIN * 1.5f)) == 0x80000000u);
31    CHECK(mesh_ftz_div(1.0f, SUB) == f(0x7f800000u));
32    CHECK(mesh_ftz_div(1.0f, -SUB) == f(0xff800000u));
33    CHECK(mesh_ftz_eq(mesh_ftz_mul(MIN * 2.0f, 0.5f), MIN));
34    CHECK(mesh_ftz_add(1.5f, 2.25f) == 3.75f);
35    CHECK(bits(mesh_ftz_neg(-SUB)) == 0);
36    CHECK(bits(mesh_ftz_abs(-SUB)) == 0);
37}
38
39static void rust_compares_and_converts_flush(void) {
40    float nan = f(0x7fc00000u), inf = f(0x7f800000u);
41    CHECK(mesh_ftz_eq(SUB, 0.0f) && mesh_ftz_eq(-SUB, 0.0f));
42    CHECK(mesh_ftz_ge(-SUB, 0.0f) && mesh_ftz_le(SUB, 0.0f));
43    CHECK(mesh_ftz_lt(SUB, MIN) && mesh_ftz_is_nan(nan));
44    CHECK(mesh_ftz_to_f64(SUB) == 0.0);
45    CHECK(bits(mesh_ftz_from_f64(1e-40)) == 0);
46    CHECK(bits(mesh_ftz_from_f64(-1e-40)) == 0x80000000u);
47    CHECK(mesh_ftz_from_f64(1e-30) == (float)1e-30);
48    volatile float moved = SUB; /* moves keep the bits: only operations flush */
49    CHECK(bits(moved) == bits(SUB));
50    CHECK(mesh_ftz_is_nan(nan) && mesh_ftz_is_nan(f(0xffc00000u)) && !mesh_ftz_is_nan(inf) &&
51          !mesh_ftz_is_nan(SUB));
52    CHECK(mesh_ftz_ne(nan, nan) && mesh_ftz_cmp(nan, 1.0f) == MESH_FTZ_UNORDERED);
53    CHECK(mesh_ftz_eq(-0.0f, 0.0f) && mesh_ftz_gt(1.0f, -SUB) && mesh_ftz_lt(-1.0f, 0.0f));
54}
55
56/* ---- golden digests (gold ftz, from the Rust at alpha-rust-mesh 00b526b) ---- */
57
58static const uint32_t SPECIALS[44] = {
59    0x00000000, 0x80000000, 0x00000001, 0x80000001, 0x007fffff, 0x807fffff, 0x00400000, 0x80400000,
60    0x00800000, 0x80800000, 0x00800001, 0x3f800000, 0xbf800000, 0x3f000000, 0x7f7fffff, 0xff7fffff,
61    0x7f800000, 0xff800000, 0x7fc00000, 0xffc00000, 0x7f800001, 0x7fa00000, 0xffbfffff, 0x7fffffff,
62    0x01000000, 0x0c000000, 0x33800000, 0x34000000, 0x4b800000, 0x5f000000, 0x1f800000, 0x20000000,
63    0x00000002, 0x00100000, 0x3fc00000, 0x40490fdb, 0xc0490fdb, 0x2edbe6ff, 0x0d800000, 0x8d800000,
64    0x00ffffff, 0x3f7fffff, 0x7effffff, 0x1fffffff,
65};
66
67static uint64_t fnv(uint64_t h, uint64_t w) { return (h ^ w) * 0x100000001b3ull; }
68
69static uint32_t mix(uint32_t x) { /* mesh-ref block.rs lowbias32 */
70    x ^= x >> 16;
71    x *= 0x7feb352du;
72    x ^= x >> 15;
73    x *= 0x846ca68bu;
74    return x ^ (x >> 16);
75}
76
77static uint32_t gen(uint32_t m, uint32_t i) {
78    uint32_t h = mix((i * 0x9e3779b9u) ^ (m * 0x85ebca6bu));
79    uint32_t h2 = mix(h ^ 0x27d4eb2fu);
80    switch (m) {
81    case 0: return h;
82    case 1: return (h & 0x80000000u) | ((h2 % 41) << 23) | (h & 0x007fffffu);
83    case 2: return (h & 0x80000000u) | ((50 + h2 % 31) << 23) | (h & 0x007fffffu);
84    default:
85        if (i % 2 == 0) return (h & 0x80000000u) | ((1 + h2 % 40) << 23) | (h & 0x007fffffu);
86        return (h & 0x80000000u) | ((120 + h2 % 41) << 23) | (h & 0x007fffffu);
87    }
88}
89
90static uint64_t f64_bits(double d) {
91    union {
92        double d;
93        uint64_t u;
94    } p;
95    p.d = d;
96    return p.u;
97}
98
99static double f64_from(uint64_t u) {
100    union {
101        double d;
102        uint64_t u;
103    } p;
104    p.u = u;
105    return p.d;
106}
107
108static uint64_t ops(uint64_t h, float a, float b) {
109    int32_t c = mesh_ftz_cmp(a, b);
110    uint64_t cmp = c == MESH_FTZ_LESS ? 0 : c == MESH_FTZ_EQUAL ? 1 : c == MESH_FTZ_GREATER ? 2 : 3;
111    uint64_t v[9] = {bits(mesh_ftz_add(a, b)), bits(mesh_ftz_sub(a, b)), bits(mesh_ftz_mul(a, b)),
112                     bits(mesh_ftz_div(a, b)), bits(mesh_ftz_neg(a)),    bits(mesh_ftz_abs(a)),
113                     f64_bits(mesh_ftz_to_f64(a)), cmp, mesh_ftz_is_nan(a)};
114    for (int i = 0; i < 9; i++) h = fnv(h, v[i]);
115    return h;
116}
117
118static void golden(const char *what, uint64_t got, uint64_t want, uint64_t flushes, uint64_t want_flushes) {
119    if (got != want || flushes != want_flushes) {
120        fprintf(stderr, "%s: digest %#018llx flushes %llu, want %#018llx flushes %llu\n", what,
121                (unsigned long long)got, (unsigned long long)flushes, (unsigned long long)want,
122                (unsigned long long)want_flushes);
123        failures++;
124    }
125}
126
127static void rust_golden(void) {
128    uint64_t f0 = mesh_ftz_flushes(), h = 0xcbf29ce484222325ull;
129    for (int i = 0; i < 44; i++)
130        for (int j = 0; j < 44; j++) h = ops(h, f(SPECIALS[i]), f(SPECIALS[j]));
131    golden("specials", h, 0xbff860139a4dfbe2ull, mesh_ftz_flushes() - f0, 4664);
132
133    static const uint64_t want[4] = {0x1a90a77d71d05624ull, 0xc547bacc65330903ull, 0xe7ef00233428a479ull,
134                                     0x7cfe6dcde1a58f4full};
135    static const uint64_t want_flushes[4] = {134486, 319309, 396123, 337035};
136    for (uint32_t m = 0; m < 4; m++) {
137        f0 = mesh_ftz_flushes();
138        h = 0xcbf29ce484222325ull;
139        for (uint32_t i = 0; i < 1000000; i++) h = ops(h, f(gen(m, 2 * i)), f(gen(m, 2 * i + 1)));
140        char what[16];
141        snprintf(what, sizeof what, "sweep %u", m);
142        golden(what, h, want[m], mesh_ftz_flushes() - f0, want_flushes[m]);
143    }
144
145    f0 = mesh_ftz_flushes();
146    h = 0xcbf29ce484222325ull;
147    for (uint32_t i = 0; i < 1000000; i++) {
148        uint32_t hi = mix(i ^ 0x5bd1e995u), lo = mix(hi ^ i);
149        uint64_t e = 1023 - 160 + hi % 200;
150        uint64_t b = (((uint64_t)hi & 0x80000000u) << 32) | (e << 52) | (((uint64_t)hi & 0xfffff) << 32) | lo;
151        h = fnv(h, bits(mesh_ftz_from_f64(f64_from(b))));
152    }
153    const double ds[12] = {1e-40, -1e-40, 1e-30, 1e300, -1e300, f64_from(0x7ff8000000000000ull),
154                           f64_from(0xfff8000000000000ull), f64_from(0x7ff0000000000001ull),
155                           1.1754942106924411e-38, 1.1754943508222875e-38, 0.0, -0.0};
156    for (int i = 0; i < 12; i++) h = fnv(h, bits(mesh_ftz_from_f64(ds[i])));
157    golden("from_f64", h, 0xfafb102caf21f6dcull, mesh_ftz_flushes() - f0, 120410);
158}
159
160int main(void) {
161    rust_inputs_and_results_flush_sign_preserving();
162    rust_compares_and_converts_flush();
163    rust_golden();
164    if (failures) fprintf(stderr, "%d failures\n", failures);
165    return failures != 0;
166}