Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

constexpr all the generate_canonical parameters #2498

Merged
merged 7 commits into from
Feb 7, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 68 additions & 8 deletions stl/inc/random
Original file line number Diff line number Diff line change
Expand Up @@ -238,19 +238,48 @@ private:
vector<result_type> _Myvec;
};

constexpr int _Generate_canonical_iterations(const int _Bits, const uint64_t _Gmin, const uint64_t _Gmax) {
StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved
// For a URBG type `G` with range == `(G::max() - G::min()) + 1`, returns the number of calls to generate at least
// _Bits bits of entropy. Specifically, max(1, ceil(_Bits / log2(range))).

MattStephanson marked this conversation as resolved.
Show resolved Hide resolved
int _Ceil = 0;
if (_Bits == 0) {
;
MattStephanson marked this conversation as resolved.
Show resolved Hide resolved
} else if (_Gmax == UINT64_MAX && _Gmin == 0) {
_Ceil = ((_Bits - 1) / 64) + 1;
MattStephanson marked this conversation as resolved.
Show resolved Hide resolved
} else {
const auto _Range = (_Gmax - _Gmin) + 1;
if ((_Range & (_Range - 1)) == 0) {
_Ceil = (_Bits - 1) / _Countr_zero(_Range) + 1;
MattStephanson marked this conversation as resolved.
Show resolved Hide resolved
} else {
const auto _Target = ~uint64_t{0} >> (64 - _Bits);
MattStephanson marked this conversation as resolved.
Show resolved Hide resolved
uint64_t _Prod = 1;
while (_Prod <= _Target) {
if (_Prod > UINT64_MAX / _Range) {
++_Ceil;
MattStephanson marked this conversation as resolved.
Show resolved Hide resolved
break;
}
_Prod *= _Range;
++_Ceil;
}
}
}

return _Ceil < 1 ? 1 : _Ceil;
}

