/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <folly/ConstexprMath.h>

#include <cmath>
#include <limits>
#include <type_traits>

#include <folly/lang/Bits.h>
#include <folly/portability/GMock.h>
#include <folly/portability/GTest.h>

namespace {
class ConstexprMathTest : public testing::Test {};
} // namespace

TEST_F(ConstexprMathTest, constexpr_iterated_squares_desc_scaling_array) {
  using lim = std::numeric_limits<float>;
  constexpr auto& isq = folly::constexpr_iterated_squares_desc_2_v<float>;

  auto get = [](auto const& arr, auto fun) {
    constexpr auto size = sizeof(arr) / sizeof(arr[0]);
    using res_t = decltype(fun(arr[0]));
    std::array<res_t, size> res{};
    for (size_t i = 0; i < size; ++i) {
      res[i] = fun(arr[i]);
    }
    return res;
  };

  EXPECT_EQ(7, isq.size);
  auto apowers = get(isq.scaling, [](auto _) { return _.power; });
  constexpr size_t epowers[] = {
      64, 32, 16, 8, 4, 2, 1, //
  };
  EXPECT_THAT(apowers, testing::ElementsAreArray(epowers));
  auto ascales = get(isq.scaling, [](auto _) { return _.scale; });
  constexpr float escales[] = {
      0x1p64, 0x1p32, 0x1p16, 0x1p8, 0x1p4, 0x1p2, 0x1p1, //
  };
  EXPECT_THAT(ascales, testing::ElementsAreArray(escales));
  EXPECT_GT(isq.scaling[0].scale, lim::max() / isq.scaling[0].scale);
}

TEST_F(ConstexprMathTest, constexpr_iterated_squares_desc_shrink) {
  using lim = std::numeric_limits<double>;
  constexpr auto& isq = folly::constexpr_iterated_squares_desc_2_v<double>;

  {
    constexpr auto n = 1.;
    constexpr auto a = isq.shrink(n, 1.);
    EXPECT_EQ(0, a.power);
    EXPECT_EQ(1., a.scale);
    EXPECT_LE(n / a.scale, 1.);
    EXPECT_GT(n / a.scale, .5);
  }
  {
    constexpr auto n = 2.;
    constexpr auto a = isq.shrink(n, 1.);
    EXPECT_EQ(1, a.power);
    EXPECT_EQ(2., a.scale);
    EXPECT_LE(n / a.scale, 1.);
    EXPECT_GT(n / a.scale, .5);
  }
  {
    constexpr auto n = 4.;
    constexpr auto a = isq.shrink(n, 1.);
    EXPECT_EQ(2, a.power);
    EXPECT_EQ(4., a.scale);
    EXPECT_LE(n / a.scale, 1.);
    EXPECT_GT(n / a.scale, .5);
  }
  {
    constexpr auto n = 7.;
    constexpr auto a = isq.shrink(n, 1.);
    EXPECT_EQ(3, a.power);
    EXPECT_EQ(8., a.scale);
    EXPECT_LE(n / a.scale, 1.);
    EXPECT_GT(n / a.scale, .5);
  }
  {
    constexpr auto n = 8.;
    constexpr auto a = isq.shrink(n, 1.);
    EXPECT_EQ(3, a.power);
    EXPECT_EQ(8., a.scale);
    EXPECT_LE(n / a.scale, 1.);
    EXPECT_GT(n / a.scale, .5);
  }
  {
    constexpr auto n = 9.;
    constexpr auto a = isq.shrink(n, 1.);
    EXPECT_EQ(4, a.power);
    EXPECT_EQ(16., a.scale);
    EXPECT_LE(n / a.scale, 1.);
    EXPECT_GT(n / a.scale, .5);
  }
  {
    constexpr auto n = 513.;
    constexpr auto a = isq.shrink(n, 1.);
    EXPECT_EQ(10, a.power);
    EXPECT_EQ(1024., a.scale);
    EXPECT_LE(n / a.scale, 1.);
    EXPECT_GT(n / a.scale, .5);
  }
  {
    constexpr auto n = 1023.;
    constexpr auto a = isq.shrink(n, 1.);
    EXPECT_EQ(10, a.power);
    EXPECT_EQ(1024., a.scale);
    EXPECT_LE(n / a.scale, 1.);
    EXPECT_GT(n / a.scale, .5);
  }
  {
    constexpr auto n = lim::max();
    constexpr auto a = isq.shrink(n, 2.);
    EXPECT_EQ(1023, a.power);
    EXPECT_EQ(folly::constexpr_pow(2., 1023), a.scale);
    EXPECT_LE(n / a.scale, 2.);
    EXPECT_GT(n / a.scale, .5);
  }
}

TEST_F(ConstexprMathTest, constexpr_iterated_squares_desc_growth) {
  using lim = std::numeric_limits<double>;
  constexpr auto& isq = folly::constexpr_iterated_squares_desc_2_v<double>;

  {
    constexpr auto n = 1.;
    constexpr auto a = isq.growth(n, 1.);
    EXPECT_EQ(0, a.power);
    EXPECT_EQ(1., a.scale);
    EXPECT_GE(n * a.scale, 1.);
    EXPECT_LT(n * a.scale, 2.);
  }
  {
    constexpr auto n = 1. / 2.;
    constexpr auto a = isq.growth(n, 1.);
    EXPECT_EQ(1, a.power);
    EXPECT_EQ(2., a.scale);
    EXPECT_GE(n * a.scale, 1.);
    EXPECT_LT(n * a.scale, 2.);
  }
  {
    constexpr auto n = 1. / 4.;
    constexpr auto a = isq.growth(n, 1.);
    EXPECT_EQ(2, a.power);
    EXPECT_EQ(4., a.scale);
    EXPECT_GE(n * a.scale, 1.);
    EXPECT_LT(n * a.scale, 2.);
  }
  {
    constexpr auto n = 1. / 7.;
    constexpr auto a = isq.growth(n, 1.);
    EXPECT_EQ(3, a.power);
    EXPECT_EQ(8., a.scale);
    EXPECT_GE(n * a.scale, 1.);
    EXPECT_LT(n * a.scale, 2.);
  }
  {
    constexpr auto n = 1. / 8.;
    constexpr auto a = isq.growth(n, 1.);
    EXPECT_EQ(3, a.power);
    EXPECT_EQ(8., a.scale);
    EXPECT_GE(n * a.scale, 1.);
    EXPECT_LT(n * a.scale, 2.);
  }
  {
    constexpr auto n = 1. / 9.;
    constexpr auto a = isq.growth(n, 1.);
    EXPECT_EQ(4, a.power);
    EXPECT_EQ(16., a.scale);
    EXPECT_GE(n * a.scale, 1.);
    EXPECT_LT(n * a.scale, 2.);
  }
  {
    constexpr auto n = 1. / 513.;
    constexpr auto a = isq.growth(n, 1.);
    EXPECT_EQ(10, a.power);
    EXPECT_EQ(1024., a.scale);
    EXPECT_GE(n * a.scale, 1.);
    EXPECT_LT(n * a.scale, 2.);
  }
  {
    constexpr auto n = 1. / 1023.;
    constexpr auto a = isq.growth(n, 1.);
    EXPECT_EQ(10, a.power);
    EXPECT_EQ(1024., a.scale);
    EXPECT_GE(n * a.scale, 1.);
    EXPECT_LT(n * a.scale, 2.);
  }
  {
    constexpr auto n = lim::min(); // NOLINT
    constexpr auto a = isq.growth(n, .5);
    EXPECT_EQ(1021, a.power);
    EXPECT_EQ(folly::constexpr_pow(2., 1021), a.scale);
    EXPECT_GE(n * a.scale, .5);
    EXPECT_LT(n * a.scale, 1.);
  }
}

