/**
 * test_multshiftround_shiftround_run.cpp
 * Unit tests for the multshiftround and shiftround functions that
 * evaluate the shift argument at runtime. These functions are tested
 * separately from those that require the shift argument at compile
 * time because they can be conditionally compiled to use rounding masks
 * based on array lookups (ARRAY_MASKS) or shift operations (COMPUTED_MASKS).
 * Consequently, this file will be compiled into two executables, one
 * to test each type of rounding mask.
 *
 * Full coverage is provided on the num and shift inputs for the
 * int8_t, int16_t, int32_t, uint8_t, uint16_t, and uint32_t types.
 *
 * The coverage of the num input for 64-bit types is only partial in
 * order to acheive a reasonable test time, especially since the
 * extended precision floating point calculations required to test the
 * 64-bit routines are slow.
 * The num input tested with an increment of 2^35-1 across the
 * range of each 64-bit type for about 536,870,912 tests at every shift
 * value.
 *
 * Written in 2019 by Ben Tesch.
 * Originally distributed at https://github.com/slugrustle/numerical_routines
 *
 * To the extent possible under law, the author has dedicated all copyright
 * and related and neighboring rights to this software to the public domain
 * worldwide. This software is distributed without any warranty.
 * The text of the CC0 Public Domain Dedication should be reproduced at the
 * end of this file. If not, see http://creativecommons.org/publicdomain/zero/1.0/
 */

#include <cstdio>
#include <cmath>
#include <limits>
#include <vector>
#include <utility>
#include <thread>
#include <chrono>
#include <mutex>
#include <atomic>
#include "multshiftround_run.hpp"
#include "shiftround_run.hpp"

#ifdef __cplusplus
  extern "C"
  {
#endif
    #include "multshiftround_run.h"
    #include "shiftround_run.h"
#ifdef __cplusplus
  }
#endif

#include <boost/multiprecision/cpp_bin_float.hpp>
#include <boost/math/special_functions/round.hpp>

typedef boost::multiprecision::number<boost::multiprecision::backends::cpp_bin_float<80, boost::multiprecision::backends::digit_base_2, void, boost::int16_t, -16382, 16383>, boost::multiprecision::et_off> cpp_bin_float_80;

/**
 * Setting the mul argument of multshiftround to 1 for various types.
 * This is for testing the shift and round portions of multshiftround.
 * The multiplication operation in multshiftround
 *     type prod = num * mul;
 * is tested separately.
 */
const int8_t   mul_i8 = 1;
const double   dbl_mul_i8 = static_cast<double>(mul_i8);
const int16_t  mul_i16 = 1;
const double   dbl_mul_i16 = static_cast<double>(mul_i16);
const int32_t  mul_i32 = 1;
const double   dbl_mul_i32 = static_cast<double>(mul_i32);
const int64_t  mul_i64 = 1ll;
const cpp_bin_float_80 ldbl_mul_i64(mul_i64);
const uint8_t  mul_u8 = 1u;
const double   dbl_mul_u8 = static_cast<double>(mul_u8);
const uint16_t mul_u16 = 1u;
const double   dbl_mul_u16 = static_cast<double>(mul_u16);
const uint32_t mul_u32 = 1u;
const double   dbl_mul_u32 = static_cast<double>(mul_u32);
const uint64_t mul_u64 = 1ull;
const cpp_bin_float_80 ldbl_mul_u64(mul_u64);

/**
 * There is one atomic bool for each thread, set to true upon
 * thread initiation and set to false as the last computation
 * in the thread.
 * This helps decide when to join a thread and replace it with
 * a new thread.
 * Plain C array because atomics don't have move or copy constructors.
 */
std::atomic<bool> *thread_running;

/**
 * Mutex for stdout when running multithreaded.
 */
std::mutex print_mutex;

/**
 * Test c++ style runtime int32_t multshiftround for num on [-2147483648, 2147483647].
 * shift should range from 0 to 30.
 */
void test_multshiftround_i32_run_cpp(uint8_t shift, size_t thread_index) {
  std::chrono::high_resolution_clock::time_point test_start = std::chrono::high_resolution_clock::now();
  {
    std::lock_guard<std::mutex> print_lock(print_mutex);
    std::printf("testing multshiftround<int32_t>(num, mul, %u)\n", shift);
  }

  if (shift > 30u)
  {
    std::lock_guard<std::mutex> print_lock(print_mutex);
    std::printf("ERROR: multshiftround<int32_t>(num, mul, ?""?): invalid shift value %u\n", shift);
  }
  else
  {
    double dbl_inv_twoexp = 1.0 / static_cast<double>(1ull << shift);
    int32_t num = std::numeric_limits<int32_t>::lowest();
    double dbl_num = static_cast<double>(num);
    while (true)
    {
      int32_t ms_res = multshiftround<int32_t>(num, mul_i32, shift);
      int32_t dbl_res = static_cast<int32_t>(std::round(dbl_num * dbl_mul_i32 * dbl_inv_twoexp));
      if (ms_res != dbl_res) {
        std::lock_guard<std::mutex> print_lock(print_mutex);
        std::printf("ERROR: multshiftround<int32_t>(num, mul, %u): ms_res %i, dbl_res %i, dbl %.16f, num %i, mul %i\n", shift, ms_res, dbl_res, dbl_num * dbl_mul_i32 * dbl_inv_twoexp, num, mul_i32);
      }
      if (num == std::numeric_limits<int32_t>::max()) break;
      num++;
      dbl_num += 1.0;
    }
  }

  std::chrono::high_resolution_clock::time_point test_end = std::chrono::high_resolution_clock::now();
  {
    std::lock_guard<std::mutex> print_lock(print_mutex);
    std::printf("  multshiftround<int32_t>(num, mul, %u) took %" PRIu64 " ms\n", shift, static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(test_end-test_start).count()));
  }

  thread_running[thread_index].store(false);
}

/**
 * Test c style runtime int32_t multshiftround for num on [-2147483648, 2147483647].
 * shift should range from 0 to 30.
 */
void test_multshiftround_i32_run_c(uint8_t shift, size_t thread_index) {
  std::chrono::high_resolution_clock::time_point test_start = std::chrono::high_resolution_clock::now();
  {
    std::lock_guard<std::mutex> print_lock(print_mutex);
    std::printf("testing multshiftround_i32(num, mul, %u)\n", shift);
  }

  if (shift > 30u)
  {
    std::lock_guard<std::mutex> print_lock(print_mutex);
    std::printf("ERROR: multshiftround_i32(num, mul, ?""?): invalid shift value %u\n", shift);
  }
  else
  {
    double dbl_inv_twoexp = 1.0 / static_cast<double>(1ull << shift);
    int32_t num = std::numeric_limits<int32_t>::lowest();
    double dbl_num = static_cast<double>(num);
    while (true)
    {
      int32_t ms_res = multshiftround_i32(num, mul_i32, shift);
      int32_t dbl_res = static_cast<int32_t>(std::round(dbl_num * dbl_mul_i32 * dbl_inv_twoexp));
      if (ms_res != dbl_res) {
        std::lock_guard<std::mutex> print_lock(print_mutex);
        std::printf("ERROR: multshiftround_i32(num, mul, %i): ms_res %i, dbl_res %i, dbl %.16f, num %i, mul %i\n", shift, ms_res, dbl_res, dbl_num * dbl_mul_i32 * dbl_inv_twoexp, num, mul_i32);
      }
      if (num == std::numeric_limits<int32_t>::max()) break;
      num++;
      dbl_num += 1.0;
    }
  }

  std::chrono::high_resolution_clock::time_point test_end = std::chrono::high_resolution_clock::now();
  {
    std::lock_guard<std::mutex> print_lock(print_mutex);
    std::printf("  multshiftround_i32(num, mul, %u) took %" PRIu64 " ms\n", shift, static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(test_end-test_start).count()));
  }

  thread_running[thread_index].store(false);
}

/**
 * Test c++ style runtime int32_t shiftround for num on [-2147483648, 2147483647].
 * shift should range from 0 to 30.
 */
void test_shiftround_i32_run_cpp(uint8_t shift, size_t thread_index) {
  std::chrono::high_resolution_clock::time_point test_start = std::chrono::high_resolution_clock::now();
  {
    std::lock_guard<std::mutex> print_lock(print_mutex);
    std::printf("testing shiftround<int32_t>(num, %u)\n", shift);
  }

  double dbl_inv_twoexp = 1.0 / static_cast<double>(1ull << shift);
  int32_t num = std::numeric_limits<int32_t>::lowest();
  double dbl_num = static_cast<double>(num);
  while (true)
  {
    int32_t s_res = shiftround<int32_t>(num, shift);
    int32_t dbl_res = static_cast<int32_t>(std::round(dbl_num * dbl_inv_twoexp));
    if (s_res != dbl_res) {
      std::lock_guard<std::mutex> print_lock(print_mutex);
      std::printf("ERROR: shiftround<int32_t>(num, %u): s_res %i, dbl_res %i, dbl %.16f, num %i\n", shift, s_res, dbl_res, dbl_num * dbl_inv_twoexp, num);
    }
    if (num == std::numeric_limits<int32_t>::max()) break;
    num++;
    dbl_num += 1.0;
  }

  std::chrono::high_resolution_clock::time_point test_end = std::chrono::high_resolution_clock::now();
  {
    std::lock_guard<std::mutex> print_lock(print_mutex);
    std::printf("  shiftround<int32_t>(mul, %u) took %" PRIu64 " ms\n", shift, static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(test_end-test_start).count()));
  }
  thread_running[thread_index].store(false);
}

/**
 * Test c style runtime int32_t shiftround for num on [-2147483648, 2147483647].
 * shift should range from 0 to 30.
 */
void test_shiftround_i32_run_c(uint8_t shift, size_t thread_index) {
  std::chrono::high_resolution_clock::time_point test_start = std::chrono::high_resolution_clock::now();
  {
    std::lock_guard<std::mutex> print_lock(print_mutex);
    std::printf("testing shiftround_i32(num, %u)\n", shift);
  }

  double dbl_inv_twoexp = 1.0 / static_cast<double>(1ull << shift);
  int32_t num = std::numeric_limits<int32_t>::lowest();
  double dbl_num = static_cast<double>(num);
  while (true)
  {
    int32_t s_res = shiftround_i32(num, shift);
    int32_t dbl_res = static_cast<int32_t>(std::round(dbl_num * dbl_inv_twoexp));
    if (s_res != dbl_res) {
      std::lock_guard<std::mutex> print_lock(print_mutex);
      std::printf("ERROR: shiftround_i32(num, %u): s_res %i, dbl_res %i, dbl %.16f, num %i\n", shift, s_res, dbl_res, dbl_num * dbl_inv_twoexp, num);
    }
    if (num == std::numeric_limits<int32_t>::max()) break;
    num++;
    dbl_num += 1.0;
  }

  std::chrono::high_resolution_clock::time_point test_end = std::chrono::high_resolution_clock::now();
  {
    std::lock_guard<std::mutex> print_lock(print_mutex);
    std::printf("  shiftround_i32(mul, %u) took %" PRIu64 " ms\n", shift, static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(test_end-test_start).count()));
  }
  thread_running[thread_index].store(false);
}

/**
 * Test c++ style runtime uint32_t multshiftround for num on [0, 4294967295].
 * shift should range from 0 to 31.
 */
void test_multshiftround_u32_run_cpp(uint8_t shift, size_t thread_index) {
  std::chrono::high_resolution_clock::time_point test_start = std::chrono::high_resolution_clock::now();
  {
    std::lock_guard<std::mutex> print_lock(print_mutex);
    std::printf("testing multshiftround<uint32_t>(num, mul, %u)\n", shift);
  }

  double dbl_inv_twoexp = 1.0 / static_cast<double>(1ull << shift);
  uint32_t num = std::numeric_limits<uint32_t>::lowest();
  double dbl_num = static_cast<double>(num);
  while (true)
  {
    uint32_t ms_res = multshiftround<uint32_t>(num, mul_u32, shift);
    uint32_t dbl_res = static_cast<uint32_t>(std::round(dbl_num * dbl_mul_u32 * dbl_inv_twoexp));
    if (ms_res != dbl_res) {
      std::lock_guard<std::mutex> print_lock(print_mutex);
      std::printf("ERROR: multshiftround<uint32_t>(num, mul, %u): ms_res %u, dbl_res %u, dbl %.16f, num %u, mul %u\n", shift, ms_res, dbl_res, dbl_num * dbl_mul_u32 * dbl_inv_twoexp, num, mul_u32);
    }
    if (num == std::numeric_limits<uint32_t>::max()) break;
    num++;
    dbl_num += 1.0;
  }

  std::chrono::high_resolution_clock::time_point test_end = std::chrono::high_resolution_clock::now();
  {
    std::lock_guard<std::mutex> print_lock(print_mutex);
    std::printf("  multshiftround<uint32_t>(num, mul, %u) took %" PRIu64 " ms\n", shift, static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(test_end-test_start).count()));
  }

  thread_running[thread_index].store(false);
}

/**
 * Test c style runtime uint32_t multshiftround for num on [0, 4294967295].
 * shift should range from 0 to 31.
 */
void test_multshiftround_u32_run_c(uint8_t shift, size_t thread_index) {
  std::chrono::high_resolution_clock::time_point test_start = std::chrono::high_resolution_clock::now();
  {
    std::lock_guard<std::mutex> print_lock(print_mutex);
    std::printf("testing multshiftround_u32(num, mul, %u)\n", shift);
  }

  double dbl_inv_twoexp = 1.0 / static_cast<double>(1ull << shift);
  uint32_t num = std::numeric_limits<uint32_t>::lowest();
  double dbl_num = static_cast<double>(num);
  while (true)
  {
    uint32_t ms_res = multshiftround_u32(num, mul_u32, shift);
    uint32_t dbl_res = static_cast<uint32_t>(std::round(dbl_num * dbl_mul_u32 * dbl_inv_twoexp));
    if (ms_res != dbl_res) {
      std::lock_guard<std::mutex> print_lock(print_mutex);
      std::printf("ERROR: multshiftround_u32(num, mul, %u): ms_res %u, dbl_res %u, dbl %.16f, num %u, mul %u\n", shift, ms_res, dbl_res, dbl_num * dbl_mul_u32 * dbl_inv_twoexp, num, mul_u32);
    }
    if (num == std::numeric_limits<uint32_t>::max()) break;
    num++;
    dbl_num += 1.0;
  }

  std::chrono::high_resolution_clock::time_point test_end = std::chrono::high_resolution_clock::now();
  {
    std::lock_guard<std::mutex> print_lock(print_mutex);
    std::printf("  multshiftround_u32(num, mul, %u) took %" PRIu64 " ms\n", shift, static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(test_end-test_start).count()));
  }

  thread_running[thread_index].store(false);
}