template <class _Real, size_t _Bits, class _Gen>
_NODISCARD _Real generate_canonical(_Gen& _Gx) { // build a floating-point value from random sequence
_RNG_REQUIRE_REALTYPE(generate_canonical, _Real);

const size_t _Digits = static_cast<size_t>(numeric_limits<_Real>::digits);
const size_t _Minbits = _Digits < _Bits ? _Digits : _Bits;
constexpr auto _Digits = static_cast<size_t>(numeric_limits<_Real>::digits);
constexpr auto _Minbits = static_cast<int>(_Digits < _Bits ? _Digits : _Bits);

const _Real _Gxmin = static_cast<_Real>((_Gx.min)());
const _Real _Gxmax = static_cast<_Real>((_Gx.max)());
const _Real _Rx = (_Gxmax - _Gxmin) + _Real{1};
constexpr auto _Gxmin = static_cast<_Real>((_Gen::min)());
constexpr auto _Gxmax = static_cast<_Real>((_Gen::max)());
constexpr auto _Rx = (_Gxmax - _Gxmin) + _Real{1};

const int _Ceil = static_cast<int>(_STD ceil(static_cast<_Real>(_Minbits) / _STD log2(_Rx)));
const int _Kx = _Ceil < 1 ? 1 : _Ceil;
constexpr int _Kx = _Generate_canonical_iterations(_Minbits, (_Gen::min)(), (_Gen::max)());

_Real _Ans{0};
_Real _Factor{1};
Expand All @@ -263,7 +292,38 @@ _NODISCARD _Real generate_canonical(_Gen& _Gx) { // build a floating-point value
return _Ans / _Factor;
}

#define _NRAND(eng, resty) (_STD generate_canonical<resty, static_cast<size_t>(-1)>(eng))
template <class _Real, class _Gen>
_NODISCARD _Real _Nrand_impl(_Gen& _Gx) { // build a floating-point value from random sequence
_RNG_REQUIRE_REALTYPE(_Nrand_impl, _Real);
StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved

constexpr auto _Digits = static_cast<size_t>(numeric_limits<_Real>::digits);
constexpr auto _Bits = ~size_t{0};
constexpr auto _Minbits = _Digits < _Bits ? _Digits : _Bits;

if constexpr (_Select_invoke_traits<decltype(&_Gen::min)>::_Is_invocable::value
&& _Select_invoke_traits<decltype(&_Gen::max)>::_Is_invocable::value && _Minbits <= 64) {
StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved
return _STD generate_canonical<_Real, _Minbits>(_Gx);
} else {
const _Real _Gxmin = static_cast<_Real>((_Gx.min)());
const _Real _Gxmax = static_cast<_Real>((_Gx.max)());
const _Real _Rx = (_Gxmax - _Gxmin) + _Real{1};

const int _Ceil = static_cast<int>(_STD ceil(static_cast<_Real>(_Minbits) / _STD log2(_Rx)));
const int _Kx = _Ceil < 1 ? 1 : _Ceil;

_Real _Ans{0};
_Real _Factor{1};

for (int _Idx = 0; _Idx < _Kx; ++_Idx) { // add in another set of bits
_Ans += (static_cast<_Real>(_Gx()) - _Gxmin) * _Factor;
_Factor *= _Rx;
}

return _Ans / _Factor;
}
}

#define _NRAND(eng, resty) (_Nrand_impl<resty>(eng))

_INLINE_VAR constexpr int _MP_len = 5;
using _MP_arr = uint64_t[_MP_len];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved
RUNALL_INCLUDE ..\usual_latest_matrix.lst
MattStephanson marked this conversation as resolved.
Show resolved Hide resolved
58 changes: 58 additions & 0 deletions tests/std/tests/GH_001964_constexpr_generate_canonical/test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#include <cassert>
StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved
#include <cmath>
#include <cstdint>
#include <random>
#include <utility>

using namespace std;

int naive_iterations(const int bits, const uint64_t gmin, const uint64_t gmax) {
// Naive implementation of [rand.util.canonical]. Note that for large values of range, it's possible that
// log2(range) == bits when range < 2^bits. This can lead to incorrect results, so we can't use this function as
// a reference for all values.

const double range = static_cast<double>(gmax) - static_cast<double>(gmin) + 1.0;
return static_cast<int>(ceil(static_cast<double>(bits) / log2(static_cast<double>(range))));
}

void test(const int target_bits) {
// Increase the range until the number of iterations repeats.
uint64_t range = 2;
int k = 0;
int prev_k = -1;
while (k != prev_k) {
prev_k = exchange(k, naive_iterations(target_bits, 1, range));
const int k1 = _Generate_canonical_iterations(target_bits, 1, range);
MattStephanson marked this conversation as resolved.
Show resolved Hide resolved
assert(k == k1);
++range;
}

// Now only check the crossover points, where incrementing the range actually causes the number of iterations to
// increase.
--k;
for (; k > 0; --k) {
MattStephanson marked this conversation as resolved.
Show resolved Hide resolved
// The largest range such that k iterations generating [1,range] produces less than target_bits bits.
if (k == 1) {
range = ~uint64_t{0} >> (64 - target_bits);
} else {
range = static_cast<uint64_t>(ceil(pow(2.0, static_cast<double>(target_bits) / k))) - 1;
}

int k0 = (k == 1) ? 2 : naive_iterations(target_bits, 1, range);
int k1 = _Generate_canonical_iterations(target_bits, 1, range);
assert(k0 == k1 && k1 == k + 1);
StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved

k0 = (k == 1) ? 1 : naive_iterations(target_bits, 0, range);
k1 = _Generate_canonical_iterations(target_bits, 0, range);
assert(k0 == k1 && k1 == k);
}
}

int main() {
static_assert(_Generate_canonical_iterations(53, 1, uint64_t{1} << 32) == 2);
static_assert(_Generate_canonical_iterations(64, 0, ~uint64_t{0}) == 1);

for (int bits = 1; bits <= 64; ++bits) {
test(bits);
}
}