|
| 1 | +// Copyright (c) Microsoft Corporation. |
| 2 | +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 3 | + |
| 4 | +#include <algorithm> |
| 5 | +#include <cassert> |
| 6 | +#include <cmath> |
| 7 | +#include <cstddef> |
| 8 | +#include <cstdint> |
| 9 | +#include <cstdio> |
| 10 | +#include <functional> |
| 11 | +#include <random> |
| 12 | +#include <vector> |
| 13 | + |
| 14 | +// INTENTIONALLY AVOIDED: using namespace std; |
| 15 | + |
| 16 | +void initialize_randomness(std::mt19937_64& gen) { |
| 17 | + constexpr std::size_t n = std::mt19937_64::state_size; |
| 18 | + constexpr std::size_t w = std::mt19937_64::word_size; |
| 19 | + static_assert(w % 32 == 0, "w should be evenly divisible by 32"); |
| 20 | + constexpr std::size_t k = w / 32; |
| 21 | + |
| 22 | + std::vector<std::uint32_t> vec(n * k); |
| 23 | + |
| 24 | + std::random_device rd; |
| 25 | + std::generate(vec.begin(), vec.end(), std::ref(rd)); |
| 26 | + |
| 27 | + std::printf("This is a randomized test.\n"); |
| 28 | + std::printf("DO NOT IGNORE/RERUN ANY FAILURES.\n"); |
| 29 | + std::printf("You must report them to the STL maintainers.\n\n"); |
| 30 | + |
| 31 | + std::seed_seq seq(vec.cbegin(), vec.cend()); |
| 32 | + gen.seed(seq); |
| 33 | +} |
| 34 | + |
| 35 | +void check_equal(const float val, const float actual, const float squared) { |
| 36 | + if (actual != squared) { |
| 37 | + std::printf("val: %.6a; actual: %.6a; squared: %.6a\n", val, actual, squared); |
| 38 | + } |
| 39 | + assert(actual == squared); |
| 40 | +} |
| 41 | + |
| 42 | +void check_equal(const double val, const double actual, const double squared) { |
| 43 | + if (actual != squared) { |
| 44 | + std::printf("val: %a; actual: %a; squared: %a\n", val, actual, squared); |
| 45 | + } |
| 46 | + assert(actual == squared); |
| 47 | +} |
| 48 | + |
| 49 | +void check_equal(const long double val, const long double actual, const long double squared) { |
| 50 | + if (actual != squared) { |
| 51 | + std::printf("val: %La; actual: %La; squared: %La\n", val, actual, squared); |
| 52 | + } |
| 53 | + assert(actual == squared); |
| 54 | +} |
| 55 | + |
| 56 | +void test_square_flt(const float val, const float squared) { |
| 57 | + // float * float is float, N5014 [expr.arith.conv]/1.4.1. |
| 58 | + check_equal(val, val * val, squared); |
| 59 | + |
| 60 | + // For std::pow(float, int), the arguments are effectively cast to double, N5014 [cmath.syn]/3. |
| 61 | + const double as_dbl = static_cast<double>(val); |
| 62 | + check_equal(as_dbl, std::pow(val, 2), as_dbl * as_dbl); |
| 63 | +} |
| 64 | + |
| 65 | +void test_square_dbl(const double val, const double squared) { |
| 66 | + check_equal(val, val * val, squared); |
| 67 | + check_equal(val, std::pow(val, 2), squared); |
| 68 | +} |
| 69 | + |
| 70 | +void test_square_ldbl(const long double val, const long double squared) { |
| 71 | + check_equal(val, val * val, squared); |
| 72 | + check_equal(val, std::pow(val, 2), squared); |
| 73 | +} |
| 74 | + |
| 75 | +// GH-5768 <cmath>: Calling UCRT ::pow(x, 2) is less accurate than x * x |
| 76 | +void test_gh_5768_manually_verified() { |
| 77 | + // Manually verified to be correctly rounded: |
| 78 | + test_square_flt(0x1.bffff4p-1f, 0x1.87ffecp-1f); |
| 79 | + test_square_flt(0x1.bc0040p-2f, 0x1.810870p-3f); |
| 80 | + test_square_flt(0x1.2b7bc2p-1f, 0x1.5e5a52p-2f); |
| 81 | + |
| 82 | + test_square_dbl(0x1.ec9a50154a6f9p-1, 0x1.d9f0c06b2463ep-1); |
| 83 | + test_square_dbl(0x1.12814d2dd432cp-1, 0x1.26590a84f9b12p-2); |
| 84 | + test_square_dbl(0x1.33994b0b751ccp-3, 0x1.719905c84494ap-6); |
| 85 | + |
| 86 | + test_square_ldbl(0x1.ec9a50154a6f9p-1L, 0x1.d9f0c06b2463ep-1L); |
| 87 | + test_square_ldbl(0x1.12814d2dd432cp-1L, 0x1.26590a84f9b12p-2L); |
| 88 | + test_square_ldbl(0x1.33994b0b751ccp-3L, 0x1.719905c84494ap-6L); |
| 89 | +} |
| 90 | + |
| 91 | +void test_gh_5768_randomized(std::mt19937_64& gen) { |
| 92 | + const int Trials = 1'000'000; |
| 93 | + |
| 94 | + { |
| 95 | + std::uniform_real_distribution<float> dist{-10.0f, 10.0f}; |
| 96 | + for (int i = 0; i < Trials; ++i) { |
| 97 | + const auto val = dist(gen); |
| 98 | + test_square_flt(val, val * val); |
| 99 | + } |
| 100 | + } |
| 101 | + |
| 102 | + { |
| 103 | + std::uniform_real_distribution<double> dist{-10.0, 10.0}; |
| 104 | + for (int i = 0; i < Trials; ++i) { |
| 105 | + const auto val = dist(gen); |
| 106 | + test_square_dbl(val, val * val); |
| 107 | + } |
| 108 | + } |
| 109 | + |
| 110 | + { |
| 111 | + std::uniform_real_distribution<long double> dist{-10.0L, 10.0L}; |
| 112 | + for (int i = 0; i < Trials; ++i) { |
| 113 | + const auto val = dist(gen); |
| 114 | + test_square_ldbl(val, val * val); |
| 115 | + } |
| 116 | + } |
| 117 | +} |
| 118 | + |
| 119 | +int main() { |
| 120 | + test_gh_5768_manually_verified(); |
| 121 | + |
| 122 | + std::mt19937_64 gen; |
| 123 | + initialize_randomness(gen); |
| 124 | + test_gh_5768_randomized(gen); |
| 125 | +} |
0 commit comments