/**
 * Test c++ style runtime uint32_t shiftround for num on [0, 4294967295].
 * shift should range from 0 to 31.
 */
void test_shiftround_u32_run_cpp(uint8_t shift, size_t thread_index) {
  std::chrono::high_resolution_clock::time_point test_start = std::chrono::high_resolution_clock::now();
  {
    std::lock_guard<std::mutex> print_lock(print_mutex);
    std::printf("testing shiftround<uint32_t>(num, %u)\n", shift);
  }

  double dbl_inv_twoexp = 1.0 / static_cast<double>(1ull << shift);
  uint32_t num = std::numeric_limits<uint32_t>::lowest();
  double dbl_num = static_cast<double>(num);
  while (true)
  {
    uint32_t s_res = shiftround<uint32_t>(num, shift);
    uint32_t dbl_res = static_cast<uint32_t>(std::round(dbl_num * dbl_inv_twoexp));
    if (s_res != dbl_res) {
      std::lock_guard<std::mutex> print_lock(print_mutex);
      std::printf("ERROR: shiftround<uint32_t>(num, %u): s_res %u, dbl_res %u, dbl %.16f, num %u\n", shift, s_res, dbl_res, dbl_num * dbl_inv_twoexp, num);
    }
    if (num == std::numeric_limits<uint32_t>::max()) break;
    num++;
    dbl_num += 1.0;
  }

  std::chrono::high_resolution_clock::time_point test_end = std::chrono::high_resolution_clock::now();
  {
    std::lock_guard<std::mutex> print_lock(print_mutex);
    std::printf("  shiftround<uint32_t>(num, %u) took %" PRIu64 " ms\n", shift, static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(test_end-test_start).count()));
  }

  thread_running[thread_index].store(false);
}

/**
 * Test c style runtime uint32_t shiftround for num on [0, 4294967295].
 * shift should range from 0 to 31.
 */
void test_shiftround_u32_run_c(uint8_t shift, size_t thread_index) {
  std::chrono::high_resolution_clock::time_point test_start = std::chrono::high_resolution_clock::now();
  {
    std::lock_guard<std::mutex> print_lock(print_mutex);
    std::printf("testing shiftround_u32(num, %u)\n", shift);
  }

  double dbl_inv_twoexp = 1.0 / static_cast<double>(1ull << shift);
  uint32_t num = std::numeric_limits<uint32_t>::lowest();
  double dbl_num = static_cast<double>(num);
  while (true)
  {
    uint32_t s_res = shiftround_u32(num, shift);
    uint32_t dbl_res = static_cast<uint32_t>(std::round(dbl_num * dbl_inv_twoexp));
    if (s_res != dbl_res) {
      std::lock_guard<std::mutex> print_lock(print_mutex);
      std::printf("ERROR: shiftround_u32(num, %u): s_res %u, dbl_res %u, dbl %.16f, num %u\n", shift, s_res, dbl_res, dbl_num * dbl_inv_twoexp, num);
    }
    if (num == std::numeric_limits<uint32_t>::max()) break;
    num++;
    dbl_num += 1.0;
  }

  std::chrono::high_resolution_clock::time_point test_end = std::chrono::high_resolution_clock::now();
  {
    std::lock_guard<std::mutex> print_lock(print_mutex);
    std::printf("  shiftround_u32(num, %u) took %" PRIu64 " ms\n", shift, static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(test_end-test_start).count()));
  }

  thread_running[thread_index].store(false);
}

/**
 * Test c++ style runtime int64_t multshiftround for num on
 * [-9223372036854775808, 9223372036854775807]
 * in steps of 34359738367 for approximately 536,870,912 tests.
 * shift should range from 0 to 62.
 */
void test_multshiftround_i64_run_cpp(uint8_t shift, size_t thread_index) {
  std::chrono::high_resolution_clock::time_point test_start = std::chrono::high_resolution_clock::now();
  {
    std::lock_guard<std::mutex> print_lock(print_mutex);
    std::printf("testing multshiftround<int64_t>(num, mul, %u)\n", shift);
  }

  cpp_bin_float_80 ldbl_inv_twoexp = cpp_bin_float_80(1.0) / cpp_bin_float_80(1ull << shift);
  int64_t increment = (1ll << 35) - 1ll;
  cpp_bin_float_80 ldbl_increment(increment);
  int64_t num = std::numeric_limits<int64_t>::lowest();
  cpp_bin_float_80 ldbl_num(num);
  while (true)
  {
    int64_t ms_res = multshiftround<int64_t>(num, mul_i64, shift);
    int64_t dbl_res = boost::math::round(ldbl_num * ldbl_mul_i64 * ldbl_inv_twoexp).convert_to<int64_t>();
    if (ms_res != dbl_res) {
      std::lock_guard<std::mutex> print_lock(print_mutex);
      std::printf("ERROR: multshiftround<int64_t>(num, mul, %u): ms_res %" PRIi64 ", dbl_res %" PRIi64 ", dbl %.16f, num %" PRIi64 ", mul %" PRIi64 "\n", shift, ms_res, dbl_res, (ldbl_num * ldbl_mul_i64 * ldbl_inv_twoexp).convert_to<double>(), num, mul_i64);
    }
    if (num > 0ll && std::numeric_limits<int64_t>::max() - num < increment) break;
    num += increment;
    ldbl_num += ldbl_increment;
  }

  std::chrono::high_resolution_clock::time_point test_end = std::chrono::high_resolution_clock::now();
  {
    std::lock_guard<std::mutex> print_lock(print_mutex);
    std::printf("  multshiftround<int64_t>(num, mul, %u) took %" PRIu64 " ms\n", shift, static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(test_end-test_start).count()));
  }

  thread_running[thread_index].store(false);
}

/**
 * Test c style runtime int64_t multshiftround for num on
 * [-9223372036854775808, 9223372036854775807]
 * in steps of 34359738367 for approximately 536,870,912 tests.
 * shift should range from 0 to 62.
 */
void test_multshiftround_i64_run_c(uint8_t shift, size_t thread_index) {
  std::chrono::high_resolution_clock::time_point test_start = std::chrono::high_resolution_clock::now();
  {
    std::lock_guard<std::mutex> print_lock(print_mutex);
    std::printf("testing multshiftround_i64(num, mul, %u)\n", shift);
  }

  cpp_bin_float_80 ldbl_inv_twoexp = cpp_bin_float_80(1.0) / cpp_bin_float_80(1ull << shift);
  int64_t increment = (1ll << 35) - 1ll;
  cpp_bin_float_80 ldbl_increment(increment);
  int64_t num = std::numeric_limits<int64_t>::lowest();
  cpp_bin_float_80 ldbl_num(num);
  while (true)
  {
    int64_t ms_res = multshiftround_i64(num, mul_i64, shift);
    int64_t dbl_res = boost::math::round(ldbl_num * ldbl_mul_i64 * ldbl_inv_twoexp).convert_to<int64_t>();
    if (ms_res != dbl_res) {
      std::lock_guard<std::mutex> print_lock(print_mutex);
      std::printf("ERROR: multshiftround_i64(num, mul, %u): ms_res %" PRIi64 ", dbl_res %" PRIi64 ", dbl %.16f, num %" PRIi64 ", mul %" PRIi64 "\n", shift, ms_res, dbl_res, (ldbl_num * ldbl_mul_i64 * ldbl_inv_twoexp).convert_to<double>(), num, mul_i64);
    }
    if (num > 0ll && std::numeric_limits<int64_t>::max() - num < increment) break;
    num += increment;
    ldbl_num += ldbl_increment;
  }

  std::chrono::high_resolution_clock::time_point test_end = std::chrono::high_resolution_clock::now();
  {
    std::lock_guard<std::mutex> print_lock(print_mutex);
    std::printf("  multshiftround_i64(num, mul, %u) took %" PRIu64 " ms\n", shift, static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(test_end-test_start).count()));
  }

  thread_running[thread_index].store(false);
}

/**
 * Test c++ style runtime int64_t shiftround for num on
 * [-9223372036854775808, 9223372036854775807]
 * in steps of 34359738367 for approximately 536,870,912 tests.
 * shift should range from 0 to 62.
 */
void test_shiftround_i64_run_cpp(uint8_t shift, size_t thread_index) {
  std::chrono::high_resolution_clock::time_point test_start = std::chrono::high_resolution_clock::now();
  {
    std::lock_guard<std::mutex> print_lock(print_mutex);
    std::printf("testing shiftround<int64_t>(num, %u)\n", shift);
  }

  cpp_bin_float_80 ldbl_inv_twoexp = cpp_bin_float_80(1.0) / cpp_bin_float_80(1ull << shift);
  int64_t increment = (1ll << 35) - 1ll;
  cpp_bin_float_80 ldbl_increment(increment);
  int64_t num = std::numeric_limits<int64_t>::lowest();
  cpp_bin_float_80 ldbl_num(num);
  while (true)
  {
    int64_t ms_res = shiftround<int64_t>(num, shift);
    int64_t dbl_res = boost::math::round(ldbl_num * ldbl_inv_twoexp).convert_to<int64_t>();
    if (ms_res != dbl_res) {
      std::lock_guard<std::mutex> print_lock(print_mutex);
      std::printf("ERROR: shiftround<int64_t>(num, %u): ms_res %" PRIi64 ", dbl_res %" PRIi64 ", dbl %.16f, num %" PRIi64 "\n", shift, ms_res, dbl_res, (ldbl_num * ldbl_inv_twoexp).convert_to<double>(), num);
    }
    if (num > 0ll && std::numeric_limits<int64_t>::max() - num < increment) break;
    num += increment;
    ldbl_num += ldbl_increment;
  }

  std::chrono::high_resolution_clock::time_point test_end = std::chrono::high_resolution_clock::now();
  {
    std::lock_guard<std::mutex> print_lock(print_mutex);
    std::printf("  shiftround<int64_t>(num, %u) took %" PRIu64 " ms\n", shift, static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(test_end-test_start).count()));
  }

  thread_running[thread_index].store(false);
}

/**
 * Test c style runtime int64_t shiftround for num on
 * [-9223372036854775808, 9223372036854775807]
 * in steps of 34359738367 for approximately 536,870,912 tests.
 * shift should range from 0 to 62.
 */
void test_shiftround_i64_run_c(uint8_t shift, size_t thread_index) {
  std::chrono::high_resolution_clock::time_point test_start = std::chrono::high_resolution_clock::now();
  {
    std::lock_guard<std::mutex> print_lock(print_mutex);
    std::printf("testing shiftround<int64_t>(num, %u)\n", shift);
  }

  cpp_bin_float_80 ldbl_inv_twoexp = cpp_bin_float_80(1.0) / cpp_bin_float_80(1ull << shift);
  int64_t increment = (1ll << 35) - 1ll;
  cpp_bin_float_80 ldbl_increment(increment);
  int64_t num = std::numeric_limits<int64_t>::lowest();
  cpp_bin_float_80 ldbl_num(num);
  while (true)
  {
    int64_t ms_res = shiftround_i64(num, shift);
    int64_t dbl_res = boost::math::round(ldbl_num * ldbl_inv_twoexp).convert_to<int64_t>();
    if (ms_res != dbl_res) {
      std::lock_guard<std::mutex> print_lock(print_mutex);
      std::printf("ERROR: shiftround_i64(num, %i): ms_res %" PRIi64 ", dbl_res %" PRIi64 ", dbl %.16f, num %" PRIi64 "\n", shift, ms_res, dbl_res, (ldbl_num * ldbl_inv_twoexp).convert_to<double>(), num);
    }
    if (num > 0ll && std::numeric_limits<int64_t>::max() - num < increment) break;
    num += increment;
    ldbl_num += ldbl_increment;
  }

  std::chrono::high_resolution_clock::time_point test_end = std::chrono::high_resolution_clock::now();
  {
    std::lock_guard<std::mutex> print_lock(print_mutex);
    std::printf("  shiftround_i64(num, %u) took %" PRIu64 " ms\n", shift, static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(test_end-test_start).count()));
  }

  thread_running[thread_index].store(false);
}

/**
 * Test c++ style runtime uint64_t multshiftround for num on
 * [0, 18446744073709551615] in steps of 34359738367 for
 * approximately 536,870,912 tests.
 * shift should range from 0 to 63.
 */
void test_multshiftround_u64_run_cpp(uint8_t shift, size_t thread_index) {
  std::chrono::high_resolution_clock::time_point test_start = std::chrono::high_resolution_clock::now();
  {
    std::lock_guard<std::mutex> print_lock(print_mutex);
    std::printf("testing multshiftround<uint64_t>(num, mul, %u)\n", shift);
  }

  cpp_bin_float_80 ldbl_inv_twoexp = cpp_bin_float_80(1.0) / cpp_bin_float_80(1ull << shift);
  uint64_t increment = (1ll << 35) - 1ll;
  cpp_bin_float_80 ldbl_increment(increment);
  uint64_t num = std::numeric_limits<uint64_t>::lowest();
  cpp_bin_float_80 ldbl_num(num);
  while (true)
  {
    uint64_t ms_res = multshiftround<uint64_t>(num, mul_u64, shift);
    uint64_t dbl_res = boost::math::round(ldbl_num * ldbl_mul_u64 * ldbl_inv_twoexp).convert_to<uint64_t>();
    if (ms_res != dbl_res) {
      std::lock_guard<std::mutex> print_lock(print_mutex);
      std::printf("ERROR: multshiftround<uint64_t>(num, mul, %u): ms_res %" PRIu64 ", dbl_res %" PRIu64 ", dbl %.16f, num %" PRIu64 ", mul %" PRIu64 "\n", shift, ms_res, dbl_res, (ldbl_num * ldbl_mul_u64 * ldbl_inv_twoexp).convert_to<double>(), num, mul_u64);
    }
    if (std::numeric_limits<uint64_t>::max() - num < increment) break;
    num += increment;
    ldbl_num += ldbl_increment;
  }

  std::chrono::high_resolution_clock::time_point test_end = std::chrono::high_resolution_clock::now();
  {
    std::lock_guard<std::mutex> print_lock(print_mutex);
    std::printf("  multshiftround<uint64_t>(num, mul, %u) took %" PRIu64 " ms\n", shift, static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(test_end-test_start).count()));
  }

  thread_running[thread_index].store(false);
}