TEST_F(ConstexprMathTest, constexpr_min) {
  constexpr auto x = uint16_t(3);
  constexpr auto y = uint16_t(7);
  constexpr auto z = uint16_t(4);
  constexpr auto a = folly::constexpr_min(x, y, z);
  EXPECT_EQ(3, a);
  EXPECT_TRUE((std::is_same<const uint16_t, decltype(a)>::value));
}

TEST_F(ConstexprMathTest, constexpr_max) {
  constexpr auto x = uint16_t(3);
  constexpr auto y = uint16_t(7);
  constexpr auto z = uint16_t(4);
  constexpr auto a = folly::constexpr_max(x, y, z);
  EXPECT_EQ(7, a);
  EXPECT_TRUE((std::is_same<const uint16_t, decltype(a)>::value));
}

TEST_F(ConstexprMathTest, constexpr_clamp) {
  constexpr auto lo = uint16_t(3);
  constexpr auto hi = uint16_t(7);
  constexpr auto x = folly::constexpr_clamp(uint16_t(2), lo, hi);
  constexpr auto y = folly::constexpr_clamp(uint16_t(5), lo, hi);
  constexpr auto z = folly::constexpr_clamp(uint16_t(8), lo, hi);
  EXPECT_EQ(3, x);
  EXPECT_EQ(5, y);
  EXPECT_EQ(7, z);
  EXPECT_TRUE((std::is_same<const uint16_t, decltype(y)>::value));
}

TEST_F(ConstexprMathTest, constexpr_abs_unsigned) {
  constexpr auto v = uint32_t(17);
  constexpr auto a = folly::constexpr_abs(v);
  EXPECT_EQ(17, a);
  EXPECT_TRUE((std::is_same<const uint32_t, decltype(a)>::value));
}

TEST_F(ConstexprMathTest, constexpr_abs_signed_positive) {
  constexpr auto v = int32_t(17);
  constexpr auto a = folly::constexpr_abs(v);
  EXPECT_EQ(17, a);
  EXPECT_TRUE((std::is_same<const uint32_t, decltype(a)>::value));
}

TEST_F(ConstexprMathTest, constexpr_abs_signed_negative) {
  constexpr auto v = int32_t(-17);
  constexpr auto a = folly::constexpr_abs(v);
  EXPECT_EQ(17, a);
  EXPECT_TRUE((std::is_same<const uint32_t, decltype(a)>::value));
}

TEST_F(ConstexprMathTest, constexpr_abs_float_positive) {
  constexpr auto v = 17.5f;
  constexpr auto a = folly::constexpr_abs(v);
  EXPECT_EQ(17.5, a);
  EXPECT_TRUE((std::is_same<const float, decltype(a)>::value));
}

TEST_F(ConstexprMathTest, constexpr_abs_float_negative) {
  constexpr auto v = -17.5f;
  constexpr auto a = folly::constexpr_abs(v);
  EXPECT_EQ(17.5, a);
  EXPECT_TRUE((std::is_same<const float, decltype(a)>::value));
}

TEST_F(ConstexprMathTest, constexpr_abs_double_positive) {
  constexpr auto v = 17.5;
  constexpr auto a = folly::constexpr_abs(v);
  EXPECT_EQ(17.5, a);
  EXPECT_TRUE((std::is_same<const double, decltype(a)>::value));
}

TEST_F(ConstexprMathTest, constexpr_abs_double_negative) {
  constexpr auto v = -17.5;
  constexpr auto a = folly::constexpr_abs(v);
  EXPECT_EQ(17.5, a);
  EXPECT_TRUE((std::is_same<const double, decltype(a)>::value));
}

TEST_F(ConstexprMathTest, constexpr_log2_1) {
  constexpr auto v = 1ull;
  constexpr auto a = folly::constexpr_log2(v);
  EXPECT_EQ(0ull, a);
  EXPECT_TRUE((std::is_same<decltype(v), decltype(a)>::value));
}

TEST_F(ConstexprMathTest, constexpr_log2_2) {
  constexpr auto v = 2ull;
  constexpr auto a = folly::constexpr_log2(v);
  EXPECT_EQ(1ull, a);
  EXPECT_TRUE((std::is_same<decltype(v), decltype(a)>::value));
}

TEST_F(ConstexprMathTest, constexpr_log2_64) {
  constexpr auto v = 64ull;
  constexpr auto a = folly::constexpr_log2(v);
  EXPECT_EQ(6ull, a);
  EXPECT_TRUE((std::is_same<decltype(v), decltype(a)>::value));
}

TEST_F(ConstexprMathTest, constexpr_log2_ceil_1) {
  constexpr auto v = 1ull;
  constexpr auto a = folly::constexpr_log2_ceil(v);
  EXPECT_EQ(0ull, a);
  EXPECT_TRUE((std::is_same<decltype(v), decltype(a)>::value));
}

TEST_F(ConstexprMathTest, constexpr_log2_ceil_2) {
  constexpr auto v = 2ull;
  constexpr auto a = folly::constexpr_log2_ceil(v);
  EXPECT_EQ(1ull, a);
  EXPECT_TRUE((std::is_same<decltype(v), decltype(a)>::value));
}

TEST_F(ConstexprMathTest, constexpr_log2_ceil_3) {
  constexpr auto v = 3ull;
  constexpr auto a = folly::constexpr_log2_ceil(v);
  EXPECT_EQ(2ull, a);
  EXPECT_TRUE((std::is_same<decltype(v), decltype(a)>::value));
}

TEST_F(ConstexprMathTest, constexpr_log2_ceil_63) {
  constexpr auto v = 63ull;
  constexpr auto a = folly::constexpr_log2_ceil(v);
  EXPECT_EQ(6ull, a);
  EXPECT_TRUE((std::is_same<decltype(v), decltype(a)>::value));
}

TEST_F(ConstexprMathTest, constexpr_log2_ceil_64) {
  constexpr auto v = 64ull;
  constexpr auto a = folly::constexpr_log2_ceil(v);
  EXPECT_EQ(6ull, a);
  EXPECT_TRUE((std::is_same<decltype(v), decltype(a)>::value));
}

TEST_F(ConstexprMathTest, constexpr_trunc_floating) {
  using lim = std::numeric_limits<double>;

  {
    constexpr auto n = lim::infinity();
    constexpr auto a = folly::constexpr_trunc(n);
    EXPECT_EQ(n, a);
  }
  {
    constexpr auto n = -lim::infinity();
    constexpr auto a = folly::constexpr_trunc(n);
    EXPECT_EQ(n, a);
  }
  {
    constexpr auto n = lim::quiet_NaN();
    constexpr auto a = folly::constexpr_trunc(n);
    EXPECT_TRUE(std::isnan(a));
  }
  {
    constexpr auto n = -lim::quiet_NaN();
    constexpr auto a = folly::constexpr_trunc(n);
    EXPECT_TRUE(std::isnan(a));
  }
  {
    constexpr auto n = lim::max();
    constexpr auto a = folly::constexpr_trunc(n);
    EXPECT_EQ(n, a);
  }
  {
    constexpr auto n = -lim::max();
    constexpr auto a = folly::constexpr_trunc(n);
    EXPECT_EQ(n, a);
  }
  {
    constexpr auto a = folly::constexpr_trunc(0.);
    EXPECT_EQ(0., a);
    EXPECT_FALSE(std::signbit(a));
  }
  {
    constexpr auto a = folly::constexpr_trunc(-0.);
    EXPECT_EQ(-0., a);
    EXPECT_TRUE(std::signbit(a));
  }
  {
    constexpr auto a = folly::constexpr_trunc(.5);
    EXPECT_EQ(0., a);
    EXPECT_FALSE(std::signbit(a));
  }
  {
    constexpr auto a = folly::constexpr_trunc(-.5);
    EXPECT_EQ(0., a);
    EXPECT_TRUE(std::signbit(a));
  }
  {
    constexpr auto a = folly::constexpr_trunc(3.5);
    EXPECT_EQ(3.0, a);
  }
  {
    constexpr auto a = folly::constexpr_trunc(-3.5);
    EXPECT_EQ(-3.0, a);
  }
  {
    constexpr auto d = lim::digits - 2;
    constexpr auto n = folly::constexpr_pow(2., d) - .5;
    constexpr auto a = folly::constexpr_trunc(n);
    EXPECT_EQ(.5, n - a);
  }
  {
    constexpr auto d = lim::digits - 2;
    constexpr auto n = folly::constexpr_pow(2., d) + .5;
    constexpr auto a = folly::constexpr_trunc(n);
    EXPECT_EQ(.5, n - a);
  }
  {
    constexpr auto d = lim::digits - 1;
    constexpr auto n = folly::constexpr_pow(2., d) - .5;
    constexpr auto a = folly::constexpr_trunc(n);
    EXPECT_EQ(.5, n - a);
  }
  {
    constexpr auto d = lim::digits - 1;
    constexpr auto n = folly::constexpr_pow(2., d) + .5;
    constexpr auto a = folly::constexpr_trunc(n);
    EXPECT_EQ(.0, n - a);
  }
  {
    constexpr auto d = lim::digits;
    constexpr auto n = folly::constexpr_pow(2., d) - .5;
    constexpr auto a = folly::constexpr_trunc(n);
    EXPECT_EQ(.0, n - a);
  }
  {
    constexpr auto d = lim::digits;
    constexpr auto n = folly::constexpr_pow(2., d) + .5;
    constexpr auto a = folly::constexpr_trunc(n);
    EXPECT_EQ(.0, n - a);
  }
  {
    constexpr auto d = lim::digits + 1;
    constexpr auto n = folly::constexpr_pow(2., d) - .5;
    constexpr auto a = folly::constexpr_trunc(n);
    EXPECT_EQ(.0, n - a);
  }
  {
    constexpr auto d = lim::digits + 1;
    constexpr auto n = folly::constexpr_pow(2., d) + .5;
    constexpr auto a = folly::constexpr_trunc(n);
    EXPECT_EQ(.0, n - a);
  }
}

