|
| 1 | +// --------------------------------------------------------------------------- |
| 2 | +// bch::abla muldiv 128-bit KAT -- the guard for the MSVC-portability rework. |
| 3 | +// |
| 4 | +// abla.hpp evaluates x*y/z (BCHN control function) in a 128-bit intermediate. |
| 5 | +// GCC/Clang use native unsigned __int128; MSVC (no __int128, error C4235) falls |
| 6 | +// back to boost::multiprecision::uint128_t. Both must be BIT-IDENTICAL or the |
| 7 | +// Windows package would silently diverge from the shipping Linux/macOS ABLA. |
| 8 | +// |
| 9 | +// This test pins that in two layers: |
| 10 | +// A. cross-platform known-answers: muldiv() must produce exact fixed results |
| 11 | +// on EVERY compiler (this is all MSVC can run -- no native to diff). |
| 12 | +// B. Linux-only equivalence sweep (guarded by __SIZEOF_INT128__): the portable |
| 13 | +// path must equal the native path across the full ABLA operand space plus a |
| 14 | +// deterministic wide-operand fuzz. Proving portable == native on the trusted |
| 15 | +// platform is what certifies the MSVC path we cannot run here. |
| 16 | +// |
| 17 | +// Build-INERT wrt consensus surface: pure header math, no node/RPC/boost-graph. |
| 18 | +// p2pool-merged-v36 surface: NONE (ABLA is a LOCAL build-time byte budget only). |
| 19 | +// --------------------------------------------------------------------------- |
| 20 | + |
| 21 | +#include <cstdint> |
| 22 | +#include <iostream> |
| 23 | +#include <limits> |
| 24 | + |
| 25 | +#include "../coin/abla.hpp" |
| 26 | + |
| 27 | +namespace { |
| 28 | + |
| 29 | +int failures = 0; |
| 30 | +#define CHECK(cond) do { if (!(cond)) { \ |
| 31 | + std::cerr << "FAIL: " #cond " @ line " << __LINE__ << "\n"; ++failures; } } while (0) |
| 32 | + |
| 33 | +using bch::coin::abla::muldiv; |
| 34 | +using bch::coin::abla::muldiv_portable; |
| 35 | +using bch::coin::abla::B7; |
| 36 | + |
| 37 | +constexpr uint64_t U64MAX = std::numeric_limits<uint64_t>::max(); |
| 38 | + |
| 39 | +// Deterministic 64-bit LCG (no <random>, no Date/rand) -- reproducible vectors. |
| 40 | +struct Lcg { |
| 41 | + uint64_t s; |
| 42 | + explicit Lcg(uint64_t seed) : s(seed) {} |
| 43 | + uint64_t next() { s = s * 6364136223846793005ULL + 1442695040888963407ULL; return s; } |
| 44 | +}; |
| 45 | + |
| 46 | +} // namespace |
| 47 | + |
| 48 | +int main() { |
| 49 | + // ---- A. cross-platform known answers (run on ALL compilers incl. MSVC) ---- |
| 50 | + // hand-computed; several exercise a product > 2^64 so the true 128-bit |
| 51 | + // intermediate is required (a 64-bit multiply would wrap and fail these). |
| 52 | + CHECK(muldiv(192, 16000000ULL, 128) == 24000000ULL); // ABLA amplify shape |
| 53 | + CHECK(muldiv(6, 7, 2) == 21ULL); |
| 54 | + CHECK(muldiv(0, 12345, 7) == 0ULL); |
| 55 | + CHECK(muldiv(U64MAX, 1, 1) == U64MAX); |
| 56 | + CHECK(muldiv(U64MAX, U64MAX, U64MAX) == U64MAX); // product ~2^128 |
| 57 | + CHECK(muldiv(1000000000000000000ULL, 1000000000ULL, |
| 58 | + 1000000000000ULL) == 1000000000000000ULL); // 1e27 intermediate |
| 59 | + CHECK(muldiv(B7, 32ULL * 1000000ULL, B7) == 32ULL * 1000000ULL);// identity via B7 |
| 60 | + |
| 61 | + // portable path must independently satisfy the same known answers |
| 62 | + CHECK(muldiv_portable(192, 16000000ULL, 128) == 24000000ULL); |
| 63 | + CHECK(muldiv_portable(U64MAX, U64MAX, U64MAX) == U64MAX); |
| 64 | + CHECK(muldiv_portable(1000000000000000000ULL, 1000000000ULL, |
| 65 | + 1000000000000ULL) == 1000000000000000ULL); |
| 66 | + |
| 67 | +#if defined(__SIZEOF_INT128__) |
| 68 | + using bch::coin::abla::muldiv_native; |
| 69 | + |
| 70 | + // ---- B1. exact ABLA-domain vectors: native == portable ---- |
| 71 | + // operand shapes taken straight from State::NextBlockState call sites. |
| 72 | + const uint64_t sizes[] = { |
| 73 | + 0, 1, B7, 192, 37938, 1000000ULL, 16ULL * 1000000ULL, |
| 74 | + 32ULL * 1000000ULL, 2000ULL * 1000000ULL, (1ULL << 40), (1ULL << 62) |
| 75 | + }; |
| 76 | + for (uint64_t x : sizes) |
| 77 | + for (uint64_t y : sizes) |
| 78 | + for (uint64_t z : sizes) { |
| 79 | + if (z == 0) continue; |
| 80 | + // only compare where the true quotient fits uint64_t (the ABLA |
| 81 | + // invariant); otherwise both paths take low-64 of an out-of-range |
| 82 | + // value and the comparison is not meaningful. |
| 83 | + const unsigned __int128 q = |
| 84 | + (static_cast<unsigned __int128>(x) * y) / z; |
| 85 | + if (q > static_cast<unsigned __int128>(U64MAX)) continue; |
| 86 | + CHECK(muldiv_native(x, y, z) == muldiv_portable(x, y, z)); |
| 87 | + } |
| 88 | + |
| 89 | + // ---- B2. wide deterministic fuzz: native == portable ---- |
| 90 | + Lcg rng(0x9E3779B97F4A7C15ULL); |
| 91 | + int compared = 0; |
| 92 | + for (int i = 0; i < 200000; ++i) { |
| 93 | + const uint64_t x = rng.next(); |
| 94 | + const uint64_t y = rng.next(); |
| 95 | + uint64_t z = rng.next(); |
| 96 | + if (z == 0) z = 1; |
| 97 | + const unsigned __int128 q = |
| 98 | + (static_cast<unsigned __int128>(x) * y) / z; |
| 99 | + if (q > static_cast<unsigned __int128>(U64MAX)) continue; // keep in-domain |
| 100 | + ++compared; |
| 101 | + if (muldiv_native(x, y, z) != muldiv_portable(x, y, z)) { |
| 102 | + std::cerr << "FAIL fuzz: x=" << x << " y=" << y << " z=" << z << "\n"; |
| 103 | + ++failures; |
| 104 | + } |
| 105 | + } |
| 106 | + CHECK(compared > 1000); // sanity: the domain filter did not reject everything |
| 107 | + std::cerr << "muldiv KAT: native==portable over " << compared << " fuzz vectors\n"; |
| 108 | +#else |
| 109 | + std::cerr << "muldiv KAT: no __int128 (MSVC) -- known-answer layer only\n"; |
| 110 | +#endif |
| 111 | + |
| 112 | + if (failures) { std::cerr << failures << " CHECK(s) FAILED\n"; return 1; } |
| 113 | + std::cerr << "muldiv_kat_test: ALL PASS\n"; |
| 114 | + return 0; |
| 115 | +} |
0 commit comments