/**
 * Test c style runtime uint64_t multshiftround for num on
 * [0, 18446744073709551615] in steps of 34359738367 for
 * approximately 536,870,912 tests.
 * shift should range from 0 to 63.
 */
void test_multshiftround_u64_run_c(uint8_t shift, size_t thread_index) {
  std::chrono::high_resolution_clock::time_point test_start = std::chrono::high_resolution_clock::now();
  {
    std::lock_guard<std::mutex> print_lock(print_mutex);
    std::printf("testing multshiftround_u64(num, mul, %u)\n", shift);
  }

  cpp_bin_float_80 ldbl_inv_twoexp = cpp_bin_float_80(1.0) / cpp_bin_float_80(1ull << shift);
  uint64_t increment = (1ll << 35) - 1ll;
  cpp_bin_float_80 ldbl_increment(increment);
  uint64_t num = std::numeric_limits<uint64_t>::lowest();
  cpp_bin_float_80 ldbl_num(num);
  while (true)
  {
    uint64_t ms_res = multshiftround_u64(num, mul_u64, shift);
    uint64_t dbl_res = boost::math::round(ldbl_num * ldbl_mul_u64 * ldbl_inv_twoexp).convert_to<uint64_t>();
    if (ms_res != dbl_res) {
      std::lock_guard<std::mutex> print_lock(print_mutex);
      std::printf("ERROR: multshiftround_u64(num, mul, %u): ms_res %" PRIu64 ", dbl_res %" PRIu64 ", dbl %.16f, num %" PRIu64 ", mul %" PRIu64 "\n", shift, ms_res, dbl_res, (ldbl_num * ldbl_mul_u64 * ldbl_inv_twoexp).convert_to<double>(), num, mul_u64);
    }
    if (std::numeric_limits<uint64_t>::max() - num < increment) break;
    num += increment;
    ldbl_num += ldbl_increment;
  }

  std::chrono::high_resolution_clock::time_point test_end = std::chrono::high_resolution_clock::now();
  {
    std::lock_guard<std::mutex> print_lock(print_mutex);
    std::printf("  multshiftround_u64(num, mul, %u) took %" PRIu64 " ms\n", shift, static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(test_end-test_start).count()));
  }

  thread_running[thread_index].store(false);
}

/**
 * Test c++ style runtime uint64_t shiftround for num on
 * [0, 18446744073709551615] in steps of 34359738367 for
 * approximately 536,870,912 tests.
 * shift should range from 0 to 63.
 */
void test_shiftround_u64_run_cpp(uint8_t shift, size_t thread_index) {
  std::chrono::high_resolution_clock::time_point test_start = std::chrono::high_resolution_clock::now();
  {
    std::lock_guard<std::mutex> print_lock(print_mutex);
    std::printf("testing shiftround<uint64_t>(num, %u)\n", shift);
  }

  cpp_bin_float_80 ldbl_inv_twoexp = cpp_bin_float_80(1.0) / cpp_bin_float_80(1ull << shift);
  uint64_t increment = (1ll << 35) - 1ll;
  cpp_bin_float_80 ldbl_increment(increment);
  uint64_t num = std::numeric_limits<uint64_t>::lowest();
  cpp_bin_float_80 ldbl_num(num);
  while (true)
  {
    uint64_t ms_res = shiftround<uint64_t>(num, shift);
    uint64_t dbl_res = boost::math::round(ldbl_num * ldbl_inv_twoexp).convert_to<uint64_t>();
    if (ms_res != dbl_res) {
      std::lock_guard<std::mutex> print_lock(print_mutex);
      std::printf("ERROR: shiftround<uint64_t>(num, %u): ms_res %" PRIu64 ", dbl_res %" PRIu64 ", dbl %.16f, num %" PRIu64 "\n", shift, ms_res, dbl_res, (ldbl_num * ldbl_inv_twoexp).convert_to<double>(), num);
    }
    if (std::numeric_limits<uint64_t>::max() - num < increment) break;
    num += increment;
    ldbl_num += ldbl_increment;
  }

  std::chrono::high_resolution_clock::time_point test_end = std::chrono::high_resolution_clock::now();
  {
    std::lock_guard<std::mutex> print_lock(print_mutex);
    std::printf("  shiftround<uint64_t>(num, %u) took %" PRIu64 " ms\n", shift, static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(test_end-test_start).count()));
  }

  thread_running[thread_index].store(false);
}

/**
 * Test c style runtime uint64_t shiftround for num on
 * [0, 18446744073709551615] in steps of 34359738367 for
 * approximately 536,870,912 tests.
 * shift should range from 0 to 63.
 */
void test_shiftround_u64_run_c(uint8_t shift, size_t thread_index) {
  std::chrono::high_resolution_clock::time_point test_start = std::chrono::high_resolution_clock::now();
  {
    std::lock_guard<std::mutex> print_lock(print_mutex);
    std::printf("testing shiftround_u64(num, %u)\n", shift);
  }

  cpp_bin_float_80 ldbl_inv_twoexp = cpp_bin_float_80(1.0) / cpp_bin_float_80(1ull << shift);
  uint64_t increment = (1ll << 35) - 1ll;
  cpp_bin_float_80 ldbl_increment(increment);
  uint64_t num = std::numeric_limits<uint64_t>::lowest();
  cpp_bin_float_80 ldbl_num(num);
  while (true)
  {
    uint64_t ms_res = shiftround_u64(num, shift);
    uint64_t dbl_res = boost::math::round(ldbl_num * ldbl_inv_twoexp).convert_to<uint64_t>();
    if (ms_res != dbl_res) {
      std::lock_guard<std::mutex> print_lock(print_mutex);
      std::printf("ERROR: shiftround_u64(num, %u): ms_res %" PRIu64 ", dbl_res %" PRIu64 ", dbl %.16f, num %" PRIu64 "\n", shift, ms_res, dbl_res, (ldbl_num * ldbl_inv_twoexp).convert_to<double>(), num);
    }
    if (std::numeric_limits<uint64_t>::max() - num < increment) break;
    num += increment;
    ldbl_num += ldbl_increment;
  }

  std::chrono::high_resolution_clock::time_point test_end = std::chrono::high_resolution_clock::now();
  {
    std::lock_guard<std::mutex> print_lock(print_mutex);
    std::printf("  shiftround_u64(num, %u) took %" PRIu64 " ms\n", shift, static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(test_end-test_start).count()));
  }

  thread_running[thread_index].store(false);
}