TEST_F(ConstexprMathTest, constexpr_trunc_integral) {
  {
    constexpr auto n = 0u;
    constexpr auto a = folly::constexpr_trunc(n);
    EXPECT_EQ(0u, a);
  }
  {
    constexpr auto n = -1;
    constexpr auto a = folly::constexpr_trunc(n);
    EXPECT_EQ(-1, a);
  }
  {
    constexpr auto n = 100;
    constexpr auto a = folly::constexpr_trunc(n);
    EXPECT_EQ(100, a);
  }
}

TEST_F(ConstexprMathTest, constexpr_round_floating) {
  using lim = std::numeric_limits<double>;

  {
    constexpr auto n = lim::infinity();
    constexpr auto a = folly::constexpr_round(n);
    EXPECT_EQ(n, a);
  }
  {
    constexpr auto n = -lim::infinity();
    constexpr auto a = folly::constexpr_round(n);
    EXPECT_EQ(n, a);
  }
  {
    constexpr auto n = lim::quiet_NaN();
    constexpr auto a = folly::constexpr_round(n);
    EXPECT_TRUE(std::isnan(a));
  }
  {
    constexpr auto n = -lim::quiet_NaN();
    constexpr auto a = folly::constexpr_round(n);
    EXPECT_TRUE(std::isnan(a));
  }
  {
    constexpr auto n = lim::max();
    constexpr auto a = folly::constexpr_round(n);
    EXPECT_EQ(n, a);
  }
  {
    constexpr auto n = -lim::max();
    constexpr auto a = folly::constexpr_round(n);
    EXPECT_EQ(n, a);
  }
  {
    constexpr auto a = folly::constexpr_round(0.);
    EXPECT_EQ(0., a);
    EXPECT_FALSE(std::signbit(a));
  }
  {
    constexpr auto a = folly::constexpr_round(-0.);
    EXPECT_EQ(-0., a);
    EXPECT_TRUE(std::signbit(a));
  }
  {
    constexpr auto a = folly::constexpr_round(0.3);
    EXPECT_EQ(0., a);
    EXPECT_FALSE(std::signbit(a));
  }
  {
    constexpr auto a = folly::constexpr_round(0.5);
    EXPECT_EQ(1., a);
  }
  {
    constexpr auto a = folly::constexpr_round(0.7);
    EXPECT_EQ(1., a);
  }
  {
    constexpr auto a = folly::constexpr_round(-0.3);
    EXPECT_EQ(-0., a);
    EXPECT_TRUE(std::signbit(a));
  }
  {
    constexpr auto a = folly::constexpr_round(-0.5);
    EXPECT_EQ(-1., a);
  }
  {
    constexpr auto a = folly::constexpr_round(-0.7);
    EXPECT_EQ(-1., a);
  }
  {
    constexpr auto a = folly::constexpr_round(7.3);
    EXPECT_EQ(7., a);
  }
  {
    constexpr auto a = folly::constexpr_round(7.5);
    EXPECT_EQ(8., a);
  }
  {
    constexpr auto a = folly::constexpr_round(7.7);
    EXPECT_EQ(8., a);
  }
  {
    constexpr auto a = folly::constexpr_round(-7.3);
    EXPECT_EQ(-7., a);
  }
  {
    constexpr auto a = folly::constexpr_round(-7.5);
    EXPECT_EQ(-8., a);
  }
  {
    constexpr auto a = folly::constexpr_round(-7.7);
    EXPECT_EQ(-8., a);
  }
}

TEST_F(ConstexprMathTest, constexpr_floor) {
  // floating-point one-argument integer
  {
    constexpr auto a = folly::constexpr_floor(size_t(0));
    EXPECT_EQ(size_t(0), a);
  }
  {
    constexpr auto a = folly::constexpr_floor(~size_t(0));
    EXPECT_EQ(~size_t(0), a);
  }
  // floating-point one-argument floating-point
  {
    constexpr auto a = folly::constexpr_floor(0.);
    EXPECT_EQ(0., a);
    EXPECT_FALSE(std::signbit(a));
  }
  {
    constexpr auto a = folly::constexpr_floor(-0.);
    EXPECT_EQ(-0., a);
    EXPECT_TRUE(std::signbit(a));
  }
  {
    constexpr auto a = folly::constexpr_floor(0.5);
    EXPECT_EQ(0., a);
    EXPECT_FALSE(std::signbit(a));
  }
  {
    constexpr auto a = folly::constexpr_floor(-0.5);
    EXPECT_EQ(-1., a);
  }
  {
    constexpr auto a = folly::constexpr_floor(1.);
    EXPECT_EQ(1., a);
  }
  {
    constexpr auto a = folly::constexpr_floor(-1.);
    EXPECT_EQ(-1., a);
  }
  {
    constexpr auto n = std::numeric_limits<double>::max();
    constexpr auto a = folly::constexpr_floor(n);
    EXPECT_EQ(std::numeric_limits<double>::max(), a);
    EXPECT_EQ(std::floor(n), a);
  }
  {
    constexpr auto n = std::numeric_limits<double>::infinity();
    constexpr auto a = folly::constexpr_floor(n);
    EXPECT_EQ(std::numeric_limits<double>::infinity(), a);
    EXPECT_EQ(std::floor(n), a);
  }
  {
    constexpr auto n = std::numeric_limits<double>::quiet_NaN();
    constexpr auto a = folly::constexpr_floor(n);
    EXPECT_TRUE(std::isnan(a));
    EXPECT_TRUE(std::isnan(std::floor(n)));
  }
  for (size_t i = 0; i < std::numeric_limits<double>::max_exponent; ++i) {
    auto n = folly::constexpr_pow(2., i);
    auto a = folly::constexpr_floor(n);
    EXPECT_EQ(n, a);
    EXPECT_EQ(std::floor(n), a);
  }
  for (size_t i = 0; i < std::numeric_limits<double>::max_exponent; ++i) {
    auto n = folly::constexpr_pow(2., i) + 1.;
    auto a = folly::constexpr_floor(n);
    EXPECT_EQ(n, a);
    EXPECT_EQ(std::floor(n), a);
  }
  for (size_t i = 0; i < std::numeric_limits<double>::max_exponent; ++i) {
    auto n = folly::constexpr_pow(2., i);
    auto a = folly::constexpr_floor(n + .5);
    EXPECT_EQ(n, a);
    EXPECT_EQ(std::floor(n), a);
  }
  for (size_t i = 0; i < std::numeric_limits<double>::max_exponent; ++i) {
    auto n = folly::constexpr_pow(2., i);
    auto a = folly::constexpr_floor(n - .5);
    auto e = i < std::numeric_limits<double>::digits ? n - 1. : n;
    EXPECT_EQ(e, a);
    EXPECT_EQ(std::floor(n - .5), e);
    EXPECT_EQ(std::floor(n - .5), a);
  }
}

