#include "./int128.h" #include "./endian.h" #include "./check.hpp" #define CHECK MLIB_CHECK #include #include #include #include #include #if (defined(__GNUC__) && __GNUC__ < 7 && !defined(__clang__)) || (defined(_MSC_VER) && _MSC_VER < 1920) // Old GCC and old MSVC have partially-broken constexpr that prevents us from // properly using static_assert with from_string() #define BROKEN_CONSTEXPR #endif #ifndef BROKEN_CONSTEXPR // Basic checks with static_asserts, check constexpr correctness and fail fast static_assert(mlib_int128_eq(MLIB_INT128(0), MLIB_INT128_FROM_PARTS(0, 0)), "fail"); static_assert(mlib_int128_eq(MLIB_INT128(4), MLIB_INT128_FROM_PARTS(4, 0)), "fail"); static_assert(mlib_int128_eq(MLIB_INT128(34), MLIB_INT128_FROM_PARTS(34, 0)), "fail"); static_assert(mlib_int128_eq(MLIB_INT128(34 + 8), MLIB_INT128_FROM_PARTS(42, 0)), "fail"); static_assert(mlib_int128_eq(MLIB_INT128_CAST(94), MLIB_INT128_FROM_PARTS(94, 0)), "fail"); static_assert(mlib_int128_eq(mlib_int128_lshift(MLIB_INT128_CAST(1), 64), MLIB_INT128_FROM_PARTS(0, 1)), "fail"); static_assert(mlib_int128_eq(mlib_int128_lshift(MLIB_INT128_CAST(1), 127), MLIB_INT128_FROM_PARTS(0, 1ull << 63)), "fail"); static_assert(mlib_int128_scmp(MLIB_INT128_CAST(2), MLIB_INT128(0)) > 0, "fail"); static_assert(mlib_int128_scmp(MLIB_INT128_CAST(-2), MLIB_INT128(0)) < 0, "fail"); static_assert(mlib_int128_scmp(MLIB_INT128_CAST(0), MLIB_INT128(0)) == 0, "fail"); // Unsigned compare doesn't believe in negative numbers: static_assert(mlib_int128_ucmp(MLIB_INT128_CAST(-2), MLIB_INT128(0)) > 0, "fail"); #endif // BROKEN_CONSTEXPR // Literals, for test convenience: #ifndef BROKEN_CONSTEXPR constexpr #endif mlib_int128 operator""_i128(const char *s) { return mlib_int128_from_string(s, NULL); } #ifndef BROKEN_CONSTEXPR constexpr #endif mlib_int128 operator""_i128(const char *s, size_t) { return mlib_int128_from_string(s, NULL); } // Operators, for test convenience constexpr bool operator==(mlib_int128 l, mlib_int128 r) { return mlib_int128_eq(l, r); } constexpr bool operator<(mlib_int128 l, mlib_int128 r) { return mlib_int128_scmp(l, r) < 0; } #ifndef BROKEN_CONSTEXPR static_assert(mlib_int128_eq(MLIB_INT128(0), 0_i128), "fail"); static_assert(mlib_int128_eq(MLIB_INT128(65025), 65025_i128), "fail"); static_assert(mlib_int128_eq(MLIB_INT128_FROM_PARTS(0, 1), 18446744073709551616_i128), "fail"); static_assert(mlib_int128_eq(MLIB_INT128_UMAX, 340282366920938463463374607431768211455_i128), "fail"); static_assert(mlib_int128_scmp(MLIB_INT128_SMIN, MLIB_INT128_SMAX) < 0, "fail"); static_assert(mlib_int128_scmp(MLIB_INT128_SMAX, MLIB_INT128_SMIN) > 0, "fail"); static_assert(mlib_int128_scmp(MLIB_INT128_CAST(-12), MLIB_INT128_CAST(0)) < 0, "fail"); static_assert(mlib_int128_scmp(MLIB_INT128_CAST(12), MLIB_INT128_CAST(0)) > 0, "fail"); // Simple arithmetic: static_assert(mlib_int128_scmp(mlib_int128_add(MLIB_INT128_SMAX, 1_i128), MLIB_INT128_SMIN) == 0, "fail"); static_assert(mlib_int128_scmp(mlib_int128_negate(MLIB_INT128_CAST(-42)), MLIB_INT128(42)) == 0, "fail"); static_assert(mlib_int128_scmp(mlib_int128_sub(5_i128, 3_i128), 2_i128) == 0, "fail"); static_assert(mlib_int128_scmp(mlib_int128_sub(3_i128, 5_i128), mlib_int128_negate(2_i128)) == 0, "fail"); static_assert(mlib_int128_ucmp(mlib_int128_sub(3_i128, 5_i128), mlib_int128_sub(MLIB_INT128_UMAX, 1_i128)) == 0, "fail"); static_assert(mlib_int128_scmp(mlib_int128_lshift(1_i128, 127), MLIB_INT128_SMIN) == 0, "fail"); static_assert(mlib_int128_scmp(mlib_int128_rshift(mlib_int128_lshift(1_i128, 127), 127), 1_i128) == 0, "fail"); // With no high-32 bits in the denominator static_assert(mlib_int128_div(316356263640858117670580590964547584140_i128, 13463362962560749016052695684_i128) == 23497566285_i128, "fail"); // Remainder correctness with high bit set: static_assert(mlib_int128_mod(292590981272581782572061492191999425232_i128, 221673222198185508195462959065350495048_i128) == 70917759074396274376598533126648930184_i128, "fail"); // Remainder with 64bit denom: static_assert(mlib_int128_mod(2795722437127403543495742528_i128, 708945413_i128) == 619266642_i128, "fail"); // 10-div: static_assert(mlib_int128_div(MLIB_INT128_SMAX, 10_i128) == 17014118346046923173168730371588410572_i128, "fail"); #endif // BROKEN_CONSTEXPR inline std::ostream &operator<<(std::ostream &out, const mlib_int128 &v) { out << mlib_int128_format(v).str; return out; } #ifndef BROKEN_CONSTEXPR static_assert(mlib_int128(MLIB_INT128_UMAX) == 340282366920938463463374607431768211455_i128, "fail"); // Check sign extension works correctly: static_assert(mlib_int128(MLIB_INT128_CAST(INT64_MIN)) == mlib_int128_negate(9223372036854775808_i128), "fail"); static_assert(mlib_int128(MLIB_INT128_CAST(INT64_MIN)) < mlib_int128_negate(9223372036854775807_i128), "fail"); static_assert(mlib_int128_negate(9223372036854775809_i128) < mlib_int128(MLIB_INT128_CAST(INT64_MIN)), "fail"); #endif #ifdef __SIZEOF_INT128__ // mlib_int128_to_native converts mlib_int128 to native __uint128_t. // Endian-ness is accounted for. static __uint128_t mlib_int128_to_native(mlib_int128 in) { __uint128_t out; uint8_t *out_u8 = (uint8_t *)&out; if (MLIB_IS_BIG_ENDIAN) { // Copy hi, then lo. memcpy(out_u8, &in.r.hi, sizeof(in.r.hi)); memcpy(out_u8 + sizeof(in.r.hi), &in.r.lo, sizeof(in.r.lo)); } else { // Copy lo, then hi. memcpy(out_u8, &in.r.lo, sizeof(in.r.lo)); memcpy(out_u8 + sizeof(in.r.lo), &in.r.hi, sizeof(in.r.hi)); } return out; } // native_to_mlib_int128 converts native __uint128_t to mlib_int128. // Endian-ness is accounted for. static mlib_int128 native_to_mlib_int128(__uint128_t in) { mlib_int128 out; uint8_t *in_u8 = (uint8_t *)∈ if (MLIB_IS_BIG_ENDIAN) { // Copy hi, then lo. memcpy(&out.r.hi, in_u8, sizeof(out.r.hi)); memcpy(&out.r.lo, in_u8 + sizeof(out.r.hi), sizeof(out.r.lo)); } else { // Copy lo, then hi. memcpy(&out.r.lo, in_u8, sizeof(out.r.lo)); memcpy(&out.r.hi, in_u8 + sizeof(out.r.lo), sizeof(out.r.hi)); } return out; } #endif // __SIZEOF_INT128__ static mlib_int128_divmod_result div_check(mlib_int128 num, mlib_int128 den) { // std::cout << "Check: " << num << " รท " << den << '\n'; mlib_int128_divmod_result res = mlib_int128_divmod(num, den); #ifdef __SIZEOF_INT128__ // When we have an existing i128 impl, test against that: __uint128_t num1 = mlib_int128_to_native(num); __uint128_t den1 = mlib_int128_to_native(den); __uint128_t q = num1 / den1; __uint128_t r = num1 % den1; mlib_int128_divmod_result expect; expect.quotient = native_to_mlib_int128(q); expect.remainder = native_to_mlib_int128(r); if (!mlib_int128_eq(expect.quotient, res.quotient) || !mlib_int128_eq(expect.remainder, res.remainder)) { std::cout << "unexpected result in division" << " num=" << mlib_int128_format(num).str << " den=" << mlib_int128_format(den).str << " expect.quotient=" << mlib_int128_format(expect.quotient).str << " expect.remainder=" << mlib_int128_format(expect.remainder).str << std::endl; } CHECK(expect.quotient == res.quotient); CHECK(expect.remainder == res.remainder); #endif // Check inversion by multiplication provides the correct result auto invert = mlib_int128_mul(res.quotient, den); invert = mlib_int128_add(invert, res.remainder); CHECK(invert == num); return res; } // Runtime checks, easier to debug that static_asserts int main() { mlib_int128 zero = MLIB_INT128(0); CHECK(mlib_int128_eq(zero, MLIB_INT128(0))); CHECK(mlib_int128_eq(zero, 0_i128)); CHECK(zero == 0_i128); auto two = MLIB_INT128(2); auto four = mlib_int128_add(two, two); CHECK(four == MLIB_INT128(4)); CHECK(four == 4_i128); CHECK(two == mlib_int128_add(two, zero)); // Addition wraps: mlib_int128 max = MLIB_INT128_SMAX; auto more = mlib_int128_add(max, four); CHECK(more == mlib_int128_add(MLIB_INT128_SMIN, MLIB_INT128(3))); // "Wrap" around zero: auto ntwo = MLIB_INT128_CAST(-2); auto sum = mlib_int128_add(ntwo, four); CHECK(sum == two); auto eight = mlib_int128_lshift(two, 2); CHECK(eight == MLIB_INT128(8)); auto big = mlib_int128_lshift(two, 72); CHECK(mlib_int128_scmp(big, MLIB_INT128(0)) > 0); auto four_v2 = mlib_int128_lshift(eight, -1); CHECK(four == four_v2); // Negative literals: CHECK(MLIB_INT128(-64) == mlib_int128_negate(64_i128)); CHECK(mlib_int128_mul(1_i128, 2_i128) == 2_i128); CHECK(mlib_int128_mul(1_i128, 0_i128) == 0_i128); CHECK(mlib_int128_mul(0_i128, 0_i128) == 0_i128); CHECK(mlib_int128_mul(2_i128, 73_i128) == 146_i128); CHECK(mlib_int128_mul(28468554863115876158655557_i128, 73_i128) == 2078204505007458959581855661_i128); CHECK(mlib_int128_mul(MLIB_INT128_CAST(-7), 4_i128) == MLIB_INT128_CAST(-28)); CHECK(mlib_int128_mul(MLIB_INT128_CAST(-7), MLIB_INT128_CAST(-7)) == 49_i128); // It's useful to specify bit patterns directly auto in_binary = 0b110101010110100100001101111001111010100010111100100101101011010110101001010110110011000100000100011110010101101001111110001000_i128; CHECK(in_binary == 70917759074396274376598533126648930184_i128); CHECK( in_binary == "0b110101010110100100001101111001111010100010111100100101101011010110101001010110110011000100000100011110010101101001111110001000"_i128); // Or hexadecimal auto in_hex = 0x355a4379ea2f25ad6a56cc411e569f88_i128; CHECK(in_hex == 70917759074396274376598533126648930184_i128); int8_t n = -12; CHECK(mlib_int128_scmp(zero, MLIB_INT128_CAST(n)) > 0); CHECK(mlib_int128_ucmp(zero, MLIB_INT128_CAST(n)) < 0); auto _2pow127 = mlib_int128_pow2(127); CHECK(std::string(mlib_int128_format(_2pow127).str) == "170141183460469231731687303715884105728"); auto r = div_check(27828649044156246570177174673037165454_i128, 499242349997913298655486252455941907_i128); CHECK(r.quotient == 55_i128); CHECK(r.remainder == 370319794271015144125430787960360569_i128); r = div_check(64208687961221311123721027584_i128, 3322092839076102144_i128); CHECK(r.remainder == 3155565729965670400_i128); // This division will trigger the rare Knuth 4.3.1D/D6 condition: r = div_check(31322872034807296605612234499929458960_i128, 34573864092216774938021667884_i128); CHECK(r.quotient == 905969663_i128); CHECK(r.remainder == 34573864092065898160364055868_i128); // Self-divide: r = div_check(628698094597401606590302208_i128, 628698094597401606590302208_i128); CHECK(r.quotient == 1_i128); CHECK(r.remainder == 0_i128); // With no high-32 bits in the denominator r = div_check(316356263640858117670580590964547584140_i128, 13463362962560749016052695684_i128); CHECK(r.quotient == 23497566285_i128); // Remainder correctness with high bit set: r = div_check(292590981272581782572061492191999425232_i128, 221673222198185508195462959065350495048_i128); CHECK(r.remainder == 70917759074396274376598533126648930184_i128); // Remainder with 64bit denom: r = div_check(2795722437127403543495742528_i128, 708945413_i128); CHECK(r.remainder == 619266642_i128); // 10-div: CHECK(mlib_int128_div(MLIB_INT128_SMAX, 10_i128) == 17014118346046923173168730371588410572_i128); std::random_device rd; std::seed_seq seed({rd(), rd(), rd(), rd()}); // Pick every numerator bit pattern from 0b00'00 to 0b11'11 for (auto nbits = 0u; nbits < 16u; ++nbits) { // This is an extremely rudimentary thread pool to parallelize the // division checks. It doesn't need to be rigorous or optimal, it only // needs to "just work." std::vector threads; // Pick every denominator bit pattern from 0b00'01 to 0b11'11: for (auto dbits = 1u; dbits < 16u; ++dbits) { // Randomness: std::mt19937 random; random.seed(seed); // Spawn a thread for this denominator bit pattern: threads.emplace_back([nbits, dbits, random]() mutable { std::uniform_int_distribution dist; // 100k random divisions: for (auto i = 0; i < 100000; ++i) { // Generate a denominator auto den = 0_i128; while (den == 0_i128) { // Regenerate until we don't have zero (very // unlikely, but be safe!) uint64_t dlo = 0, dhi = 0; (dbits & 1) && (dlo |= dist(random)); (dbits & 2) && (dlo |= (uint64_t)dist(random) << 32); (dbits & 4) && (dhi |= dist(random)); (dbits & 8) && (dhi |= (uint64_t)dist(random) << 32); den = MLIB_INT128_FROM_PARTS(dlo, dhi); } // Generate a numerator uint64_t nlo = 0, nhi = 0; (nbits & 1) && (nlo |= dist(random)); (nbits & 2) && (nlo |= (uint64_t)dist(random) << 32); (nbits & 4) && (nhi |= dist(random)); (nbits & 8) && (nhi |= (uint64_t)dist(random) << 32); mlib_int128 num = MLIB_INT128_FROM_PARTS(nlo, nhi); // Divide them: div_check(num, den); } }); } // Join the threads that are dividing: for (auto &t : threads) { t.join(); } } }