int main()
{
  /**
   * Result variables for initial quick multiplication and rounding tests.
   */
  int8_t msr_i8;
  uint8_t msr_u8;
  int16_t msr_i16;
  uint16_t msr_u16;
  int32_t msr_i32;
  uint32_t msr_u32;
  int64_t msr_i64;
  uint64_t msr_u64;

  std::printf("\nTesting multiplication operation in multshiftround routines.\n");

  for (uint8_t shift = 0u; shift <= 6u; shift++) {
    uint8_t half_shift = shift >> 1;
    int8_t result = 1;
    int8_t num = static_cast<int8_t>(1) << half_shift;
    if (num < 2) {
      num = 2;
      result = 2;
    }
    int8_t mul = static_cast<int8_t>(1) << (shift - half_shift);

    msr_i8 = multshiftround_i8(num, mul, shift);
    if (msr_i8 != result) std::printf("\nERROR: multshiftround_i8(%i, %i, %u) returned %i. expected %i.\n\n", num, mul, shift, msr_i8, result);

    msr_i8 = multshiftround<int8_t>(num, mul, shift);
    if (msr_i8 != result) std::printf("\nERROR: multshiftround<int8_t>(%i, %i, %u) returned %i. expected %i.\n\n", num, mul, shift, msr_i8, result);
  }

  for (uint8_t shift = 0u; shift <= 7u; shift++) {
    uint8_t half_shift = shift >> 1;
    uint8_t result = 1u;
    uint8_t num = static_cast<uint8_t>(1) << half_shift;
    if (num < 2u) {
      num = 2u;
      result = 2u;
    }
    uint8_t mul = static_cast<uint8_t>(1) << (shift - half_shift);

    msr_u8 = multshiftround_u8(num, mul, shift);
    if (msr_u8 != result) std::printf("\nERROR: multshiftround_u8(%u, %u, %u) returned %u. expected %u.\n\n", num, mul, shift, msr_u8, result);

    msr_u8 = multshiftround<uint8_t>(num, mul, shift);
    if (msr_u8 != result) std::printf("\nERROR: multshiftround<uint8_t>(%u, %u, %u) returned %u. expected %u.\n\n", num, mul, shift, msr_u8, result);
  }

  for (uint8_t shift = 0u; shift <= 14u; shift++) {
    uint8_t half_shift = shift >> 1;
    int16_t result = 1;
    int16_t num = static_cast<int16_t>(1) << half_shift;
    if (num < 2) {
      num = 2;
      result = 2;
    }
    int16_t mul = static_cast<int16_t>(1) << (shift - half_shift);

    msr_i16 = multshiftround_i16(num, mul, shift);
    if (msr_i16 != result) std::printf("\nERROR: multshiftround_i16(%i, %i, %u) returned %i. expected %i.\n\n", num, mul, shift, msr_i16, result);

    msr_i16 = multshiftround<int16_t>(num, mul, shift);
    if (msr_i16 != result) std::printf("\nERROR: multshiftround<int16_t>(%i, %i, %u) returned %i. expected %i.\n\n", num, mul, shift, msr_i16, result);
  }

  for (uint8_t shift = 0u; shift <= 15u; shift++) {
    uint8_t half_shift = shift >> 1;
    uint16_t result = 1u;
    uint16_t num = static_cast<uint16_t>(1) << half_shift;
    if (num < 2u) {
      num = 2u;
      result = 2u;
    }
    uint16_t mul = static_cast<uint16_t>(1) << (shift - half_shift);

    msr_u16 = multshiftround_u16(num, mul, shift);
    if (msr_u16 != result) std::printf("\nERROR: multshiftround_u16(%u, %u, %u) returned %u. expected %u.\n\n", num, mul, shift, msr_u16, result);

    msr_u16 = multshiftround<uint16_t>(num, mul, shift);
    if (msr_u16 != result) std::printf("\nERROR: multshiftround<uint16_t>(%u, %u, %u) returned %u. expected %u.\n\n", num, mul, shift, msr_u16, result);
  }

  for (uint8_t shift = 0u; shift <= 30u; shift++) {
    uint8_t half_shift = shift >> 1;
    int32_t result = 1;
    int32_t num = 1 << half_shift;
    if (num < 2) {
      num = 2;
      result = 2;
    }
    int32_t mul = 1 << (shift - half_shift);

    msr_i32 = multshiftround_i32(num, mul, shift);
    if (msr_i32 != result) std::printf("\nERROR: multshiftround_i32(%i, %i, %u) returned %i. expected %i.\n\n", num, mul, shift, msr_i32, result);

    msr_i32 = multshiftround<int32_t>(num, mul, shift);
    if (msr_i32 != result) std::printf("\nERROR: multshiftround<int32_t>(%i, %i, %u) returned %i. expected %i.\n\n", num, mul, shift, msr_i32, result);
  }

  for (uint8_t shift = 0u; shift <= 31u; shift++) {
    uint8_t half_shift = shift >> 1;
    uint32_t result = 1u;
    uint32_t num = 1u << half_shift;
    if (num < 2u) {
      num = 2u;
      result = 2u;
    }
    uint32_t mul = 1u << (shift - half_shift);

    msr_u32 = multshiftround_u32(num, mul, shift);
    if (msr_u32 != result) std::printf("\nERROR: multshiftround_u32(%u, %u, %u) returned %u. expected %u.\n\n", num, mul, shift, msr_u32, result);

    msr_u32 = multshiftround<uint32_t>(num, mul, shift);
    if (msr_u32 != result) std::printf("\nERROR: multshiftround<uint32_t>(%u, %u, %u) returned %u. expected %u.\n\n", num, mul, shift, msr_u32, result);
  }

  for (uint8_t shift = 0u; shift <= 62u; shift++) {
    uint8_t half_shift = shift >> 1;
    int64_t result = 1ll;
    int64_t num = 1ll << half_shift;
    if (num < 2ll) {
      num = 2ll;
      result = 2ll;
    }
    int64_t mul = 1ll << (shift - half_shift);

    msr_i64 = multshiftround_i64(num, mul, shift);
    if (msr_i64 != result) std::printf("\nERROR: multshiftround_i64(%" PRIi64 ", %" PRIi64 ", %u) returned %" PRIi64 ". expected %" PRIi64 ".\n\n", num, mul, shift, msr_i64, result);

    msr_i64 = multshiftround<int64_t>(num, mul, shift);
    if (msr_i64 != result) std::printf("\nERROR: multshiftround<int64_t>(%" PRIi64 ", %" PRIi64 ", %u) returned %" PRIi64 ". expected %" PRIi64 ".\n\n", num, mul, shift, msr_i64, result);
  }

  for (uint8_t shift = 0u; shift <= 63u; shift++) {
    uint8_t half_shift = shift >> 1;
    uint64_t result = 1ull;
    uint64_t num = 1ull << half_shift;
    if (num < 2ull) {
      num = 2ull;
      result = 2ull;
    }
    uint64_t mul = 1ull << (shift - half_shift);

    msr_u64 = multshiftround_u64(num, mul, shift);
    if (msr_u64 != result) std::printf("\nERROR: multshiftround_u64(%" PRIu64 ", %" PRIu64 ", %u) returned %" PRIu64 ". expected %" PRIu64 ".\n\n", num, mul, shift, msr_u64, result);

    msr_u64 = multshiftround<uint64_t>(num, mul, shift);
    if (msr_u64 != result) std::printf("\nERROR: multshiftround<uint64_t>(%" PRIu64 ", %" PRIu64 ", %u) returned %" PRIu64 ". expected %" PRIu64 ".\n\n", num, mul, shift, msr_u64, result);
  }

  std::printf("Multiplication tests finished.\n\n");

  std::printf("Running quick tests of rounding operation in multshiftround and shiftround routines.\n");

  for (uint8_t shift = 1u; shift <= 6u; shift++) {
    int8_t num = -(1 << (shift-1u));
    msr_i8 = multshiftround_i8(num, 1, shift);
    if (msr_i8 != -1) std::printf("\nERROR: multshiftround_i8(%i, 1, %u) returned %i. expected -1.\n\n", num, shift, msr_i8);
    msr_i8 = multshiftround<int8_t>(num, 1, shift);
    if (msr_i8 != -1) std::printf("\nERROR: multshiftround<int8_t>(%i, 1, %u) returned %i. expected -1.\n\n", num, shift, msr_i8);

    num = -(1 << (shift-1u)) + 1;
    msr_i8 = multshiftround_i8(num, 1, shift);
    if (msr_i8 != 0) std::printf("\nERROR: multshiftround_i8(%i, 1, %u) returned %i. expected 0.\n\n", num, shift, msr_i8);
    msr_i8 = multshiftround<int8_t>(num, 1, shift);
    if (msr_i8 != 0) std::printf("\nERROR: multshiftround<int8_t>(%i, 1, %u) returned %i. expected 0.\n\n", num, shift, msr_i8);

    num = (1 << (shift-1u)) - 1;
    msr_i8 = multshiftround_i8(num, 1, shift);
    if (msr_i8 != 0) std::printf("\nERROR: multshiftround_i8(%i, 1, %u) returned %i. expected 0.\n\n", num, shift, msr_i8);
    msr_i8 = multshiftround<int8_t>(num, 1, shift);
    if (msr_i8 != 0) std::printf("\nERROR: multshiftround<int8_t>(%i, 1, %u) returned %i. expected 0.\n\n", num, shift, msr_i8);

    num = 1 << (shift-1u);
    msr_i8 = multshiftround_i8(num, 1, shift);
    if (msr_i8 != 1) std::printf("\nERROR: multshiftround_i8(%i, 1, %u) returned %i. expected 1.\n\n", num, shift, msr_i8);
    msr_i8 = multshiftround<int8_t>(num, 1, shift);
    if (msr_i8 != 1) std::printf("\nERROR: multshiftround<int8_t>(%i, 1, %u) returned %i. expected 1.\n\n", num, shift, msr_i8);
  }

  for (uint8_t shift = 1u; shift <= 14u; shift++) {
    int16_t num = -(1 << (shift-1u));
    msr_i16 = multshiftround_i16(num, 1, shift);
    if (msr_i16 != -1) std::printf("\nERROR: multshiftround_i16(%i, 1, %u) returned %i. expected -1.\n\n", num, shift, msr_i16);
    msr_i16 = multshiftround<int16_t>(num, 1, shift);
    if (msr_i16 != -1) std::printf("\nERROR: multshiftround<int16_t>(%i, 1, %u) returned %i. expected -1.\n\n", num, shift, msr_i16);

    num = -(1 << (shift-1u)) + 1;
    msr_i16 = multshiftround_i16(num, 1, shift);
    if (msr_i16 != 0) std::printf("\nERROR: multshiftround_i16(%i, 1, %u) returned %i. expected 0.\n\n", num, shift, msr_i16);
    msr_i16 = multshiftround<int16_t>(num, 1, shift);
    if (msr_i16 != 0) std::printf("\nERROR: multshiftround<int16_t>(%i, 1, %u) returned %i. expected 0.\n\n", num, shift, msr_i16);

    num = (1 << (shift-1u)) - 1;
    msr_i16 = multshiftround_i16(num, 1, shift);
    if (msr_i16 != 0) std::printf("\nERROR: multshiftround_i16(%i, 1, %u) returned %i. expected 0.\n\n", num, shift, msr_i16);
    msr_i16 = multshiftround<int16_t>(num, 1, shift);
    if (msr_i16 != 0) std::printf("\nERROR: multshiftround<int16_t>(%i, 1, %u) returned %i. expected 0.\n\n", num, shift, msr_i16);

    num = 1 << (shift-1u);
    msr_i16 = multshiftround_i16(num, 1, shift);
    if (msr_i16 != 1) std::printf("\nERROR: multshiftround_i16(%i, 1, %u) returned %i. expected 1.\n\n", num, shift, msr_i16);
    msr_i16 = multshiftround<int16_t>(num, 1, shift);
    if (msr_i16 != 1) std::printf("\nERROR: multshiftround<int16_t>(%i, 1, %u) returned %i. expected 1.\n\n", num, shift, msr_i16);
  }

  for (uint8_t shift = 1u; shift <= 30u; shift++) {
    int32_t num = -(1 << (shift-1u));
    msr_i32 = multshiftround_i32(num, 1, shift);
    if (msr_i32 != -1) std::printf("\nERROR: multshiftround_i32(%i, 1, %u) returned %i. expected -1.\n\n", num, shift, msr_i32);
    msr_i32 = multshiftround<int32_t>(num, 1, shift);
    if (msr_i32 != -1) std::printf("\nERROR: multshiftround<int32_t>(%i, 1, %u) returned %i. expected -1.\n\n", num, shift, msr_i32);

    num = -(1 << (shift-1u)) + 1;
    msr_i32 = multshiftround_i32(num, 1, shift);
    if (msr_i32 != 0) std::printf("\nERROR: multshiftround_i32(%i, 1, %u) returned %i. expected 0.\n\n", num, shift, msr_i32);
    msr_i32 = multshiftround<int32_t>(num, 1, shift);
    if (msr_i32 != 0) std::printf("\nERROR: multshiftround<int32_t>(%i, 1, %u) returned %i. expected 0.\n\n", num, shift, msr_i32);

    num = (1 << (shift-1u)) - 1;
    msr_i32 = multshiftround_i32(num, 1, shift);
    if (msr_i32 != 0) std::printf("\nERROR: multshiftround_i32(%i, 1, %u) returned %i. expected 0.\n\n", num, shift, msr_i32);
    msr_i32 = multshiftround<int32_t>(num, 1, shift);
    if (msr_i32 != 0) std::printf("\nERROR: multshiftround<int32_t>(%i, 1, %u) returned %i. expected 0.\n\n", num, shift, msr_i32);

    num = 1 << (shift-1u);
    msr_i32 = multshiftround_i32(num, 1, shift);
    if (msr_i32 != 1) std::printf("\nERROR: multshiftround_i32(%i, 1, %u) returned %i. expected 1.\n\n", num, shift, msr_i32);
    msr_i32 = multshiftround<int32_t>(num, 1, shift);
    if (msr_i32 != 1) std::printf("\nERROR: multshiftround<int32_t>(%i, 1, %u) returned %i. expected 1.\n\n", num, shift, msr_i32);
  }

  for (uint8_t shift = 1u; shift <= 62u; shift++) {
    int64_t num = -(1ll << (shift-1u));
    msr_i64 = multshiftround_i64(num, 1ll, shift);
    if (msr_i64 != -1ll) std::printf("\nERROR: multshiftround_i64(%" PRIi64 ", 1, %u) returned %" PRIi64 ". expected -1.\n\n", num, shift, msr_i64);
    msr_i64 = multshiftround<int64_t>(num, 1ll, shift);
    if (msr_i64 != -1ll) std::printf("\nERROR: multshiftround<int64_t>(%" PRIi64 ", 1, %u) returned %" PRIi64 ". expected -1.\n\n", num, shift, msr_i64);

    num = -(1ll << (shift-1u)) + 1ll;
    msr_i64 = multshiftround_i64(num, 1ll, shift);
    if (msr_i64 != 0ll) std::printf("\nERROR: multshiftround_i64(%" PRIi64 ", 1, %u) returned %" PRIi64 ". expected 0.\n\n", num, shift, msr_i64);
    msr_i64 = multshiftround<int64_t>(num, 1ll, shift);
    if (msr_i64 != 0ll) std::printf("\nERROR: multshiftround<int64_t>(%" PRIi64 ", 1, %u) returned %" PRIi64 ". expected 0.\n\n", num, shift, msr_i64);

    num = (1ll << (shift-1u)) - 1ll;
    msr_i64 = multshiftround_i64(num, 1ll, shift);
    if (msr_i64 != 0ll) std::printf("\nERROR: multshiftround_i64(%" PRIi64 ", 1, %u) returned %" PRIi64 ". expected 0.\n\n", num, shift, msr_i64);
    msr_i64 = multshiftround<int64_t>(num, 1ll, shift);
    if (msr_i64 != 0ll) std::printf("\nERROR: multshiftround<int64_t>(%" PRIi64 ", 1, %u) returned %" PRIi64 ". expected 0.\n\n", num, shift, msr_i64);

    num = 1ll << (shift-1u);
    msr_i64 = multshiftround_i64(num, 1ll, shift);
    if (msr_i64 != 1ll) std::printf("\nERROR: multshiftround_i64(%" PRIi64 ", 1, %u) returned %" PRIi64 ". expected 1.\n\n", num, shift, msr_i64);
    msr_i64 = multshiftround<int64_t>(num, 1ll, shift);
    if (msr_i64 != 1ll) std::printf("\nERROR: multshiftround<int64_t>(%" PRIi64 ", 1, %u) returned %" PRIi64 ". expected 1.\n\n", num, shift, msr_i64);
  }

  for (uint8_t shift = 1u; shift <= 7u; shift++) {
    uint8_t num = (1u << (shift-1u)) - 1u;
    msr_u8 = multshiftround_u8(num, 1u, shift);
    if (msr_u8 != 0u) std::printf("\nERROR: multshiftround_u8(%u, 1, %u) returned %u. expected 0.\n\n", num, shift, msr_u8);
    msr_u8 = multshiftround<uint8_t>(num, 1u, shift);
    if (msr_u8 != 0u) std::printf("\nERROR: multshiftround<uint8_t>(%u, 1, %u) returned %u. expected 0.\n\n", num, shift, msr_u8);

    num = 1u << (shift-1u);
    msr_u8 = multshiftround_u8(num, 1u, shift);
    if (msr_u8 != 1u) std::printf("\nERROR: multshiftround_u8(%u, 1, %u) returned %u. expected 1.\n\n", num, shift, msr_u8);
    msr_u8 = multshiftround<uint8_t>(num, 1u, shift);
    if (msr_u8 != 1u) std::printf("\nERROR: multshiftround<uint8_t>(%u, 1, %u) returned %u. expected 1.\n\n", num, shift, msr_u8);
  }

  for (uint8_t shift = 1u; shift <= 15u; shift++) {
    uint16_t num = (1u << (shift-1u)) - 1u;
    msr_u16 = multshiftround_u16(num, 1u, shift);
    if (msr_u16 != 0u) std::printf("\nERROR: multshiftround_u16(%u, 1, %u) returned %u. expected 0.\n\n", num, shift, msr_u16);
    msr_u16 = multshiftround<uint16_t>(num, 1u, shift);
    if (msr_u16 != 0u) std::printf("\nERROR: multshiftround<uint16_t>(%u, 1, %u) returned %u. expected 0.\n\n", num, shift, msr_u16);

    num = 1u << (shift-1u);
    msr_u16 = multshiftround_u16(num, 1u, shift);
    if (msr_u16 != 1u) std::printf("\nERROR: multshiftround_u16(%u, 1, %u) returned %u. expected 1.\n\n", num, shift, msr_u16);
    msr_u16 = multshiftround<uint16_t>(num, 1u, shift);
    if (msr_u16 != 1u) std::printf("\nERROR: multshiftround<uint16_t>(%u, 1, %u) returned %u. expected 1.\n\n", num, shift, msr_u16);
  }

  for (uint8_t shift = 1u; shift <= 31u; shift++) {
    uint32_t num = (1u << (shift-1u)) - 1u;
    msr_u32 = multshiftround_u32(num, 1u, shift);
    if (msr_u32 != 0u) std::printf("\nERROR: multshiftround_u32(%u, 1, %u) returned %u. expected 0.\n\n", num, shift, msr_u32);
    msr_u32 = multshiftround<uint32_t>(num, 1u, shift);
    if (msr_u32 != 0u) std::printf("\nERROR: multshiftround<uint32_t>(%u, 1, %u) returned %u. expected 0.\n\n", num, shift, msr_u32);

    num = 1u << (shift-1u);
    msr_u32 = multshiftround_u32(num, 1u, shift);
    if (msr_u32 != 1u) std::printf("\nERROR: multshiftround_u32(%u, 1, %u) returned %u. expected 1.\n\n", num, shift, msr_u32);
    msr_u32 = multshiftround<uint32_t>(num, 1u, shift);
    if (msr_u32 != 1u) std::printf("\nERROR: multshiftround<uint32_t>(%u, 1, %u) returned %u. expected 1.\n\n", num, shift, msr_u32);
  }

  for (uint8_t shift = 1u; shift <= 63u; shift++) {
    uint64_t num = (1ull << (shift-1u)) - 1ull;
    msr_u64 = multshiftround_u64(num, 1ull, shift);
    if (msr_u64 != 0ull) std::printf("\nERROR: multshiftround_u64(%" PRIu64 ", 1, %u) returned %" PRIu64 ". expected 0.\n\n", num, shift, msr_u64);
    msr_u64 = multshiftround<uint64_t>(num, 1ull, shift);
    if (msr_u64 != 0ull) std::printf("\nERROR: multshiftround<uint64_t>(%" PRIu64 ", 1, %u) returned %" PRIu64 ". expected 0.\n\n", num, shift, msr_u64);

    num = 1ull << (shift-1u);
    msr_u64 = multshiftround_u64(num, 1ull, shift);
    if (msr_u64 != 1ull) std::printf("\nERROR: multshiftround_u64(%" PRIu64 ", 1, %u) returned %" PRIu64 ". expected 1.\n\n", num, shift, msr_u64);
    msr_u64 = multshiftround<uint64_t>(num, 1ull, shift);
    if (msr_u64 != 1ull) std::printf("\nERROR: multshiftround<uint64_t>(%" PRIu64 ", 1, %u) returned %" PRIu64 ". expected 1.\n\n", num, shift, msr_u64);
  }

  for (uint8_t shift = 1u; shift <= 6u; shift++) {
    int8_t num = -(1 << (shift-1u));
    msr_i8 = shiftround_i8(num, shift);
    if (msr_i8 != -1) std::printf("\nERROR: shiftround_i8(%i, %u) returned %i. expected -1.\n\n", num, shift, msr_i8);
    msr_i8 = shiftround<int8_t>(num, shift);
    if (msr_i8 != -1) std::printf("\nERROR: shiftround<int8_t>(%i, %u) returned %i. expected -1.\n\n", num, shift, msr_i8);

    num = -(1 << (shift-1u)) + 1;
    msr_i8 = shiftround_i8(num, shift);
    if (msr_i8 != 0) std::printf("\nERROR: shiftround_i8(%i, %u) returned %i. expected 0.\n\n", num, shift, msr_i8);
    msr_i8 = shiftround<int8_t>(num, shift);
    if (msr_i8 != 0) std::printf("\nERROR: shiftround<int8_t>(%i, %u) returned %i. expected 0.\n\n", num, shift, msr_i8);

    num = (1 << (shift-1u)) - 1;
    msr_i8 = shiftround_i8(num, shift);
    if (msr_i8 != 0) std::printf("\nERROR: shiftround_i8(%i, %u) returned %i. expected 0.\n\n", num, shift, msr_i8);
    msr_i8 = shiftround<int8_t>(num, shift);
    if (msr_i8 != 0) std::printf("\nERROR: shiftround<int8_t>(%i, %u) returned %i. expected 0.\n\n", num, shift, msr_i8);

    num = 1 << (shift-1u);
    msr_i8 = shiftround_i8(num, shift);
    if (msr_i8 != 1) std::printf("\nERROR: shiftround_i8(%i, %u) returned %i. expected 1.\n\n", num, shift, msr_i8);
    msr_i8 = shiftround<int8_t>(num, shift);
    if (msr_i8 != 1) std::printf("\nERROR: shiftround<int8_t>(%i, %u) returned %i. expected 1.\n\n", num, shift, msr_i8);
  }

  for (uint8_t shift = 1u; shift <= 14u; shift++) {
    int16_t num = -(1 << (shift-1u));
    msr_i16 = shiftround_i16(num, shift);
    if (msr_i16 != -1) std::printf("\nERROR: shiftround_i16(%i, %u) returned %i. expected -1.\n\n", num, shift, msr_i16);
    msr_i16 = shiftround<int16_t>(num, shift);
    if (msr_i16 != -1) std::printf("\nERROR: shiftround<int16_t>(%i, %u) returned %i. expected -1.\n\n", num, shift, msr_i16);

    num = -(1 << (shift-1u)) + 1;
    msr_i16 = shiftround_i16(num, shift);
    if (msr_i16 != 0) std::printf("\nERROR: shiftround_i16(%i, %u) returned %i. expected 0.\n\n", num, shift, msr_i16);
    msr_i16 = shiftround<int16_t>(num, shift);
    if (msr_i16 != 0) std::printf("\nERROR: shiftround<int16_t>(%i, %u) returned %i. expected 0.\n\n", num, shift, msr_i16);

    num = (1 << (shift-1u)) - 1;
    msr_i16 = shiftround_i16(num, shift);
    if (msr_i16 != 0) std::printf("\nERROR: shiftround_i16(%i, %u) returned %i. expected 0.\n\n", num, shift, msr_i16);
    msr_i16 = shiftround<int16_t>(num, shift);
    if (msr_i16 != 0) std::printf("\nERROR: shiftround<int16_t>(%i, %u) returned %i. expected 0.\n\n", num, shift, msr_i16);

    num = 1 << (shift-1u);
    msr_i16 = shiftround_i16(num, shift);
    if (msr_i16 != 1) std::printf("\nERROR: shiftround_i16(%i, %u) returned %i. expected 1.\n\n", num, shift, msr_i16);
    msr_i16 = shiftround<int16_t>(num, shift);
    if (msr_i16 != 1) std::printf("\nERROR: shiftround<int16_t>(%i, %u) returned %i. expected 1.\n\n", num, shift, msr_i16);
  }

  for (uint8_t shift = 1u; shift <= 30u; shift++) {
    int32_t num = -(1 << (shift-1u));
    msr_i32 = shiftround_i32(num, shift);
    if (msr_i32 != -1) std::printf("\nERROR: shiftround_i32(%i, %u) returned %i. expected -1.\n\n", num, shift, msr_i32);
    msr_i32 = shiftround<int32_t>(num, shift);
    if (msr_i32 != -1) std::printf("\nERROR: shiftround<int32_t>(%i, %u) returned %i. expected -1.\n\n", num, shift, msr_i32);

    num = -(1 << (shift-1u)) + 1;
    msr_i32 = shiftround_i32(num, shift);
    if (msr_i32 != 0) std::printf("\nERROR: shiftround_i32(%i, %u) returned %i. expected 0.\n\n", num, shift, msr_i32);
    msr_i32 = shiftround<int32_t>(num, shift);
    if (msr_i32 != 0) std::printf("\nERROR: shiftround<int32_t>(%i, %u) returned %i. expected 0.\n\n", num, shift, msr_i32);

    num = (1 << (shift-1u)) - 1;
    msr_i32 = shiftround_i32(num, shift);
    if (msr_i32 != 0) std::printf("\nERROR: shiftround_i32(%i, %u) returned %i. expected 0.\n\n", num, shift, msr_i32);
    msr_i32 = shiftround<int32_t>(num, shift);
    if (msr_i32 != 0) std::printf("\nERROR: shiftround<int32_t>(%i, %u) returned %i. expected 0.\n\n", num, shift, msr_i32);

    num = 1 << (shift-1u);
    msr_i32 = shiftround_i32(num, shift);
    if (msr_i32 != 1) std::printf("\nERROR: shiftround_i32(%i, %u) returned %i. expected 1.\n\n", num, shift, msr_i32);
    msr_i32 = shiftround<int32_t>(num, shift);
    if (msr_i32 != 1) std::printf("\nERROR: shiftround<int32_t>(%i, %u) returned %i. expected 1.\n\n", num, shift, msr_i32);
  }

  for (uint8_t shift = 1u; shift <= 62u; shift++) {
    int64_t num = -(1ll << (shift-1u));
    msr_i64 = shiftround_i64(num, shift);
    if (msr_i64 != -1ll) std::printf("\nERROR: shiftround_i64(%" PRIi64 ", %u) returned %" PRIi64 ". expected -1.\n\n", num, shift, msr_i64);
    msr_i64 = shiftround<int64_t>(num, shift);
    if (msr_i64 != -1ll) std::printf("\nERROR: shiftround<int64_t>(%" PRIi64 ", %u) returned %" PRIi64 ". expected -1.\n\n", num, shift, msr_i64);

    num = -(1ll << (shift-1u)) + 1ll;
    msr_i64 = shiftround_i64(num, shift);
    if (msr_i64 != 0ll) std::printf("\nERROR: shiftround_i64(%" PRIi64 ", %u) returned %" PRIi64 ". expected 0.\n\n", num, shift, msr_i64);
    msr_i64 = shiftround<int64_t>(num, shift);
    if (msr_i64 != 0ll) std::printf("\nERROR: shiftround<int64_t>(%" PRIi64 ", %u) returned %" PRIi64 ". expected 0.\n\n", num, shift, msr_i64);

    num = (1ll << (shift-1u)) - 1ll;
    msr_i64 = shiftround_i64(num, shift);
    if (msr_i64 != 0ll) std::printf("\nERROR: shiftround_i64(%" PRIi64 ", %u) returned %" PRIi64 ". expected 0.\n\n", num, shift, msr_i64);
    msr_i64 = shiftround<int64_t>(num, shift);
    if (msr_i64 != 0ll) std::printf("\nERROR: shiftround<int64_t>(%" PRIi64 ", %u) returned %" PRIi64 ". expected 0.\n\n", num, shift, msr_i64);

    num = 1ll << (shift-1u);
    msr_i64 = shiftround_i64(num, shift);
    if (msr_i64 != 1ll) std::printf("\nERROR: shiftround_i64(%" PRIi64 ", %u) returned %" PRIi64 ". expected 1.\n\n", num, shift, msr_i64);
    msr_i64 = shiftround<int64_t>(num, shift);
    if (msr_i64 != 1ll) std::printf("\nERROR: shiftround<int64_t>(%" PRIi64 ", %u) returned %" PRIi64 ". expected 1.\n\n", num, shift, msr_i64);
  }

  for (uint8_t shift = 1u; shift <= 7u; shift++) {
    uint8_t num = (1u << (shift-1u)) - 1u;
    msr_u8 = shiftround_u8(num, shift);
    if (msr_u8 != 0u) std::printf("\nERROR: shiftround_u8(%u, %u) returned %u. expected 0.\n\n", num, shift, msr_u8);
    msr_u8 = shiftround<uint8_t>(num, shift);
    if (msr_u8 != 0u) std::printf("\nERROR: shiftround<uint8_t>(%u, %u) returned %u. expected 0.\n\n", num, shift, msr_u8);

    num = 1u << (shift-1u);
    msr_u8 = shiftround_u8(num, shift);
    if (msr_u8 != 1u) std::printf("\nERROR: shiftround_u8(%u, %u) returned %u. expected 1.\n\n", num, shift, msr_u8);
    msr_u8 = shiftround<uint8_t>(num, shift);
    if (msr_u8 != 1u) std::printf("\nERROR: shiftround<uint8_t>(%u, %u) returned %u. expected 1.\n\n", num, shift, msr_u8);
  }

  for (uint8_t shift = 1u; shift <= 15u; shift++) {
    uint16_t num = (1u << (shift-1u)) - 1u;
    msr_u16 = shiftround_u16(num, shift);
    if (msr_u16 != 0u) std::printf("\nERROR: shiftround_u16(%u, %u) returned %u. expected 0.\n\n", num, shift, msr_u16);
    msr_u16 = shiftround<uint16_t>(num, shift);
    if (msr_u16 != 0u) std::printf("\nERROR: shiftround<uint16_t>(%u, %u) returned %u. expected 0.\n\n", num, shift, msr_u16);

    num = 1u << (shift-1u);
    msr_u16 = shiftround_u16(num, shift);
    if (msr_u16 != 1u) std::printf("\nERROR: shiftround_u16(%u, %u) returned %u. expected 1.\n\n", num, shift, msr_u16);
    msr_u16 = shiftround<uint16_t>(num, shift);
    if (msr_u16 != 1u) std::printf("\nERROR: shiftround<uint16_t>(%u, %u) returned %u. expected 1.\n\n", num, shift, msr_u16);
  }

  for (uint8_t shift = 1u; shift <= 31u; shift++) {
    uint32_t num = (1u << (shift-1u)) - 1u;
    msr_u32 = shiftround_u32(num, shift);
    if (msr_u32 != 0u) std::printf("\nERROR: shiftround_u32(%u, %u) returned %u. expected 0.\n\n", num, shift, msr_u32);
    msr_u32 = shiftround<uint32_t>(num, shift);
    if (msr_u32 != 0u) std::printf("\nERROR: shiftround<uint32_t>(%u, %u) returned %u. expected 0.\n\n", num, shift, msr_u32);

    num = 1u << (shift-1u);
    msr_u32 = shiftround_u32(num, shift);
    if (msr_u32 != 1u) std::printf("\nERROR: shiftround_u32(%u, %u) returned %u. expected 1.\n\n", num, shift, msr_u32);
    msr_u32 = shiftround<uint32_t>(num, shift);
    if (msr_u32 != 1u) std::printf("\nERROR: shiftround<uint32_t>(%u, %u) returned %u. expected 1.\n\n", num, shift, msr_u32);
  }

  for (uint8_t shift = 1u; shift <= 63u; shift++) {
    uint64_t num = (1ull << (shift-1u)) - 1ull;
    msr_u64 = shiftround_u64(num, shift);
    if (msr_u64 != 0ull) std::printf("\nERROR: shiftround_u64(%" PRIu64 ", %u) returned %" PRIu64 ". expected 0.\n\n", num, shift, msr_u64);
    msr_u64 = shiftround<uint64_t>(num, shift);
    if (msr_u64 != 0ull) std::printf("\nERROR: shiftround<uint64_t>(%" PRIu64 ", %u) returned %" PRIu64 ". expected 0.\n\n", num, shift, msr_u64);

    num = 1ull << (shift-1u);
    msr_u64 = shiftround_u64(num, shift);
    if (msr_u64 != 1ull) std::printf("\nERROR: shiftround_u64(%" PRIu64 ", %u) returned %" PRIu64 ". expected 1.\n\n", num, shift, msr_u64);
    msr_u64 = shiftround<uint64_t>(num, shift);
    if (msr_u64 != 1ull) std::printf("\nERROR: shiftround<uint64_t>(%" PRIu64 ", %u) returned %" PRIu64 ". expected 1.\n\n", num, shift, msr_u64);
  }

  std::printf("Quick tests of rounding operation finished.\n\n");

  /**
   * Test int8_t multshiftround for num on [-128, 127] and shift on [0, 6].
   */
  for (uint8_t shift = 0u; shift <= 6u; shift++)
  {
    std::printf("testing multshiftround<int8_t>(num, mul, %u)\n", shift);
    double dbl_inv_twoexp = 1.0 / static_cast<double>(1ull << shift);
    int8_t num = std::numeric_limits<int8_t>::lowest();
    double dbl_num = static_cast<double>(num);
    while (true)
    {
      int8_t ms_res = multshiftround<int8_t>(num, mul_i8, shift);
      int8_t dbl_res = static_cast<int8_t>(std::round(dbl_num * dbl_mul_i8 * dbl_inv_twoexp));
      if (ms_res != dbl_res) std::printf("ERROR: multshiftround<int8_t>(num, mul, %u): ms_res %i, dbl_res %i, dbl %.16f, num %i, mul %i\n", shift, ms_res, dbl_res, dbl_num * dbl_mul_i8 * dbl_inv_twoexp, num, mul_i8);
      if (num == std::numeric_limits<int8_t>::max()) break;
      num++;
      dbl_num += 1.0;
    }
  }
  std::printf("\n");

  /**
   * Test multshiftround_i8 for num on [-128, 127] and shift on [0, 6].
   */
  for (uint8_t shift = 0u; shift <= 6u; shift++)
  {
    std::printf("testing multshiftround_i8(num, mul, %u)\n", shift);
    double dbl_inv_twoexp = 1.0 / static_cast<double>(1ull << shift);
    int8_t num = std::numeric_limits<int8_t>::lowest();
    double dbl_num = static_cast<double>(num);
    while (true)
    {
      int8_t ms_res = multshiftround_i8(num, mul_i8, shift);
      int8_t dbl_res = static_cast<int8_t>(std::round(dbl_num * dbl_mul_i8 * dbl_inv_twoexp));
      if (ms_res != dbl_res) std::printf("ERROR: multshiftround_i8(num, mul, %u): ms_res %i, dbl_res %i, dbl %.16f, num %i, mul %i\n", shift, ms_res, dbl_res, dbl_num * dbl_mul_i8 * dbl_inv_twoexp, num, mul_i8);
      if (num == std::numeric_limits<int8_t>::max()) break;
      num++;
      dbl_num += 1.0;
    }
  }
  std::printf("\n");

  /**
   * Test int8_t shiftround for num on [-128, 127] and shift on [0, 6].
   */
  for (uint8_t shift = 0u; shift <= 6u; shift++)
  {
    std::printf("testing shiftround<int8_t>(num, %u)\n", shift);
    double dbl_inv_twoexp = 1.0 / static_cast<double>(1ull << shift);
    int8_t num = std::numeric_limits<int8_t>::lowest();
    double dbl_num = static_cast<double>(num);
    while (true)
    {
      int8_t ms_res = shiftround<int8_t>(num, shift);
      int8_t dbl_res = static_cast<int8_t>(std::round(dbl_num * dbl_inv_twoexp));
      if (ms_res != dbl_res) std::printf("ERROR: shiftround<int8_t>(num, %u): ms_res %i, dbl_res %i, dbl %.16f, num %i\n", shift, ms_res, dbl_res, dbl_num * dbl_inv_twoexp, num);
      if (num == std::numeric_limits<int8_t>::max()) break;
      num++;
      dbl_num += 1.0;
    }
  }
  std::printf("\n");

  /**
   * Test shiftround_i8 for num on [-128, 127] and shift on [0, 6].
   */
  for (uint8_t shift = 0u; shift <= 6u; shift++)
  {
    std::printf("testing shiftround_i8(num, %u)\n", shift);
    double dbl_inv_twoexp = 1.0 / static_cast<double>(1ull << shift);
    int8_t num = std::numeric_limits<int8_t>::lowest();
    double dbl_num = static_cast<double>(num);
    while (true)
    {
      int8_t ms_res = shiftround_i8(num, shift);
      int8_t dbl_res = static_cast<int8_t>(std::round(dbl_num * dbl_inv_twoexp));
      if (ms_res != dbl_res) std::printf("ERROR: shiftround_i8(num, %u): ms_res %i, dbl_res %i, dbl %.16f, num %i\n", shift, ms_res, dbl_res, dbl_num * dbl_inv_twoexp, num);
      if (num == std::numeric_limits<int8_t>::max()) break;
      num++;
      dbl_num += 1.0;
    }
  }
  std::printf("\n");

  /**
   * Test int16_t multshiftround for num on [-32768, 32767] and shift on [0, 14].
   */
  for (uint8_t shift = 0u; shift <= 14u; shift++)
  {
    std::printf("testing multshiftround<int16_t>(num, mul, %u)\n", shift);
    double dbl_inv_twoexp = 1.0 / static_cast<double>(1ull << shift);
    int16_t num = std::numeric_limits<int16_t>::lowest();
    double dbl_num = static_cast<double>(num);
    while (true)
    {
      int16_t ms_res = multshiftround<int16_t>(num, mul_i16, shift);
      int16_t dbl_res = static_cast<int16_t>(std::round(dbl_num * dbl_mul_i16 * dbl_inv_twoexp));
      if (ms_res != dbl_res) std::printf("ERROR: multshiftround<int16_t>(num, mul, %u): ms_res %i, dbl_res %i, dbl %.16f, num %i, mul %i\n", shift, ms_res, dbl_res, dbl_num * dbl_mul_i16 * dbl_inv_twoexp, num, mul_i16);
      if (num == std::numeric_limits<int16_t>::max()) break;
      num++;
      dbl_num += 1.0;
    }
  }
  std::printf("\n");

  /**
   * Test multshiftround_i16 for num on [-32768, 32767] and shift on [0, 14].
   */
  for (uint8_t shift = 0u; shift <= 14u; shift++)
  {
    std::printf("testing multshiftround_i16(num, mul, %u)\n", shift);
    double dbl_inv_twoexp = 1.0 / static_cast<double>(1ull << shift);
    int16_t num = std::numeric_limits<int16_t>::lowest();
    double dbl_num = static_cast<double>(num);
    while (true)
    {
      int16_t ms_res = multshiftround_i16(num, mul_i16, shift);
      int16_t dbl_res = static_cast<int16_t>(std::round(dbl_num * dbl_mul_i16 * dbl_inv_twoexp));
      if (ms_res != dbl_res) std::printf("ERROR: multshiftround_i16(num, mul, %u): ms_res %i, dbl_res %i, dbl %.16f, num %i, mul %i\n", shift, ms_res, dbl_res, dbl_num * dbl_mul_i16 * dbl_inv_twoexp, num, mul_i16);
      if (num == std::numeric_limits<int16_t>::max()) break;
      num++;
      dbl_num += 1.0;
    }
  }
  std::printf("\n");

  /**
   * Test int16_t shiftround for num on [-32768, 32767] and shift on [0, 14].
   */
  for (uint8_t shift = 0u; shift <= 14u; shift++)
  {
    std::printf("testing shiftround<int16_t>(num, %u)\n", shift);
    double dbl_inv_twoexp = 1.0 / static_cast<double>(1ull << shift);
    int16_t num = std::numeric_limits<int16_t>::lowest();
    double dbl_num = static_cast<double>(num);
    while (true)
    {
      int16_t s_res = shiftround<int16_t>(num, shift);
      int16_t dbl_res = static_cast<int16_t>(std::round(dbl_num * dbl_inv_twoexp));
      if (s_res != dbl_res) std::printf("ERROR: shiftround<int16_t>(num, %u): s_res %i, dbl_res %i, dbl %.16f, num %i\n", shift, s_res, dbl_res, dbl_num * dbl_inv_twoexp, num);
      if (num == std::numeric_limits<int16_t>::max()) break;
      num++;
      dbl_num += 1.0;
    }
  }
  std::printf("\n");

  /**
   * Test shiftround_i16 for num on [-32768, 32767] and shift on [0, 14].
   */
  for (uint8_t shift = 0u; shift <= 14u; shift++)
  {
    std::printf("testing shiftround_i16(num, %u)\n", shift);
    double dbl_inv_twoexp = 1.0 / static_cast<double>(1ull << shift);
    int16_t num = std::numeric_limits<int16_t>::lowest();
    double dbl_num = static_cast<double>(num);
    while (true)
    {
      int16_t s_res = shiftround_i16(num, shift);
      int16_t dbl_res = static_cast<int16_t>(std::round(dbl_num * dbl_inv_twoexp));
      if (s_res != dbl_res) std::printf("ERROR: shiftround_i16(num, %u): s_res %i, dbl_res %i, dbl %.16f, num %i\n", shift, s_res, dbl_res, dbl_num * dbl_inv_twoexp, num);
      if (num == std::numeric_limits<int16_t>::max()) break;
      num++;
      dbl_num += 1.0;
    }
  }
  std::printf("\n");

  /**
   * Test uint8_t multshiftround for num on [0, 255] and shift on [0, 7].
   */
  for (uint8_t shift = 0u; shift <= 7u; shift++)
  {
    std::printf("testing multshiftround<uint8_t>(num, mul, %u)\n", shift);
    double dbl_inv_twoexp = 1.0 / static_cast<double>(1ull << shift);
    uint8_t num = std::numeric_limits<uint8_t>::lowest();
    double dbl_num = static_cast<double>(num);
    while (true)
    {
      uint8_t ms_res = multshiftround<uint8_t>(num, mul_u8, shift);
      uint8_t dbl_res = static_cast<uint8_t>(std::round(dbl_num * dbl_mul_u8 * dbl_inv_twoexp));
      if (ms_res != dbl_res) std::printf("ERROR: multshiftround<uint8_t>(num, mul, %u): ms_res %u, dbl_res %u, dbl %.16f, num %u, mul %u\n", shift, ms_res, dbl_res, dbl_num * dbl_mul_u8 * dbl_inv_twoexp, num, mul_u8);
      if (num == std::numeric_limits<uint8_t>::max()) break;
      num++;
      dbl_num += 1.0;
    }
  }
  std::printf("\n");

  /**
   * Test multshiftround_u8 for num on [0, 255] and shift on [0, 7].
   */
  for (uint8_t shift = 0u; shift <= 7u; shift++)
  {
    std::printf("testing multshiftround_u8(num, mul, %u)\n", shift);
    double dbl_inv_twoexp = 1.0 / static_cast<double>(1ull << shift);
    uint8_t num = std::numeric_limits<uint8_t>::lowest();
    double dbl_num = static_cast<double>(num);
    while (true)
    {
      uint8_t ms_res = multshiftround_u8(num, mul_u8, shift);
      uint8_t dbl_res = static_cast<uint8_t>(std::round(dbl_num * dbl_mul_u8 * dbl_inv_twoexp));
      if (ms_res != dbl_res) std::printf("ERROR: multshiftround_u8(num, mul, %u): ms_res %u, dbl_res %u, dbl %.16f, num %u, mul %u\n", shift, ms_res, dbl_res, dbl_num * dbl_mul_u8 * dbl_inv_twoexp, num, mul_u8);
      if (num == std::numeric_limits<uint8_t>::max()) break;
      num++;
      dbl_num += 1.0;
    }
  }
  std::printf("\n");

  /**
   * Test uint8_t shiftround for num on [0, 255] and shift on [0, 7].
   */
  for (uint8_t shift = 0u; shift <= 7u; shift++)
  {
    std::printf("testing shiftround<uint8_t>(num, %u)\n", shift);
    double dbl_inv_twoexp = 1.0 / static_cast<double>(1ull << shift);
    uint8_t num = std::numeric_limits<uint8_t>::lowest();
    double dbl_num = static_cast<double>(num);
    while (true)
    {
      uint8_t ms_res = shiftround<uint8_t>(num, shift);
      uint8_t dbl_res = static_cast<uint8_t>(std::round(dbl_num * dbl_inv_twoexp));
      if (ms_res != dbl_res) std::printf("ERROR: shiftround<uint8_t>(num, %u): ms_res %u, dbl_res %u, dbl %.16f, num %u\n", shift, ms_res, dbl_res, dbl_num * dbl_inv_twoexp, num);
      if (num == std::numeric_limits<uint8_t>::max()) break;
      num++;
      dbl_num += 1.0;
    }
  }
  std::printf("\n");

  /**
   * Test shiftround_u8 for num on [0, 255] and shift on [0, 7].
   */
  for (uint8_t shift = 0u; shift <= 7u; shift++)
  {
    std::printf("testing shiftround_u8(num, %u)\n", shift);
    double dbl_inv_twoexp = 1.0 / static_cast<double>(1ull << shift);
    uint8_t num = std::numeric_limits<uint8_t>::lowest();
    double dbl_num = static_cast<double>(num);
    while (true)
    {
      uint8_t ms_res = shiftround_u8(num, shift);
      uint8_t dbl_res = static_cast<uint8_t>(std::round(dbl_num * dbl_inv_twoexp));
      if (ms_res != dbl_res) std::printf("ERROR: shiftround_u8(num, %u): ms_res %u, dbl_res %u, dbl %.16f, num %u\n", shift, ms_res, dbl_res, dbl_num * dbl_inv_twoexp, num);
      if (num == std::numeric_limits<uint8_t>::max()) break;
      num++;
      dbl_num += 1.0;
    }
  }
  std::printf("\n");

  /**
   * Test uint8_t shiftround for num on [0, 255] and shift on [0, 7].
   */
  for (uint8_t shift = 0u; shift <= 7u; shift++)
  {
    std::printf("testing shiftround<uint8_t>(num, %u)\n", shift);
    double dbl_inv_twoexp = 1.0 / static_cast<double>(1ull << shift);
    uint8_t num = std::numeric_limits<uint8_t>::lowest();
    double dbl_num = static_cast<double>(num);
    while (true)
    {
      uint8_t ms_res = shiftround<uint8_t>(num, shift);
      uint8_t dbl_res = static_cast<uint8_t>(std::round(dbl_num * dbl_inv_twoexp));
      if (ms_res != dbl_res) std::printf("ERROR: shiftround<uint8_t>(num, %u): ms_res %u, dbl_res %u, dbl %.16f, num %u\n", shift, ms_res, dbl_res, dbl_num * dbl_inv_twoexp, num);
      if (num == std::numeric_limits<uint8_t>::max()) break;
      num++;
      dbl_num += 1.0;
    }
  }
  std::printf("\n");

  /**
   * Test shiftround_u8 for num on [0, 255] and shift on [0, 7].
   */
  for (uint8_t shift = 0u; shift <= 7u; shift++)
  {
    std::printf("testing shiftround_u8(num, %u)\n", shift);
    double dbl_inv_twoexp = 1.0 / static_cast<double>(1ull << shift);
    uint8_t num = std::numeric_limits<uint8_t>::lowest();
    double dbl_num = static_cast<double>(num);
    while (true)
    {
      uint8_t ms_res = shiftround_u8(num, shift);
      uint8_t dbl_res = static_cast<uint8_t>(std::round(dbl_num * dbl_inv_twoexp));
      if (ms_res != dbl_res) std::printf("ERROR: shiftround_u8(num, %u): ms_res %u, dbl_res %u, dbl %.16f, num %u\n", shift, ms_res, dbl_res, dbl_num * dbl_inv_twoexp, num);
      if (num == std::numeric_limits<uint8_t>::max()) break;
      num++;
      dbl_num += 1.0;
    }
  }
  std::printf("\n");

  /**
   * Test uint16_t multshiftround for num on [0, 65535] and shift on [0, 15].
   */
  for (uint8_t shift = 0u; shift <= 15u; shift++)
  {
    std::printf("testing multshiftround<uint16_t>(num, mul, %u)\n", shift);
    double dbl_inv_twoexp = 1.0 / static_cast<double>(1ull << shift);
    uint16_t num = std::numeric_limits<uint16_t>::lowest();
    double dbl_num = static_cast<double>(num);
    while (true)
    {
      uint16_t ms_res = multshiftround<uint16_t>(num, mul_u16, shift);
      uint16_t dbl_res = static_cast<uint16_t>(std::round(dbl_num * dbl_mul_u16 * dbl_inv_twoexp));
      if (ms_res != dbl_res) std::printf("ERROR: multshiftround<uint16_t>(num, mul, %u): ms_res %u, dbl_res %u, dbl %.16f, num %u, mul %u\n", shift, ms_res, dbl_res, dbl_num * dbl_mul_u16 * dbl_inv_twoexp, num, mul_u16);
      if (num == std::numeric_limits<uint16_t>::max()) break;
      num++;
      dbl_num += 1.0;
    }
  }
  std::printf("\n");

  /**
   * Test multshiftround_u16 for num on [0, 65535] and shift on [0, 15].
   */
  for (uint8_t shift = 0u; shift <= 15u; shift++)
  {
    std::printf("testing multshiftround_u16(num, mul, %u)\n", shift);
    double dbl_inv_twoexp = 1.0 / static_cast<double>(1ull << shift);
    uint16_t num = std::numeric_limits<uint16_t>::lowest();
    double dbl_num = static_cast<double>(num);
    while (true)
    {
      uint16_t ms_res = multshiftround_u16(num, mul_u16, shift);
      uint16_t dbl_res = static_cast<uint16_t>(std::round(dbl_num * dbl_mul_u16 * dbl_inv_twoexp));
      if (ms_res != dbl_res) std::printf("ERROR: multshiftround_u16(num, mul, %u): ms_res %u, dbl_res %u, dbl %.16f, num %u, mul %u\n", shift, ms_res, dbl_res, dbl_num * dbl_mul_u16 * dbl_inv_twoexp, num, mul_u16);
      if (num == std::numeric_limits<uint16_t>::max()) break;
      num++;
      dbl_num += 1.0;
    }
  }
  std::printf("\n");

  /**
   * Test uint16_t shiftround for num on [0, 65535] and shift on [0, 15].
   */
  for (uint8_t shift = 0u; shift <= 15u; shift++)
  {
    std::printf("testing shiftround<uint16_t>(num, %u)\n", shift);
    double dbl_inv_twoexp = 1.0 / static_cast<double>(1ull << shift);
    uint16_t num = std::numeric_limits<uint16_t>::lowest();
    double dbl_num = static_cast<double>(num);
    while (true)
    {
      uint16_t s_res = shiftround<uint16_t>(num, shift);
      uint16_t dbl_res = static_cast<uint16_t>(std::round(dbl_num * dbl_inv_twoexp));
      if (s_res != dbl_res) std::printf("ERROR: shiftround<uint16_t>(num, %u): ms_res %u, dbl_res %u, dbl %.16f, num %u\n", shift, s_res, dbl_res, dbl_num * dbl_inv_twoexp, num);
      if (num == std::numeric_limits<uint16_t>::max()) break;
      num++;
      dbl_num += 1.0;
    }
  }
  std::printf("\n");

  /**
   * Test shiftround_u16 for num on [0, 65535] and shift on [0, 15].
   */
  for (uint8_t shift = 0u; shift <= 15u; shift++)
  {
    std::printf("testing shiftround_u16(num, %u)\n", shift);
    double dbl_inv_twoexp = 1.0 / static_cast<double>(1ull << shift);
    uint16_t num = std::numeric_limits<uint16_t>::lowest();
    double dbl_num = static_cast<double>(num);
    while (true)
    {
      uint16_t s_res = shiftround_u16(num, shift);
      uint16_t dbl_res = static_cast<uint16_t>(std::round(dbl_num * dbl_inv_twoexp));
      if (s_res != dbl_res) std::printf("ERROR: shiftround_u16(num, %u): ms_res %u, dbl_res %u, dbl %.16f, num %u\n", shift, s_res, dbl_res, dbl_num * dbl_inv_twoexp, num);
      if (num == std::numeric_limits<uint16_t>::max()) break;
      num++;
      dbl_num += 1.0;
    }
  }
  std::printf("\n");

  /**
   * vTests stores the list of tests to run multithreaded.
   * The first pair element function pointer should point to one of the
   * test_...(uint8_t shift, size_t thread_index) functions.
   * The second pair element uint8_t is the shift argument value to use
   * when running the test.
   */
  std::vector<std::pair<void (*)(uint8_t, size_t), uint8_t> > vTests;

  /**
   * Queue all the tests that will be run multithreaded.
   */
  for (uint8_t shift = 0u; shift <= 63u; shift++)
    vTests.push_back(std::make_pair(test_shiftround_u64_run_cpp, shift));

  for (uint8_t shift = 0u; shift <= 63u; shift++)
    vTests.push_back(std::make_pair(test_shiftround_u64_run_c, shift));

  for (uint8_t shift = 0u; shift <= 63u; shift++)
    vTests.push_back(std::make_pair(test_multshiftround_u64_run_cpp, shift));

  for (uint8_t shift = 0u; shift <= 63u; shift++)
    vTests.push_back(std::make_pair(test_multshiftround_u64_run_c, shift));

  for (uint8_t shift = 0u; shift <= 62u; shift++)
    vTests.push_back(std::make_pair(test_shiftround_i64_run_cpp, shift));

  for (uint8_t shift = 0u; shift <= 62u; shift++)
    vTests.push_back(std::make_pair(test_shiftround_i64_run_c, shift));

  for (uint8_t shift = 0u; shift <= 62u; shift++)
    vTests.push_back(std::make_pair(test_multshiftround_i64_run_cpp, shift));

  for (uint8_t shift = 0u; shift <= 62u; shift++)
    vTests.push_back(std::make_pair(test_multshiftround_i64_run_c, shift));

  for (uint8_t shift = 0u; shift <= 31u; shift++)
    vTests.push_back(std::make_pair(test_shiftround_u32_run_cpp, shift));

  for (uint8_t shift = 0u; shift <= 31u; shift++)
    vTests.push_back(std::make_pair(test_shiftround_u32_run_c, shift));

  for (uint8_t shift = 0u; shift <= 31u; shift++)
    vTests.push_back(std::make_pair(test_multshiftround_u32_run_cpp, shift));

  for (uint8_t shift = 0u; shift <= 31u; shift++)
    vTests.push_back(std::make_pair(test_multshiftround_u32_run_c, shift));

  for (uint8_t shift = 0u; shift <= 30u; shift++)
    vTests.push_back(std::make_pair(test_shiftround_i32_run_cpp, shift));

  for (uint8_t shift = 0u; shift <= 30u; shift++)
    vTests.push_back(std::make_pair(test_shiftround_i32_run_c, shift));

  for (uint8_t shift = 0u; shift <= 30u; shift++)
    vTests.push_back(std::make_pair(test_multshiftround_i32_run_cpp, shift));

  for (uint8_t shift = 0u; shift <= 30u; shift++)
    vTests.push_back(std::make_pair(test_multshiftround_i32_run_c, shift));

  /**
   * Use one thread if only one hardware thread is available. Otherwise, use
   * one less than the number of available hardware threads.
   */
  uint32_t nThreads = std::thread::hardware_concurrency();
  if (nThreads <= 2u) nThreads = 1u;
  else nThreads--;

  std::printf("Starting multithreaded tests with %u threads.\n\n", nThreads);

  /**
   * Allocate and initialize the atomic bools for checking
   * when threads are done running tests.
   */
  thread_running = new std::atomic<bool>[nThreads];
  for (uint32_t jThread = 0u; jThread < nThreads; jThread++) {
    thread_running[jThread].store(false);
  }

  /**
   * Start all threads running with some test.
   */
  std::vector<std::thread> vThreads;
  for (uint32_t jThread = 0u; jThread < nThreads && !vTests.empty(); jThread++) {
    thread_running[jThread].store(true);
    vThreads.push_back(std::move(std::thread(vTests.back().first, vTests.back().second, jThread)));
    vTests.pop_back();
  }

  while (!vTests.empty()) {
    /**
     * Replace finished threads with new ones until there are no more
     * tests to run.
     */
    for (size_t jThread = 0ull; jThread < vThreads.size() && !vTests.empty(); jThread++) {
      if (!thread_running[jThread].load() && vThreads.at(jThread).joinable()) {
        vThreads.at(jThread).join();
        thread_running[jThread].store(true);
        vThreads.at(jThread) = std::move(std::thread(vTests.back().first, vTests.back().second, jThread));
        vTests.pop_back();
      }
    }
    /**
     * Sleep so as not to spam the CPU.
     */
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
  }

  /**
   * Wait until all threads are finished.
   */
  bool any_joinable = true;
  while (any_joinable) {
    any_joinable = false;
    for (size_t jThread = 0ull; jThread < vThreads.size(); jThread++) {
      if (vThreads.at(jThread).joinable()) {
        any_joinable = true;
        if (!thread_running[jThread].load())
          vThreads.at(jThread).join();
      }
    }
    /**
     * Sleep so as not to spam the CPU.
     */
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
  }

  delete[] thread_running;

  std::printf("\nFinished running multithreaded code.\n\n");
  std::printf("\n");
  std::printf("Testing succeeded if there are no errors above.\n\n");
  return 0;
}