TEST_F(ConstexprMathTest, constexpr_ceil_integral) {
  {
    constexpr auto a = folly::constexpr_ceil(size_t(0));
    EXPECT_EQ(size_t(0), a);
  }
  {
    constexpr auto a = folly::constexpr_ceil(~size_t(0));
    EXPECT_EQ(~size_t(0), a);
  }
}

TEST_F(ConstexprMathTest, constexpr_ceil_floating) {
  using lim = std::numeric_limits<double>;

  {
    constexpr auto a = folly::constexpr_ceil(0.);
    EXPECT_EQ(0., a);
    EXPECT_FALSE(std::signbit(a));
  }
  {
    constexpr auto a = folly::constexpr_ceil(-0.);
    EXPECT_EQ(-0., a);
    EXPECT_TRUE(std::signbit(a));
  }
  {
    constexpr auto a = folly::constexpr_ceil(0.5);
    EXPECT_EQ(1., a);
  }
  {
    constexpr auto a = folly::constexpr_ceil(-0.5);
    EXPECT_EQ(-0., a);
    EXPECT_TRUE(std::signbit(a));
  }
  {
    constexpr auto a = folly::constexpr_ceil(1.);
    EXPECT_EQ(1., a);
  }
  {
    constexpr auto a = folly::constexpr_ceil(-1.);
    EXPECT_EQ(-1., a);
  }
  {
    constexpr auto n = lim::max();
    constexpr auto a = folly::constexpr_ceil(n);
    EXPECT_EQ(n, a);
    EXPECT_EQ(std::ceil(n), a);
  }
  {
    constexpr auto n = lim::infinity();
    constexpr auto a = folly::constexpr_ceil(n);
    EXPECT_EQ(n, a);
    EXPECT_EQ(std::ceil(n), a);
  }
  {
    constexpr auto n = lim::quiet_NaN();
    constexpr auto a = folly::constexpr_ceil(n);
    EXPECT_TRUE(std::isnan(a));
    EXPECT_TRUE(std::isnan(std::ceil(n)));
  }
  for (size_t i = 0; i < lim::max_exponent; ++i) {
    auto n = folly::constexpr_pow(2., i);
    auto a = folly::constexpr_ceil(n);
    EXPECT_EQ(n, a);
    EXPECT_EQ(std::ceil(n), a);
  }
  for (size_t i = 0; i < lim::max_exponent; ++i) {
    auto n = folly::constexpr_pow(2., i) + 1.;
    auto a = folly::constexpr_ceil(n);
    EXPECT_EQ(n, a);
    EXPECT_EQ(std::ceil(n), a);
  }
  for (size_t i = 0; i < lim::max_exponent; ++i) {
    auto n = folly::constexpr_pow(2., i);
    auto a = folly::constexpr_ceil(n - .5);
    EXPECT_EQ(n, a);
    EXPECT_EQ(std::ceil(n), a);
  }
  for (size_t i = 0; i < lim::max_exponent; ++i) {
    auto n = folly::constexpr_pow(2., i);
    auto a = folly::constexpr_ceil(n + .5);
    auto e = i < lim::digits - 1 ? n + 1. : n;
    EXPECT_EQ(e, a) << i;
    EXPECT_EQ(std::ceil(n + .5), e);
    EXPECT_EQ(std::ceil(n + .5), a);
  }
}

TEST_F(ConstexprMathTest, constexpr_ceil_integral_round) {
  {
    constexpr auto roundable = 20ull;
    constexpr auto round = 6ull;
    constexpr auto rounded = folly::constexpr_ceil(roundable, round);
    EXPECT_EQ(24ull, rounded);
  }
  {
    constexpr auto roundable = -20ll;
    constexpr auto round = 6ll;
    constexpr auto rounded = folly::constexpr_ceil(roundable, round);
    EXPECT_EQ(-18ll, rounded);
  }
  {
    constexpr auto roundable = -20ll;
    constexpr auto round = 0ll;
    constexpr auto rounded = folly::constexpr_ceil(roundable, round);
    EXPECT_EQ(-20ll, rounded);
  }
}

TEST_F(ConstexprMathTest, constexpr_mult_floating) {
  using lim = std::numeric_limits<double>;

  {
    constexpr auto a = folly::constexpr_mult(lim::quiet_NaN(), 1.);
    EXPECT_TRUE(std::isnan(a));
  }
  {
    constexpr auto a = folly::constexpr_mult(1., lim::quiet_NaN());
    EXPECT_TRUE(std::isnan(a));
  }
  {
    constexpr auto a = folly::constexpr_mult(0., lim::infinity());
    EXPECT_TRUE(std::isnan(a));
  }
  {
    constexpr auto a = folly::constexpr_mult(0., -lim::infinity());
    EXPECT_TRUE(std::isnan(a));
  }
  {
    constexpr auto a = folly::constexpr_mult(lim::infinity(), 0.);
    EXPECT_TRUE(std::isnan(a));
  }
  {
    constexpr auto a = folly::constexpr_mult(-lim::infinity(), 0.);
    EXPECT_TRUE(std::isnan(a));
  }
  {
    constexpr auto a = folly::constexpr_mult(1., lim::infinity());
    EXPECT_EQ(lim::infinity(), a);
  }
  {
    constexpr auto a = folly::constexpr_mult(1., -lim::infinity());
    EXPECT_EQ(-lim::infinity(), a);
  }
  {
    constexpr auto a = folly::constexpr_mult(-1., lim::infinity());
    EXPECT_EQ(-lim::infinity(), a);
  }
  {
    constexpr auto a = folly::constexpr_mult(-1., -lim::infinity());
    EXPECT_EQ(lim::infinity(), a);
  }
  {
    constexpr auto a = folly::constexpr_mult(lim::max(), lim::max());
    EXPECT_EQ(lim::infinity(), a);
  }
  {
    constexpr auto a = folly::constexpr_mult(-lim::max(), lim::max());
    EXPECT_EQ(-lim::infinity(), a);
  }
  {
    constexpr auto a = folly::constexpr_mult(lim::max(), -lim::max());
    EXPECT_EQ(-lim::infinity(), a);
  }
  {
    constexpr auto a = folly::constexpr_mult(-lim::max(), -lim::max());
    EXPECT_EQ(lim::infinity(), a);
  }
  {
    constexpr auto a = folly::constexpr_mult(lim::min(), 1.); // NOLINT
    EXPECT_EQ(lim::min(), a); // NOLINT
  }
  {
    constexpr auto a = folly::constexpr_mult(-lim::min(), 1.); // NOLINT
    EXPECT_EQ(-lim::min(), a); // NOLINT
  }
  {
    constexpr auto a = folly::constexpr_mult(lim::min(), -1.); // NOLINT
    EXPECT_EQ(-lim::min(), a); // NOLINT
  }
  {
    constexpr auto a = folly::constexpr_mult(-lim::min(), -1.); // NOLINT
    EXPECT_EQ(lim::min(), a); // NOLINT
  }
  {
    constexpr auto a = folly::constexpr_mult(1., lim::min()); // NOLINT
    EXPECT_EQ(lim::min(), a); // NOLINT
  }
  {
    constexpr auto a = folly::constexpr_mult(-1., lim::min()); // NOLINT
    EXPECT_EQ(-lim::min(), a); // NOLINT
  }
  {
    constexpr auto a = folly::constexpr_mult(1., -lim::min()); // NOLINT
    EXPECT_EQ(-lim::min(), a); // NOLINT
  }
  {
    constexpr auto a = folly::constexpr_mult(-1., -lim::min()); // NOLINT
    EXPECT_EQ(lim::min(), a); // NOLINT
  }
  {
    constexpr auto a = folly::constexpr_mult(lim::denorm_min(), .125);
    EXPECT_EQ(0., a);
  }
  {
    constexpr auto a = folly::constexpr_mult(.125, lim::denorm_min());
    EXPECT_EQ(0., a);
  }
  {
    constexpr auto n = lim::denorm_min();
    constexpr auto a = folly::constexpr_mult(n, n);
    EXPECT_EQ(0., a);
  }
}

TEST_F(ConstexprMathTest, constexpr_exp_integral) {
  using lim = std::numeric_limits<double>;

  {
    constexpr auto n = folly::constexpr_exp(0u); // unsigned
    EXPECT_DOUBLE_EQ(1., n);
  }
  {
    constexpr auto n = folly::constexpr_exp(0); // signed
    EXPECT_DOUBLE_EQ(1., n);
  }
  {
    constexpr auto n = folly::constexpr_exp(1u); // unsigned
    EXPECT_DOUBLE_EQ(folly::numbers::e, n);
  }
  {
    constexpr auto n = folly::constexpr_exp(1); // signed
    EXPECT_DOUBLE_EQ(folly::numbers::e, n);
  }
  {
    constexpr auto n = folly::constexpr_exp(13u); // unsigned
    EXPECT_DOUBLE_EQ(folly::constexpr_pow(folly::numbers::e, 13u), n);
  }
  {
    constexpr auto n = folly::constexpr_exp(13); // signed positive
    EXPECT_DOUBLE_EQ(folly::constexpr_pow(folly::numbers::e, 13u), n);
  }
  {
    constexpr auto n = folly::constexpr_exp(-13); // signed negative
    EXPECT_DOUBLE_EQ(1. / folly::constexpr_pow(folly::numbers::e, 13u), n);
  }
  {
    constexpr auto n = folly::constexpr_exp(4097u); // unsigned
    EXPECT_EQ(lim::infinity(), n);
  }
  {
    constexpr auto n = folly::constexpr_exp(4097); // signed positive
    EXPECT_EQ(lim::infinity(), n);
  }
  {
    constexpr auto n = folly::constexpr_exp(-4097); // signed negative
    EXPECT_EQ(0., n);
  }
}

TEST_F(ConstexprMathTest, constexpr_exp_floating) {
  using lim = std::numeric_limits<double>;

  {
    constexpr auto n = lim::quiet_NaN();
    constexpr auto a = folly::constexpr_exp(n);
    EXPECT_NE(a, a);
    EXPECT_NE(std::exp(n), std::exp(n));
  }
  {
    constexpr auto n = -lim::infinity();
    constexpr auto a = folly::constexpr_exp(n);
    EXPECT_EQ(std::exp(n), a);
    EXPECT_EQ(+0., a);
  }
  {
    constexpr auto n = +lim::infinity();
    constexpr auto a = folly::constexpr_exp(n);
    EXPECT_EQ(std::exp(n), a);
    EXPECT_EQ(n, a);
  }
  {
    constexpr auto n = lim::lowest();
    constexpr auto a = folly::constexpr_exp(n);
    EXPECT_EQ(std::exp(n), a);
    EXPECT_EQ(+0., a);
  }
  {
    constexpr auto n = lim::max();
    constexpr auto a = folly::constexpr_exp(n);
    EXPECT_EQ(std::exp(n), a);
    EXPECT_EQ(std::numeric_limits<double>::infinity(), a);
  }
  {
    constexpr auto a = folly::constexpr_exp(+0.);
    EXPECT_EQ(std::exp(+0.), a);
    EXPECT_EQ(1., a);
  }
  {
    constexpr auto a = folly::constexpr_exp(-0.);
    EXPECT_EQ(std::exp(-0.), a);
    EXPECT_EQ(1., a);
  }
  {
    constexpr auto n = +lim::min(); // NOLINT
    constexpr auto a = folly::constexpr_exp(n);
    EXPECT_EQ(std::exp(n), a);
    EXPECT_EQ(+1., a);
  }
  {
    constexpr auto n = -lim::min(); // NOLINT
    constexpr auto a = folly::constexpr_exp(n);
    EXPECT_EQ(std::exp(n), a);
    EXPECT_EQ(+1., a);
  }
  {
    constexpr auto a = folly::constexpr_exp(1.);
    EXPECT_DOUBLE_EQ(std::exp(1.), a);
    EXPECT_DOUBLE_EQ(folly::numbers::e, a);
  }
  {
    constexpr auto a = folly::constexpr_exp(3.3);
    EXPECT_DOUBLE_EQ(std::exp(3.3), a);
  }
  {
    constexpr auto a = folly::constexpr_exp(471.L);
    EXPECT_DOUBLE_EQ(std::exp(471.L), a);
  }
  {
    constexpr auto a = folly::constexpr_exp(600.);
    EXPECT_NE(lim::infinity(), a);
    EXPECT_LT( // too inexact for expect-double-eq
        std::exp(600.) / a - 1,
        16 * lim::epsilon());
  }
  {
    constexpr auto a = folly::constexpr_exp(709.8);
    EXPECT_EQ(lim::infinity(), a);
    EXPECT_EQ(std::exp(709.8), a);
  }
  {
    constexpr auto a = folly::constexpr_exp(-4.1);
    EXPECT_DOUBLE_EQ(std::exp(-4.1), a);
  }
  {
    constexpr auto a = folly::constexpr_exp(-77.1);
    EXPECT_DOUBLE_EQ(std::exp(-77.1), a);
  }
}

TEST_F(ConstexprMathTest, constexpr_log) {
  using lim = std::numeric_limits<double>;

  {
    constexpr auto a = folly::constexpr_log(1.);
    EXPECT_DOUBLE_EQ(0., a);
  }
  {
    constexpr auto a = folly::constexpr_log(folly::numbers::e);
    EXPECT_DOUBLE_EQ(1., a);
  }
  {
    constexpr auto a = folly::constexpr_log(0.);
    EXPECT_DOUBLE_EQ(-lim::infinity(), a);
  }
  {
    constexpr auto n = 15539.1;
    constexpr auto a = folly::constexpr_log(n);
    EXPECT_DOUBLE_EQ(std::log(n), a);
  }
  {
    constexpr auto n = 15539.1;
    constexpr auto a = folly::constexpr_log(n * n * n);
    EXPECT_DOUBLE_EQ(std::log(n * n * n), a);
  }
  {
    constexpr auto n = .00033112;
    constexpr auto a = folly::constexpr_log(n);
    EXPECT_DOUBLE_EQ(std::log(n), a);
  }
  {
    constexpr auto n = lim::epsilon();
    constexpr auto a = folly::constexpr_log(n);
    EXPECT_DOUBLE_EQ(std::log(n), a);
  }
  {
    constexpr auto n = lim::min(); // NOLINT
    constexpr auto a = folly::constexpr_log(n);
    EXPECT_DOUBLE_EQ(std::log(n), a);
  }
  {
    constexpr auto n = lim::min(); // NOLINT
    constexpr auto a = folly::constexpr_log(n);
    EXPECT_DOUBLE_EQ(std::log(n), a);
  }
  {
    constexpr auto n = lim::max();
    constexpr auto a = folly::constexpr_log(n);
    EXPECT_DOUBLE_EQ(std::log(n), a);
  }
  {
    constexpr auto n = lim::max();
    static_assert(n < lim::infinity());
    constexpr auto a = folly::constexpr_log(n);
    EXPECT_DOUBLE_EQ(std::log(n), a);
  }
}