/*
Creative Commons Legal Code

CC0 1.0 Universal

    CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
    LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
    ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
    INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
    REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
    PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
    THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
    HEREUNDER.

Statement of Purpose

The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").

Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.

For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.

1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:

  i. the right to reproduce, adapt, distribute, perform, display,
     communicate, and translate a Work;
 ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
     likeness depicted in a Work;
 iv. rights protecting against unfair competition in regards to a Work,
     subject to the limitations in paragraph 4(a), below;
  v. rights protecting the extraction, dissemination, use and reuse of data
     in a Work;
 vi. database rights (such as those arising under Directive 96/9/EC of the
     European Parliament and of the Council of 11 March 1996 on the legal
     protection of databases, and under any national implementation
     thereof, including any amended or successor version of such
     directive); and
vii. other similar, equivalent or corresponding rights throughout the
     world based on applicable law or treaty, and any national
     implementations thereof.

2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.

3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.

4. Limitations and Disclaimers.

 a. No trademark or patent rights held by Affirmer are waived, abandoned,
    surrendered, licensed or otherwise affected by this document.
 b. Affirmer offers the Work as-is and makes no representations or
    warranties of any kind concerning the Work, express, implied,
    statutory or otherwise, including without limitation warranties of
    title, merchantability, fitness for a particular purpose, non
    infringement, or the absence of latent or other defects, accuracy, or
    the present or absence of errors, whether or not discoverable, all to
    the greatest extent permissible under applicable law.
 c. Affirmer disclaims responsibility for clearing rights of other persons
    that may apply to the Work or any use thereof, including without
    limitation any person's Copyright and Related Rights in the Work.
    Further, Affirmer disclaims responsibility for obtaining any necessary
    consents, permissions or other rights required for any use of the
    Work.
 d. Affirmer understands and acknowledges that Creative Commons is not a
    party to this document and has no duty or obligation with respect to
    this CC0 or use of the Work.
*/