TEST_F(ConstexprMathTest, constexpr_pow_integral_base_integral_exp) {
  {
    constexpr auto a = folly::constexpr_pow(uint64_t(0), 15);
    EXPECT_EQ(0, a);
  }
  {
    constexpr auto a = folly::constexpr_pow(uint64_t(15), 0);
    EXPECT_EQ(1, a);
  }
  {
    constexpr auto a = folly::constexpr_pow(uint64_t(2), 6);
    EXPECT_EQ(64, a);
  }
}

TEST_F(ConstexprMathTest, constexpr_pow_floating_base_integral_exp) {
  using lim = std::numeric_limits<double>;

  {
    constexpr auto a = folly::constexpr_pow(2., 0u);
    EXPECT_EQ(1., a);
  }
  {
    constexpr auto a = folly::constexpr_pow(2., 1u);
    EXPECT_EQ(2., a);
  }
  {
    constexpr auto a = folly::constexpr_pow(2., 7u);
    EXPECT_EQ(128., a);
  }
  {
    constexpr auto a = folly::constexpr_pow(2., 12345u);
    EXPECT_EQ(lim::infinity(), a);
  }
  {
    constexpr auto a = folly::constexpr_pow(+0., 7u);
    EXPECT_EQ(+0., a);
    EXPECT_FALSE(std::signbit(a));
  }
  {
    constexpr auto a = folly::constexpr_pow(-0., 7u);
    EXPECT_EQ(-0., a);
    EXPECT_TRUE(std::signbit(a));
  }
  {
    constexpr auto a = folly::constexpr_pow(-0., 8u);
    EXPECT_EQ(+0., a);
    EXPECT_FALSE(std::signbit(a));
  }
  {
    constexpr auto a = folly::constexpr_pow(lim::infinity(), 7u);
    EXPECT_EQ(lim::infinity(), a);
  }
  {
    constexpr auto a = folly::constexpr_pow(-lim::infinity(), 7u);
    EXPECT_EQ(-lim::infinity(), a);
  }
  {
    constexpr auto a = folly::constexpr_pow(lim::infinity(), 8u);
    EXPECT_EQ(lim::infinity(), a);
  }
  {
    constexpr auto a = folly::constexpr_pow(-lim::infinity(), 8u);
    EXPECT_EQ(lim::infinity(), a);
  }
  {
    constexpr auto a = folly::constexpr_pow(lim::quiet_NaN(), 0u);
    EXPECT_EQ(1., a);
  }
  {
    constexpr auto a = folly::constexpr_pow(-lim::quiet_NaN(), 0u);
    EXPECT_EQ(1., a);
  }
  {
    constexpr auto a = folly::constexpr_pow(lim::quiet_NaN(), 8u);
    EXPECT_TRUE(std::isnan(a));
  }
  {
    constexpr auto a = folly::constexpr_pow(-lim::quiet_NaN(), 8u);
    EXPECT_TRUE(std::isnan(a));
  }
}

TEST_F(ConstexprMathTest, constexpr_pow_floating_base_floating_exp) {
  using lim = std::numeric_limits<double>;

  {
    constexpr auto a = folly::constexpr_pow(1., 1.);
    EXPECT_DOUBLE_EQ(1., a);
  }
  {
    constexpr auto a = folly::constexpr_pow(1., 7.);
    EXPECT_DOUBLE_EQ(1., a);
  }
  {
    constexpr auto a = folly::constexpr_pow(0., 7.);
    EXPECT_DOUBLE_EQ(0., a);
  }
  {
    constexpr auto a = folly::constexpr_pow(7., 0.);
    EXPECT_DOUBLE_EQ(1., a);
  }
  {
    constexpr auto a = folly::constexpr_pow(7., 1.);
    EXPECT_DOUBLE_EQ(7., a);
  }
  {
    constexpr auto a = folly::constexpr_pow(24.331, 6.922);
    EXPECT_DOUBLE_EQ(std::pow(24.331, 6.922), a);
  }
  {
    constexpr auto a = folly::constexpr_pow(24.331, -6.922);
    EXPECT_DOUBLE_EQ(std::pow(24.331, -6.922), a);
  }
  {
    constexpr auto a = folly::constexpr_pow(0., 0.);
    EXPECT_EQ(1., a);
  }
  {
    constexpr auto a = folly::constexpr_pow(1., 0.);
    EXPECT_EQ(1., a);
  }
  {
    constexpr auto a = folly::constexpr_pow(-1., 0.);
    EXPECT_EQ(1., a);
  }
  {
    constexpr auto a = folly::constexpr_pow(-1., 1.);
    EXPECT_EQ(-1., a);
  }
  {
    constexpr auto a = folly::constexpr_pow(-1., 2.);
    EXPECT_EQ(1., a);
  }
  {
    constexpr auto a = folly::constexpr_pow(lim::infinity(), 0.);
    EXPECT_EQ(1., a);
  }
  {
    constexpr auto a = folly::constexpr_pow(-lim::infinity(), 0.);
    EXPECT_EQ(1., a);
  }
  {
    constexpr auto a = folly::constexpr_pow(lim::quiet_NaN(), 0.);
    EXPECT_EQ(1., a);
  }
  {
    constexpr auto a = folly::constexpr_pow(lim::infinity(), -1.);
    EXPECT_EQ(0., a);
    EXPECT_FALSE(std::signbit(a));
  }
  {
    constexpr auto a = folly::constexpr_pow(lim::infinity(), -9.);
    EXPECT_EQ(0., a);
    EXPECT_FALSE(std::signbit(a));
  }
  {
    constexpr auto a = folly::constexpr_pow(lim::infinity(), -1.5);
    EXPECT_EQ(0., a);
    EXPECT_FALSE(std::signbit(a));
  }
  {
    constexpr auto a = folly::constexpr_pow(lim::infinity(), -9.5);
    EXPECT_EQ(0., a);
    EXPECT_FALSE(std::signbit(a));
  }
  {
    constexpr auto a = folly::constexpr_pow(lim::infinity(), -lim::infinity());
    EXPECT_EQ(0., a);
  }
  {
    constexpr auto a = folly::constexpr_pow(lim::infinity(), 1.);
    EXPECT_EQ(lim::infinity(), a);
  }
  {
    constexpr auto a = folly::constexpr_pow(lim::infinity(), 9.);
    EXPECT_EQ(lim::infinity(), a);
  }
  {
    constexpr auto a = folly::constexpr_pow(lim::infinity(), 1.5);
    EXPECT_EQ(lim::infinity(), a);
  }
  {
    constexpr auto a = folly::constexpr_pow(lim::infinity(), lim::infinity());
    EXPECT_EQ(lim::infinity(), a);
  }
  {
    constexpr auto a = folly::constexpr_pow(lim::infinity(), lim::quiet_NaN());
    EXPECT_TRUE(std::isnan(a));
  }
  {
    constexpr auto a = folly::constexpr_pow(-lim::infinity(), -.5);
    EXPECT_EQ(0., a);
    EXPECT_FALSE(std::signbit(a));
  }
  {
    constexpr auto a = folly::constexpr_pow(-lim::infinity(), -3.);
    EXPECT_EQ(0., a);
    EXPECT_TRUE(std::signbit(a));
  }
  {
    constexpr auto a = folly::constexpr_pow(-lim::infinity(), -6.);
    EXPECT_EQ(0., a);
    EXPECT_FALSE(std::signbit(a));
  }
  {
    constexpr auto a = folly::constexpr_pow(-lim::infinity(), -lim::infinity());
    EXPECT_EQ(0., a);
    EXPECT_FALSE(std::signbit(a));
  }
  {
    constexpr auto a = folly::constexpr_pow(-lim::infinity(), .5);
    EXPECT_EQ(lim::infinity(), a);
  }
  {
    constexpr auto a = folly::constexpr_pow(-lim::infinity(), 3.);
    EXPECT_EQ(-lim::infinity(), a);
  }
  {
    constexpr auto a = folly::constexpr_pow(-lim::infinity(), 6.);
    EXPECT_EQ(lim::infinity(), a);
  }
  {
    constexpr auto a = folly::constexpr_pow(-lim::infinity(), lim::infinity());
    EXPECT_EQ(lim::infinity(), a);
  }
  {
    constexpr auto a = folly::constexpr_pow(-lim::infinity(), lim::quiet_NaN());
    EXPECT_TRUE(std::isnan(a));
  }
  {
    constexpr auto a = folly::constexpr_pow(2., -lim::infinity());
    EXPECT_EQ(0., a);
    EXPECT_FALSE(std::signbit(a));
  }
  {
    constexpr auto a = folly::constexpr_pow(2., lim::infinity());
    EXPECT_EQ(lim::infinity(), a);
  }
  {
    constexpr auto a = folly::constexpr_pow(1., lim::quiet_NaN());
    EXPECT_EQ(1., a);
  }
  {
    constexpr auto a = folly::constexpr_pow(1., -lim::infinity());
    EXPECT_EQ(1., a);
  }
  {
    constexpr auto a = folly::constexpr_pow(1., lim::lowest());
    EXPECT_EQ(1., a);
  }
  {
    constexpr auto a = folly::constexpr_pow(1., lim::min()); // NOLINT
    EXPECT_EQ(1., a);
  }
  {
    constexpr auto a = folly::constexpr_pow(1., lim::max());
    EXPECT_EQ(1., a);
  }
  {
    constexpr auto a = folly::constexpr_pow(1., lim::infinity());
    EXPECT_EQ(1., a);
  }
  {
    constexpr auto a = folly::constexpr_pow(1., lim::quiet_NaN());
    EXPECT_EQ(1., a);
  }
  {
    constexpr auto a = folly::constexpr_pow(0., lim::infinity());
    EXPECT_EQ(0., a);
    EXPECT_FALSE(std::signbit(a));
  }
  {
    constexpr auto a = folly::constexpr_pow(0., 1.);
    EXPECT_EQ(0., a);
    EXPECT_FALSE(std::signbit(a));
  }
  {
    constexpr auto a = folly::constexpr_pow(-0., .5);
    EXPECT_EQ(0., a);
    EXPECT_FALSE(std::signbit(a));
  }
  {
    constexpr auto a = folly::constexpr_pow(-0., 1.);
    EXPECT_EQ(0., a);
    EXPECT_TRUE(std::signbit(a));
  }
  {
    constexpr auto a = folly::constexpr_pow(-0., 2.);
    EXPECT_EQ(0., a);
    EXPECT_FALSE(std::signbit(a));
  }
  {
    constexpr auto a = folly::constexpr_pow(0., -lim::infinity());
    EXPECT_EQ(lim::infinity(), a);
  }
  {
    constexpr auto a = folly::constexpr_pow(-1., -lim::infinity());
    EXPECT_EQ(1., a);
  }
  {
    constexpr auto a = folly::constexpr_pow(-1., lim::infinity());
    EXPECT_EQ(1., a);
  }
  {
    constexpr auto a = folly::constexpr_pow(-1., lim::quiet_NaN());
    EXPECT_TRUE(std::isnan(a));
  }
  {
    constexpr auto a = folly::constexpr_pow(-2., lim::infinity());
    EXPECT_EQ(lim::infinity(), a);
  }
  {
    constexpr auto a = folly::constexpr_pow(-2., -lim::infinity());
    EXPECT_EQ(0., a);
    EXPECT_FALSE(std::signbit(a));
  }
}

TEST_F(ConstexprMathTest, constexpr_find_last_set_examples) {
  {
    constexpr auto a = folly::constexpr_find_last_set(int64_t(0));
    EXPECT_EQ(0, a);
  }
  {
    constexpr auto a = folly::constexpr_find_last_set(int64_t(2));
    EXPECT_EQ(2, a);
  }
  {
    constexpr auto a = folly::constexpr_find_last_set(int64_t(4096 + 64));
    EXPECT_EQ(13, a);
  }
}

TEST_F(ConstexprMathTest, constexpr_find_last_set_all_64_adjacents) {
  using type = uint64_t;
  constexpr auto const bits = std::numeric_limits<type>::digits;
  EXPECT_EQ(0, folly::constexpr_find_last_set(type(0)));
  for (size_t i = 0; i < bits; ++i) {
    type const v = type(1) << i;
    EXPECT_EQ(i + 1, folly::constexpr_find_last_set(v));
    EXPECT_EQ(i, folly::constexpr_find_last_set((v - 1)));
  }
}

TEST_F(ConstexprMathTest, constexpr_find_last_set_all_8_reference) {
  using type = char;
  for (size_t i = 0; i < 256u; ++i) {
    auto const expected = folly::findLastSet(type(i));
    EXPECT_EQ(expected, folly::constexpr_find_last_set(type(i)));
  }
}

TEST_F(ConstexprMathTest, constexpr_find_first_set_examples) {
  {
    constexpr auto a = folly::constexpr_find_first_set(int64_t(0));
    EXPECT_EQ(0, a);
  }
  {
    constexpr auto a = folly::constexpr_find_first_set(int64_t(2));
    EXPECT_EQ(2, a);
  }
  {
    constexpr auto a = folly::constexpr_find_first_set(int64_t(4096 + 64));
    EXPECT_EQ(7, a);
  }
}

TEST_F(ConstexprMathTest, constexpr_find_first_set_all_64_adjacent) {
  using type = uint64_t;
  constexpr auto const bits = std::numeric_limits<type>::digits;
  EXPECT_EQ(0, folly::constexpr_find_first_set(type(0)));
  for (size_t i = 0; i < bits; ++i) {
    type const v = (type(1) << (bits - 1)) | (type(1) << i);
    EXPECT_EQ(i + 1, folly::constexpr_find_first_set(v));
  }
}

TEST_F(ConstexprMathTest, constexpr_find_first_set_all_8_reference) {
  using type = char;
  for (size_t i = 0; i < 256u; ++i) {
    auto const expected = folly::findFirstSet(type(i));
    EXPECT_EQ(expected, folly::constexpr_find_first_set(type(i)));
  }
}

constexpr auto kInt64Max = std::numeric_limits<int64_t>::max();
constexpr auto kInt64Min = std::numeric_limits<int64_t>::min();
constexpr auto kUInt64Max = std::numeric_limits<uint64_t>::max();
constexpr auto kInt8Max = std::numeric_limits<int8_t>::max();
constexpr auto kInt8Min = std::numeric_limits<int8_t>::min();
constexpr auto kUInt8Max = std::numeric_limits<uint8_t>::max();

TEST_F(ConstexprMathTest, constexpr_add_overflow_clamped) {
  for (int a = kInt8Min; a <= kInt8Max; a++) {
    for (int b = kInt8Min; b <= kInt8Max; b++) {
      int c = folly::constexpr_clamp(a + b, int(kInt8Min), int(kInt8Max));
      int8_t a1 = a;
      int8_t b1 = b;
      int8_t c1 = folly::constexpr_add_overflow_clamped(a1, b1);
      ASSERT_LE(c1, kInt8Max);
      ASSERT_GE(c1, kInt8Min);
      ASSERT_EQ(c1, c);
    }
  }

  for (int a = 0; a <= kUInt8Max; a++) {
    for (int b = 0; b <= kUInt8Max; b++) {
      int c = folly::constexpr_clamp(a + b, 0, int(kUInt8Max));
      uint8_t a1 = a;
      uint8_t b1 = b;
      uint8_t c1 = folly::constexpr_add_overflow_clamped(a1, b1);
      ASSERT_LE(c1, kUInt8Max);
      ASSERT_GE(c1, 0);
      ASSERT_EQ(c1, c);
    }
  }

  constexpr auto v1 =
      folly::constexpr_add_overflow_clamped(int64_t(23), kInt64Max - 12);
  EXPECT_EQ(kInt64Max, v1);

  constexpr auto v2 =
      folly::constexpr_add_overflow_clamped(int64_t(23), int64_t(12));
  EXPECT_EQ(int64_t(35), v2);

  constexpr auto v3 =
      folly::constexpr_add_overflow_clamped(int64_t(-23), int64_t(12));
  EXPECT_EQ(int64_t(-11), v3);

  constexpr auto v4 =
      folly::constexpr_add_overflow_clamped(int64_t(-23), int64_t(-12));
  EXPECT_EQ(int64_t(-35), v4);

  constexpr auto v5 =
      folly::constexpr_add_overflow_clamped(uint64_t(23), kUInt64Max - 12);
  EXPECT_EQ(kUInt64Max, v5);
}