/*
   This header file defines a set of mathematics utility
   functions. Additionally, other header files which define
   further utility functions such as representation transformations,
   lidar data processing. The user should include this header file
   to benefit from all the utiity function while keeping in mind
   the namespace structure.
 */

#ifndef PI
#define PI 3.14159265359
#endif

#include <cmath>
#include <limits>
#include <vector>
#include <cassert>
#include <iostream>

#include <ros/ros.h>
#include <Eigen/Core>
#include <Eigen/Dense>
#include <Eigen/Geometry>

#include <nav_msgs/Odometry.h>
#include <sensor_msgs/Imu.h>
#include <sensor_msgs/LaserScan.h>
#include <sensor_msgs/PointCloud2.h>

#include <opencv2/opencv.hpp>

#include "trans_utils.hh"
#include "laser_utils.hh"
#include "color_utils.hh"

#include <armadillo>

using namespace std;
using namespace Eigen;
//using namespace arma;

// Putting this header lock at the beginning of the file prevents
// including the library headers in the preamble of this file.
// However functions in the "trans_utils" and "laser_utils" headers
// utilizes these libraries.

#ifndef _UTILS_HH_
#define _UTILS_HH_

// Define new Eigen matrix and vector types
namespace Eigen{
  typedef Eigen::Matrix<double, 6, 6> Matrix6d;
  typedef Eigen::Matrix<double, 6, 1> Vector6d;
  typedef Eigen::Matrix<double, 5, 1> Vector5d;
}

namespace utils{
  // This macro provides an efficient way to raise exceptions with
  // very informative command line messages.
#define ASSERT(condition, message) \
  if (! (condition)) { \
    std::cerr << "Assertion `" #condition "` failed in " << __FILE__ \
    << " line " << __LINE__ << ": " << message << std::endl; \
    std::exit(EXIT_FAILURE); \
  } \

#define PRINT_FLF {cout << "File : " << __FILE__ << " Line # : " << __LINE__ << " Func : " << __func__ << endl;}

#define SGN(x) ((x) == 0 ? 0 : ((x) < 0 ? -1 : +1))

#define DEBUG_MSGS_ON 0
#define DEBUG_MSG(text) {if(DEBUG_MSGS_ON){cout << "DEBUG_MSG : " << text << endl; fflush(NULL);}}

#ifndef DEG2RAD
#define DEG2RAD(x) ((x) / 180.0 * PI)
#endif
#ifndef RAD2DEG
#define RAD2DEG(x) ((x) / PI * 180.0)
#endif

extern std::map<string, ros::Time> __timers__;
#define TIC(text) {utils::__timers__[(text)] = ros::Time::now();}
#define TOC(text) {auto it = utils::__timers__.find(text); \
                   if(it != utils::__timers__.end()) \
                    cout << "Timer <" << it->first << "> : " << (ros::Time::now() - it->second).toSec() << endl; \
                   else \
                    cout << "No timer <" << (text) << ">" << endl;}

  // The 'utils' namespace implements utility functions which cannot
  // be categorized into a specific class of helper routines. However
  // functions such as rotation transformations and ROS-Eigen data
  // structure conversions are collected under the 'utils::trans'
  // namespace in its specific header and source files. Similarly
  // laser processing routines are grouped under 'utils::laser'
  // namespace. As new functions are required, they should be added
  // directly under 'utils' namespace unless they intuitively form
  // a group/set of functions.

  class Timer{
    private:
      ros::Time _start;
      bool _print_on;
      string _label;
    public:
      Timer(bool print_on = true, string label = ""){
        _print_on = print_on;
        _label    = label;
      }

      void tic(){
        _start = ros::Time::now();
      }

      void print_on() {_print_on = true ;}
      void print_off(){_print_on = false;}

      double toc(string sublabel = ""){
        double dt = (ros::Time::now() - _start).toSec();
        if(_print_on == true)
          cout << "<" << _label << sublabel << " Time elapsed : " << dt << ">" << endl;
        return dt;
      }
  };

  // Clamps the given value in between the extrema
  inline double clamp(double val, double min, double max){
    return val > max ? max : val < min ? min : val;
  }

  /*
     inline void clamp(double &val, double min, double max){
     val = val > max ? max : val < min ? min : val;
     }
   */

  // This function returns the 2*PI modula shifted by -PI
  // of an angle in radians
  inline double fix_angle(double ang){
    while(ang > PI)
      ang -= PI;
    while(ang <= -PI)
      ang += PI;
    return ang;
  }

  inline void fix_angle(double *ang){
    while(*ang > PI)
      *ang -= PI;
    while(*ang <= -PI)
      *ang += PI;
  }

  inline void generate_colors(vector<Eigen::Vector3i> &colors){
    static int temp_colors[] = {
      0x000000, 0x00FF00, 0x0000FF, 0xFF0000,
      0x01FFFE, 0xFFA6FE, 0xFFDB66, 0x006401,
      0x010067, 0x95003A, 0x007DB5, 0xFF00F6,
      0xFFEEE8, 0x774D00, 0x90FB92, 0x0076FF,
      0xD5FF00, 0xFF937E, 0x6A826C, 0xFF029D,
      0xFE8900, 0x7A4782, 0x7E2DD2, 0x85A900,
      0xFF0056, 0xA42400, 0x00AE7E, 0x683D3B,
      0xBDC6FF, 0x263400, 0xBDD393, 0x00B917,
      0x9E008E, 0x001544, 0xC28C9F, 0xFF74A3,
      0x01D0FF, 0x004754, 0xE56FFE, 0x788231,
      0x0E4CA1, 0x91D0CB, 0xBE9970, 0x968AE8,
      0xBB8800, 0x43002C, 0xDEFF74, 0x00FFC6,
      0xFFE502, 0x620E00, 0x008F9C, 0x98FF52,
      0x7544B1, 0xB500FF, 0x00FF78, 0xFF6E41,
      0x005F39, 0x6B6882, 0x5FAD4E, 0xA75740,
      0xA5FFD2, 0xFFB167, 0x009BFF, 0xE85EBE};
    colors.clear();
    colors.resize(64);
    for(int i = 0 ; i < 64 ; i++){
      colors[i](0) = (temp_colors[i] & 0xFF0000) >> 16;
      colors[i](1) = (temp_colors[i] & 0x00FF00) >> 8;
      colors[i](2) = (temp_colors[i] & 0x0000FF);
    }
  }

  typedef struct Ellipse
  {
    arma::vec::fixed<2> center;
    arma::vec::fixed<2> major_axis;
    arma::vec::fixed<2> minor_axis;
    float len_major, len_minor;
    float theta;
    bool success;
  } Ellipse;

  // Output is A = [a b c d e f]' such that 'ax^2 + bxy + cy^2 +dx + ey + f = 0'
  bool fit_ellipse_ransac(vector<double> &xs, vector<double> &ys, vector<double> &fit, int num_trials = 200);
  void fit_ellipse(vector<double> &xs, vector<double> &ys, vector<double> &fit);
  void get_ellipse_parameters(vector<double> &fit, Ellipse &ell);

}