TEST_F(ConstexprMathTest, constexpr_sub_overflow_clamped) {
  for (int a = kInt8Min; a <= kInt8Max; a++) {
    for (int b = kInt8Min; b <= kInt8Max; b++) {
      int c = folly::constexpr_clamp(a - b, int(kInt8Min), int(kInt8Max));
      int8_t a1 = a;
      int8_t b1 = b;
      int8_t c1 = folly::constexpr_sub_overflow_clamped(a1, b1);
      ASSERT_LE(c1, kInt8Max);
      ASSERT_GE(c1, kInt8Min);
      ASSERT_EQ(c1, c);
    }
  }

  for (int a = 0; a <= kUInt8Max; a++) {
    for (int b = 0; b <= kUInt8Max; b++) {
      int c = folly::constexpr_clamp(a - b, 0, int(kUInt8Max));
      uint8_t a1 = a;
      uint8_t b1 = b;
      uint8_t c1 = folly::constexpr_sub_overflow_clamped(a1, b1);
      ASSERT_LE(c1, kUInt8Max);
      ASSERT_GE(c1, 0);
      ASSERT_EQ(c1, c);
    }
  }

  constexpr auto v1 =
      folly::constexpr_sub_overflow_clamped(int64_t(23), int64_t(12));
  EXPECT_EQ(int64_t(11), v1);

  constexpr auto v2 =
      folly::constexpr_sub_overflow_clamped(int64_t(-23), int64_t(-12));
  EXPECT_EQ(int64_t(-11), v2);

  constexpr auto v3 =
      folly::constexpr_sub_overflow_clamped(int64_t(23), int64_t(-12));
  EXPECT_EQ(int64_t(35), v3);

  constexpr auto v4 =
      folly::constexpr_sub_overflow_clamped(int64_t(23), kInt64Min);
  EXPECT_EQ(kInt64Max, v4);

  constexpr auto v5 =
      folly::constexpr_sub_overflow_clamped(int64_t(-23), kInt64Min);
  EXPECT_EQ(kInt64Max - 22, v5);

  constexpr auto v6 =
      folly::constexpr_sub_overflow_clamped(uint64_t(23), uint64_t(12));
  EXPECT_EQ(uint64_t(11), v6);

  constexpr auto v7 =
      folly::constexpr_sub_overflow_clamped(uint64_t(12), uint64_t(23));
  EXPECT_EQ(uint64_t(0), v7);
}

template <class F, class... Args>
void for_each_argument(F&& f, Args&&... args) {
  [](...) {}((f(std::forward<Args>(args)), 0)...);
}

// float -> integral clamp cast
template <typename Src, typename Dst>
static typename std::enable_if<std::is_floating_point<Src>::value, void>::type
run_constexpr_clamp_cast_test(Src, Dst) {
  constexpr auto kQuietNaN = std::numeric_limits<Src>::quiet_NaN();
  constexpr auto kSignalingNaN = std::numeric_limits<Src>::signaling_NaN();
  constexpr auto kPositiveInf = std::numeric_limits<Src>::infinity();
  constexpr auto kNegativeInf = -kPositiveInf;

  constexpr auto kDstMax = std::numeric_limits<Dst>::max();
  constexpr auto kDstMin = std::numeric_limits<Dst>::min();

  // Check f != f trick for identifying NaN.
  EXPECT_TRUE(kQuietNaN != kQuietNaN);
  EXPECT_TRUE(kSignalingNaN != kSignalingNaN);
  EXPECT_FALSE(kPositiveInf != kPositiveInf);
  EXPECT_FALSE(kNegativeInf != kNegativeInf);

  // NaN -> 0
  EXPECT_EQ(0, folly::constexpr_clamp_cast<Dst>(kQuietNaN));
  EXPECT_EQ(0, folly::constexpr_clamp_cast<Dst>(kSignalingNaN));

  // Inf -> Max/Min
  EXPECT_EQ(kDstMax, folly::constexpr_clamp_cast<Dst>(kPositiveInf));
  EXPECT_EQ(kDstMin, folly::constexpr_clamp_cast<Dst>(kNegativeInf));

  // barely out of range -> Max/Min
  EXPECT_EQ(kDstMax, folly::constexpr_clamp_cast<Dst>(Src(kDstMax) * 1.001));
  EXPECT_EQ(kDstMin, folly::constexpr_clamp_cast<Dst>(Src(kDstMin) * 1.001));

  // value in range -> same value
  EXPECT_EQ(Dst(0), folly::constexpr_clamp_cast<Dst>(Src(0)));
  EXPECT_EQ(Dst(123), folly::constexpr_clamp_cast<Dst>(Src(123)));
}

// integral -> integral clamp cast
template <typename Src, typename Dst>
static typename std::enable_if<std::is_integral<Src>::value, void>::type
run_constexpr_clamp_cast_test(Src, Dst) {
  constexpr auto kSrcMax = std::numeric_limits<Src>::max();
  constexpr auto kSrcMin = std::numeric_limits<Src>::min();

  constexpr auto kDstMax = std::numeric_limits<Dst>::max();
  constexpr auto kDstMin = std::numeric_limits<Dst>::min();

  // value in range -> same value
  EXPECT_EQ(Dst(0), folly::constexpr_clamp_cast<Dst>(Src(0)));
  EXPECT_EQ(Dst(123), folly::constexpr_clamp_cast<Dst>(Src(123)));

  // int -> uint
  if (std::is_signed<Src>::value && std::is_unsigned<Dst>::value) {
    EXPECT_EQ(Dst(0), folly::constexpr_clamp_cast<Dst>(Src(-123)));
  }

  if (sizeof(Src) > sizeof(Dst)) {
    // range clamping
    EXPECT_EQ(kDstMax, folly::constexpr_clamp_cast<Dst>(kSrcMax));

    // int -> int
    if (std::is_signed<Src>::value && std::is_signed<Dst>::value) {
      EXPECT_EQ(kDstMin, folly::constexpr_clamp_cast<Dst>(kSrcMin));
    }

  } else if (
      std::is_unsigned<Src>::value && std::is_signed<Dst>::value &&
      sizeof(Src) == sizeof(Dst)) {
    // uint -> int, same size
    EXPECT_EQ(kDstMax, folly::constexpr_clamp_cast<Dst>(kSrcMax));
  }
}

TEST_F(ConstexprMathTest, constexpr_clamp_cast) {
  for_each_argument(
      [](auto dst) {
        for_each_argument(
            [&](auto src) { run_constexpr_clamp_cast_test(src, dst); },
            // source types
            float(),
            double(),
            (long double)(0),
            int8_t(),
            uint8_t(),
            int16_t(),
            uint16_t(),
            int32_t(),
            uint32_t(),
            int64_t(),
            uint64_t());
      },
      // dst types
      int8_t(),
      uint8_t(),
      int16_t(),
      uint16_t(),
      int32_t(),
      uint32_t(),
      int64_t(),
      uint64_t());
}