#endif


#include <stan/math/prim/scal.hpp>
#include <boost/math/special_functions/fpclassify.hpp>
#include <gtest/gtest.h>
#include <limits>

TEST(MathsFunctions, square) {
  double y = 2.0;
  EXPECT_FLOAT_EQ(y * y, stan::math::square(y));

  y = 0.0;
  EXPECT_FLOAT_EQ(y * y, stan::math::square(y));

  y = -32.7;
  EXPECT_FLOAT_EQ(y * y, stan::math::square(y));
}

TEST(MathFunctions, square_nan) {
  double nan = std::numeric_limits<double>::quiet_NaN();

  EXPECT_PRED1(boost::math::isnan<double>, stan::math::square(nan));
}


/*! \file scfloop.cpp
    \brief SCFを行うクラスの実装

    Copyright ©  2015 @dc1394 All Rights Reserved.
    This software is released under the BSD 2-Clause License.
*/

#include "eigenvaluesearch.h"
#include "normalization.h"
#include "readinputfile.h"
#include "scfloop.h"
#include "simpson.h"
#include <iomanip>                              // for std::setw
#include <iostream>                             // for std::cout
#include <stdexcept>                            // for std::runtime_error
#include <boost/math/constants/constants.hpp>   // for boost::math::constants

namespace schrac {
    // #region コンストラクタ

    ScfLoop::ScfLoop(std::pair<std::string, bool> const & arg) :
        PData([this]{ return std::cref(pdata_); }, nullptr),
        PDiffData([this]{ return std::cref(pdiffdata_); }, nullptr),
        PEhartree([this]{ return std::cref(ehartree_); }, nullptr),
        ehartree_(std::nullopt)
    {
        ReadInputFile rif(arg);         // ファイルを読み込む
        rif.readFile();
        pdata_ = rif.PData;

        initialize();

        if (pdata_->chemical_symbol_ == Data::Chemical_Symbol[0]) {
            pdiffsolver_ = std::make_shared<DiffSolver>(pdata_, pdiffdata_);
        }
        else {
            prho_ = std::make_shared<Rho>(pdiffdata_);
            pvh_ = std::make_shared<Vhartree>(pdiffdata_->r_mesh_);
            pdiffsolver_ = std::make_shared<DiffSolver>(pdata_, pdiffdata_, prho_, pvh_);
        }

        message();
    }

    // #endregion コンストラクタ

    // #region publicメンバ関数

    void ScfLoop::message() const
    {
        std::cout << pdata_->chemical_symbol_
            << "原子の"
            << pdata_->orbital_
            << "軌道";

        if (pdata_->eq_type_ == Data::Eq_type::DIRAC && pdata_->spin_orbital_ == Data::ALPHA) {
            std::cout << "、スピン上向き";
        }
        else if (pdata_->eq_type_ == Data::Eq_type::DIRAC && pdata_->spin_orbital_ == Data::BETA) {
            std::cout << "、スピン下向き";
        }

        std::cout << "の波動関数と固有値を計算します。\n";
    }

    ScfLoop::mypair ScfLoop::operator()()
    {
        if (pdata_->chemical_symbol_ == Data::Chemical_Symbol[0]) {
            return std::make_pair(std::move(pdiffdata_), run());
        }
        else {
            return std::make_pair(std::move(pdiffdata_), scfrun());
        }
    }

    // #endregion publicメンバ関数

    // #region privateメンバ関数

    bool ScfLoop::check_converge(dvector const & newrho, std::int32_t scfloop)
    {
        auto const normrd = std::abs(req_normrd(newrho, prho_->PRho));

        req_hartree_energy(newrho, pvh_->Vhart);
        std::cout << std::setw(2) << "Iteration # "
            << scfloop
            << ": NormRD = " << normrd
            << ", Energy = " << req_energy(pdiffdata_->E_)
            << std::endl;

        return normrd < pdata_->scf_criterion_;
    }

    void ScfLoop::initialize()
    {
        pdiffdata_ = std::make_shared<DiffData>(pdata_);

        pdiffdata_->r_mesh_.reserve(pdata_->grid_num_ + 1);
        for (auto i = 0; i <= pdata_->grid_num_; i++) {
            pdiffdata_->r_mesh_.push_back(std::exp(pdata_->xmin_ + static_cast<double>(i) * pdiffdata_->dx_));
        }
    }

    void ScfLoop::make_vhartree()
    {
        pdiffsolver_->solve_poisson();
        pvh_->set_vhartree_boundary_condition(pdata_->Z_);
        pvh_->vhart_init();
    }

    void ScfLoop::req_hartree_energy(dvector const & rho, dvector const & vhartree)
    {
        dvector u2;
        u2.reserve(pdata_->grid_num_ + 1);
        for (auto i = 0; i <= pdata_->grid_num_; i++) {
            u2.push_back(sqr(pdiffdata_->r_mesh_[i]) * rho[i]);
        }

        Simpson simpson(pdiffdata_->dx_);
        ehartree_.emplace(simpson(vhartree, u2, pdiffdata_->r_mesh_, 1));
    }

    double ScfLoop::req_energy(double eigen) const
    {
        return 2.0 * eigen - *ehartree_;
    }

    double ScfLoop::req_normrd(dvector const & newrho, dvector const & oldrho) const
    {
        using namespace boost::math::constants;
        BOOST_ASSERT(newrho.size() == oldrho.size());

        dvector residual;
        residual.reserve(newrho.size());

        for (auto i = 0; i <= pdata_->grid_num_; i++) {
            residual.push_back(newrho[i] - oldrho[i]);
        }

        Simpson const simpson(pdiffdata_->dx_);
        return 4.0 * pi<double>() * simpson(residual, residual, pdiffdata_->r_mesh_, 3);
    }

    dvector ScfLoop::req_newrho(dvector const & rf) const
    {
        dvector newrho;
        newrho.reserve(pdata_->grid_num_ + 1);
        for (auto i = 0; i <= pdata_->grid_num_; i++) {
            newrho.push_back(sqr(rf[i]));
        }

        return newrho;
    }

    ScfLoop::mymap ScfLoop::run()
    {
        EigenValueSearch evs(pdata_, pdiffdata_, prho_, pvh_);

        if (!evs.search()) {
            throw std::runtime_error("固有値が見つかりませんでした。終了します。");
        }

        return nomalization(evs.PDiffSolver);
    }

    ScfLoop::mymap ScfLoop::scfrun()
    {
        auto scfloop = 1;
        ScfLoop::mymap wavefunctions;
        for (; scfloop <= pdata_->scf_maxiter_; scfloop++) {
            prho_->init();
            make_vhartree();

            EigenValueSearch evs(pdata_, pdiffdata_, prho_, pvh_);

            if (!evs.search()) {
                throw std::runtime_error("固有値が見つかりませんでした。終了します。");
            }

            wavefunctions = nomalization(evs.PDiffSolver);
            auto const newrho = req_newrho(wavefunctions.at("2 Eigen function"));
            if (check_converge(newrho, scfloop)) {
                break;
            }
            prho_->rhomix(newrho);
        }

        if (scfloop == pdata_->scf_maxiter_) {
            throw std::runtime_error("SCFが収束しませんでした。終了します。");
        }

        return wavefunctions;
    }

    // #endregion privateメンバ関数
}


#include <iostream>
#include <cmath>
#include <random>
#include <stdexcept>
#include <fstream>
#include <cstdlib>
#include <string>
#include <Eigen/Dense>
#include "aggregate_gen_CCA.hpp"
using namespace Eigen;
using namespace std;

default_random_engine re(random_device{}()); // random seed
//default_random_engine re; //default fixed seed
uniform_real_distribution<double> urf {0,1.0};

int main(int argc, char *argv[]){

    // argv[0] == program name
    // argv[1] == num_sph_SA
    // argv[2] == levels
    // argv[3] == fractal prefactor kf
    // argv[4] == fractal dimension Df
    // argv[5] == error tolerance of attached distance

    double a= {1.0}; // radius of spherule
    double tol= {1.0e-2}; // error torelance of the distance between attached spherules
    int num_sph_SA;
    int levels;
    double kf;
    double Df;
    int num_agg;

    switch (argc)
    {
    case 1:
        // use default parameter_list
        num_sph_SA= {4}; // number of spherule in aggregate generated given by SA
        levels= {10}; //number of hierarchical coagulation
        kf= {1.0}; // fractal prefactor
        Df= {2.5}; // fractal dimension
        tol= {1.0e-3}; // error torelance of the distance between attached spherules
        num_agg= {1}; // number of aggregate
        break;
    case 6:
        // parameter_list from the command line arguments
        num_sph_SA= atoi(argv[1]); // number of spherule in aggregate generated given by SA
        levels= atoi(argv[2]); //number of hierarchical coagulation
        kf= atof(argv[3]); // fractal prefactor
        Df= atof(argv[4]); // fractal dimension
        num_agg= atoi(argv[5]); // number of aggregate
        break;
    default:
        runtime_error("incorrect parameter_list");
        break;
    }

    cout << "num_sph_SA: " << num_sph_SA << endl;
    cout << "levels: " << levels << endl;
    cout << "kf: " << kf << endl;
    cout << "Df: " << Df << endl;
    cout << "tol: " << tol << endl;
    cout << "num_agg: " << num_agg << endl;

    for(int ind_agg=0; ind_agg < num_agg; ++ind_agg){
      vector<MatrixXd> pos_sph_levels= aggregate_gen_CCA(a,num_sph_SA,levels,kf,Df,tol);

      for(int k=0; k<pos_sph_levels.size(); ++k){
          int num_sph = num_sph_SA*pow(2,k);
          cout << "writing " << k << "th level aggregate, num_sph= " << num_sph << endl;
          MatrixXd pos_sph  {pos_sph_levels[k]};
          string oname = "agg"+to_string(ind_agg)+"_N"+to_string(num_sph)+"_kf"+to_string(kf).erase(3)+"_Df"+to_string(Df).erase(3)+".out";
          ofstream ost {oname};
          for(int i=0; i<num_sph; ++i){
              ost << scientific << showpoint
               << a << ' '
               << pos_sph(0,i) << ' '
               << pos_sph(1,i) << ' '
               << pos_sph(2,i) << endl;
          }
          ost.close();
      }
    }

}