From 1995c6a3c2f9080160d9d8716504dc004e5e1ec0 Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Sun, 2 Aug 2020 14:23:26 -0700 Subject: Export of internal Abseil changes -- 790f9061df340cd900e8da70e66c363f7af3c2eb by Abseil Team : Add support for rvalue reference to function types. PiperOrigin-RevId: 324508531 -- 51fe201dbb41a3ebc3d49ff65250b5f464279d43 by Abseil Team : Cleaning up function comment style; no substantive change. PiperOrigin-RevId: 324497401 -- da8595d5266577d0c170528d12f6de17b8affcc2 by Abseil Team : Add support for demangling GNU vector types. PiperOrigin-RevId: 324494559 -- 0cb0acf88c1750f6963c9cb85249f9b4f0bd5104 by Abseil Team : Add support for thread-local types. PiperOrigin-RevId: 324491183 -- c676bc8380560599cd26f7f231e04e6be532e904 by Abseil Team : Add support for demangling "Du" (char8_t). PiperOrigin-RevId: 324441607 -- b218bf6467bc62b327214782c881e8224ad91509 by Abseil Team : Update doc comments in header of `any.h` to reflect that `absl::variant` has been released. PiperOrigin-RevId: 324431690 -- e5b579f3f1aa598c1f62e71dba7103b98811de59 by Laramie Leavitt : Bugfix: Fix bounds in absl::Uniform where one of the bounds is min/max. When absl::Uniform(rng, tag, a, b) is called, the tag is used in conjunction with the type to determine whether or not to manipulate the bounds to make them inclusive or exclusive through the uniform_*_bound functions. Unfortunately, at limits of the interval the function was not well behaved. The previous implementation used wrapping arithmetic. This causes incorrect bounds computation at the extremes (numeric_limits::min / numeric_limits::max) the bound would wrap. Improve this situation by: 1/ Changing the uniform_*_bound functions to use saturating arithmetic instead of wrapping, thus in the unsigned case, the upper_bound of IntervalOpenOpen for 0 is now 0, rather than numeric_limits::max, likewise for the lower bound. 2/ Adjusting the hi/lo checks in the distributions. When the interval is empty, such as for absl::Uniform(absl::IntervalOpenOpen, gen, 1, 0), the return value is somewhat nonsensical. Now absl::Uniform more consistently returns the low input rather than any adjusted input. In the above case, that means that 1 is returned rather than 2. NOTE: Calls to absl::Uniform where the resolved upper bound is < the lower bound are still ill-formed and should be avoided. 3/ Adding better tests. The underlying uniform_*_distribution classes are not affected. PiperOrigin-RevId: 324240873 GitOrigin-RevId: 790f9061df340cd900e8da70e66c363f7af3c2eb Change-Id: I2a2208650ea3135c575e200b868ce1d275069fc8 --- absl/random/internal/BUILD.bazel | 12 ++ absl/random/internal/uniform_helper.h | 38 +++- absl/random/internal/uniform_helper_test.cc | 279 ++++++++++++++++++++++++++++ 3 files changed, 327 insertions(+), 2 deletions(-) create mode 100644 absl/random/internal/uniform_helper_test.cc (limited to 'absl/random/internal') diff --git a/absl/random/internal/BUILD.bazel b/absl/random/internal/BUILD.bazel index 83843b4c..000cc45b 100644 --- a/absl/random/internal/BUILD.bazel +++ b/absl/random/internal/BUILD.bazel @@ -716,3 +716,15 @@ cc_test( "@com_google_googletest//:gtest_main", ], ) + +cc_test( + name = "uniform_helper_test", + size = "small", + srcs = ["uniform_helper_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":uniform_helper", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/absl/random/internal/uniform_helper.h b/absl/random/internal/uniform_helper.h index 5b2afecb..1243bc1c 100644 --- a/absl/random/internal/uniform_helper.h +++ b/absl/random/internal/uniform_helper.h @@ -105,7 +105,7 @@ typename absl::enable_if_t< std::is_same>>::value, IntType> uniform_lower_bound(Tag, IntType a, IntType) { - return a + 1; + return a < (std::numeric_limits::max)() ? (a + 1) : a; } template @@ -136,7 +136,7 @@ typename absl::enable_if_t< std::is_same>>::value, IntType> uniform_upper_bound(Tag, IntType, IntType b) { - return b - 1; + return b > (std::numeric_limits::min)() ? (b - 1) : b; } template @@ -172,6 +172,40 @@ uniform_upper_bound(Tag, FloatType, FloatType b) { return std::nextafter(b, (std::numeric_limits::max)()); } +// Returns whether the bounds are valid for the underlying distribution. +// Inputs must have already been resolved via uniform_*_bound calls. +// +// The c++ standard constraints in [rand.dist.uni.int] are listed as: +// requires: lo <= hi. +// +// In the uniform_int_distrubtion, {lo, hi} are closed, closed. Thus: +// [0, 0] is legal. +// [0, 0) is not legal, but [0, 1) is, which translates to [0, 0]. +// (0, 1) is not legal, but (0, 2) is, which translates to [1, 1]. +// (0, 0] is not legal, but (0, 1] is, which translates to [1, 1]. +// +// The c++ standard constraints in [rand.dist.uni.real] are listed as: +// requires: lo <= hi. +// requires: (hi - lo) <= numeric_limits::max() +// +// In the uniform_real_distribution, {lo, hi} are closed, open, Thus: +// [0, 0] is legal, which is [0, 0+epsilon). +// [0, 0) is legal. +// (0, 0) is not legal, but (0-epsilon, 0+epsilon) is. +// (0, 0] is not legal, but (0, 0+epsilon] is. +// +template +absl::enable_if_t::value, bool> +is_uniform_range_valid(FloatType a, FloatType b) { + return a <= b && std::isfinite(b - a); +} + +template +absl::enable_if_t::value, bool> +is_uniform_range_valid(IntType a, IntType b) { + return a <= b; +} + // UniformDistribution selects either absl::uniform_int_distribution // or absl::uniform_real_distribution depending on the NumType parameter. template diff --git a/absl/random/internal/uniform_helper_test.cc b/absl/random/internal/uniform_helper_test.cc new file mode 100644 index 00000000..173c49b0 --- /dev/null +++ b/absl/random/internal/uniform_helper_test.cc @@ -0,0 +1,279 @@ +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/random/internal/uniform_helper.h" + +#include +#include +#include + +#include "gtest/gtest.h" + +namespace { + +using absl::IntervalClosedClosedTag; +using absl::IntervalClosedOpenTag; +using absl::IntervalOpenClosedTag; +using absl::IntervalOpenOpenTag; +using absl::random_internal::uniform_inferred_return_t; +using absl::random_internal::uniform_lower_bound; +using absl::random_internal::uniform_upper_bound; + +class UniformHelperTest : public testing::Test {}; + +TEST_F(UniformHelperTest, UniformBoundFunctionsGeneral) { + constexpr IntervalClosedClosedTag IntervalClosedClosed; + constexpr IntervalClosedOpenTag IntervalClosedOpen; + constexpr IntervalOpenClosedTag IntervalOpenClosed; + constexpr IntervalOpenOpenTag IntervalOpenOpen; + + // absl::uniform_int_distribution natively assumes IntervalClosedClosed + // absl::uniform_real_distribution natively assumes IntervalClosedOpen + + EXPECT_EQ(uniform_lower_bound(IntervalOpenClosed, 0, 100), 1); + EXPECT_EQ(uniform_lower_bound(IntervalOpenOpen, 0, 100), 1); + EXPECT_GT(uniform_lower_bound(IntervalOpenClosed, 0, 1.0), 0); + EXPECT_GT(uniform_lower_bound(IntervalOpenOpen, 0, 1.0), 0); + EXPECT_GT(uniform_lower_bound(IntervalOpenClosed, 0, 1.0), 0); + EXPECT_GT(uniform_lower_bound(IntervalOpenOpen, 0, 1.0), 0); + + EXPECT_EQ(uniform_lower_bound(IntervalClosedClosed, 0, 100), 0); + EXPECT_EQ(uniform_lower_bound(IntervalClosedOpen, 0, 100), 0); + EXPECT_EQ(uniform_lower_bound(IntervalClosedClosed, 0, 1.0), 0); + EXPECT_EQ(uniform_lower_bound(IntervalClosedOpen, 0, 1.0), 0); + EXPECT_EQ(uniform_lower_bound(IntervalClosedClosed, 0, 1.0), 0); + EXPECT_EQ(uniform_lower_bound(IntervalClosedOpen, 0, 1.0), 0); + + EXPECT_EQ(uniform_upper_bound(IntervalOpenOpen, 0, 100), 99); + EXPECT_EQ(uniform_upper_bound(IntervalClosedOpen, 0, 100), 99); + EXPECT_EQ(uniform_upper_bound(IntervalOpenOpen, 0, 1.0), 1.0); + EXPECT_EQ(uniform_upper_bound(IntervalClosedOpen, 0, 1.0), 1.0); + EXPECT_EQ(uniform_upper_bound(IntervalOpenOpen, 0, 1.0), 1.0); + EXPECT_EQ(uniform_upper_bound(IntervalClosedOpen, 0, 1.0), 1.0); + + EXPECT_EQ(uniform_upper_bound(IntervalOpenClosed, 0, 100), 100); + EXPECT_EQ(uniform_upper_bound(IntervalClosedClosed, 0, 100), 100); + EXPECT_GT(uniform_upper_bound(IntervalOpenClosed, 0, 1.0), 1.0); + EXPECT_GT(uniform_upper_bound(IntervalClosedClosed, 0, 1.0), 1.0); + EXPECT_GT(uniform_upper_bound(IntervalOpenClosed, 0, 1.0), 1.0); + EXPECT_GT(uniform_upper_bound(IntervalClosedClosed, 0, 1.0), 1.0); + + // Negative value tests + EXPECT_EQ(uniform_lower_bound(IntervalOpenClosed, -100, -1), -99); + EXPECT_EQ(uniform_lower_bound(IntervalOpenOpen, -100, -1), -99); + EXPECT_GT(uniform_lower_bound(IntervalOpenClosed, -2.0, -1.0), -2.0); + EXPECT_GT(uniform_lower_bound(IntervalOpenOpen, -2.0, -1.0), -2.0); + EXPECT_GT(uniform_lower_bound(IntervalOpenClosed, -2.0, -1.0), -2.0); + EXPECT_GT(uniform_lower_bound(IntervalOpenOpen, -2.0, -1.0), -2.0); + + EXPECT_EQ(uniform_lower_bound(IntervalClosedClosed, -100, -1), -100); + EXPECT_EQ(uniform_lower_bound(IntervalClosedOpen, -100, -1), -100); + EXPECT_EQ(uniform_lower_bound(IntervalClosedClosed, -2.0, -1.0), -2.0); + EXPECT_EQ(uniform_lower_bound(IntervalClosedOpen, -2.0, -1.0), -2.0); + EXPECT_EQ(uniform_lower_bound(IntervalClosedClosed, -2.0, -1.0), + -2.0); + EXPECT_EQ(uniform_lower_bound(IntervalClosedOpen, -2.0, -1.0), -2.0); + + EXPECT_EQ(uniform_upper_bound(IntervalOpenOpen, -100, -1), -2); + EXPECT_EQ(uniform_upper_bound(IntervalClosedOpen, -100, -1), -2); + EXPECT_EQ(uniform_upper_bound(IntervalOpenOpen, -2.0, -1.0), -1.0); + EXPECT_EQ(uniform_upper_bound(IntervalClosedOpen, -2.0, -1.0), -1.0); + EXPECT_EQ(uniform_upper_bound(IntervalOpenOpen, -2.0, -1.0), -1.0); + EXPECT_EQ(uniform_upper_bound(IntervalClosedOpen, -2.0, -1.0), -1.0); + + EXPECT_EQ(uniform_upper_bound(IntervalOpenClosed, -100, -1), -1); + EXPECT_EQ(uniform_upper_bound(IntervalClosedClosed, -100, -1), -1); + EXPECT_GT(uniform_upper_bound(IntervalOpenClosed, -2.0, -1.0), -1.0); + EXPECT_GT(uniform_upper_bound(IntervalClosedClosed, -2.0, -1.0), -1.0); + EXPECT_GT(uniform_upper_bound(IntervalOpenClosed, -2.0, -1.0), -1.0); + EXPECT_GT(uniform_upper_bound(IntervalClosedClosed, -2.0, -1.0), + -1.0); + + EXPECT_GT(uniform_lower_bound(IntervalOpenClosed, 1.0, 2.0), 1.0); + EXPECT_LT(uniform_lower_bound(IntervalOpenClosed, 1.0, +0.0), 1.0); + EXPECT_LT(uniform_lower_bound(IntervalOpenClosed, 1.0, -0.0), 1.0); + EXPECT_LT(uniform_lower_bound(IntervalOpenClosed, 1.0, -1.0), 1.0); +} + +TEST_F(UniformHelperTest, UniformBoundFunctionsIntBounds) { + // Verifies the saturating nature of uniform_lower_bound and + // uniform_upper_bound + constexpr IntervalOpenOpenTag IntervalOpenOpen; + + // uint max. + constexpr auto m = (std::numeric_limits::max)(); + + EXPECT_EQ(1, uniform_lower_bound(IntervalOpenOpen, 0u, 0u)); + EXPECT_EQ(m, uniform_lower_bound(IntervalOpenOpen, m, m)); + EXPECT_EQ(m, uniform_lower_bound(IntervalOpenOpen, m - 1, m - 1)); + EXPECT_EQ(0, uniform_upper_bound(IntervalOpenOpen, 0u, 0u)); + EXPECT_EQ(m - 1, uniform_upper_bound(IntervalOpenOpen, m, m)); + + // int min/max + constexpr auto l = (std::numeric_limits::min)(); + constexpr auto r = (std::numeric_limits::max)(); + EXPECT_EQ(1, uniform_lower_bound(IntervalOpenOpen, 0, 0)); + EXPECT_EQ(l + 1, uniform_lower_bound(IntervalOpenOpen, l, l)); + EXPECT_EQ(r, uniform_lower_bound(IntervalOpenOpen, r - 1, r - 1)); + EXPECT_EQ(r, uniform_lower_bound(IntervalOpenOpen, r, r)); + EXPECT_EQ(-1, uniform_upper_bound(IntervalOpenOpen, 0, 0)); + EXPECT_EQ(l, uniform_upper_bound(IntervalOpenOpen, l, l)); + EXPECT_EQ(r - 1, uniform_upper_bound(IntervalOpenOpen, r, r)); +} + +TEST_F(UniformHelperTest, UniformBoundFunctionsRealBounds) { + // absl::uniform_real_distribution natively assumes IntervalClosedOpen; + // use the inverse here so each bound has to change. + constexpr IntervalOpenClosedTag IntervalOpenClosed; + + // Edge cases: the next value toward itself is itself. + EXPECT_EQ(1.0, uniform_lower_bound(IntervalOpenClosed, 1.0, 1.0)); + EXPECT_EQ(1.0f, uniform_lower_bound(IntervalOpenClosed, 1.0f, 1.0f)); + + // rightmost and leftmost finite values. + constexpr auto r = (std::numeric_limits::max)(); + const auto re = std::nexttoward(r, 0.0); + constexpr auto l = -r; + const auto le = std::nexttoward(l, 0.0); + + EXPECT_EQ(l, uniform_lower_bound(IntervalOpenClosed, l, l)); // (l,l) + EXPECT_EQ(r, uniform_lower_bound(IntervalOpenClosed, r, r)); // (r,r) + EXPECT_EQ(le, uniform_lower_bound(IntervalOpenClosed, l, r)); // (l,r) + EXPECT_EQ(le, uniform_lower_bound(IntervalOpenClosed, l, 0.0)); // (l, 0) + EXPECT_EQ(le, uniform_lower_bound(IntervalOpenClosed, l, le)); // (l, le) + EXPECT_EQ(r, uniform_lower_bound(IntervalOpenClosed, re, r)); // (re, r) + + EXPECT_EQ(le, uniform_upper_bound(IntervalOpenClosed, l, l)); // (l,l) + EXPECT_EQ(r, uniform_upper_bound(IntervalOpenClosed, r, r)); // (r,r) + EXPECT_EQ(r, uniform_upper_bound(IntervalOpenClosed, l, r)); // (l,r) + EXPECT_EQ(r, uniform_upper_bound(IntervalOpenClosed, l, re)); // (l,re) + EXPECT_EQ(r, uniform_upper_bound(IntervalOpenClosed, 0.0, r)); // (0, r) + EXPECT_EQ(r, uniform_upper_bound(IntervalOpenClosed, re, r)); // (re, r) + EXPECT_EQ(r, uniform_upper_bound(IntervalOpenClosed, le, re)); // (le, re) + + const double e = std::nextafter(1.0, 2.0); // 1 + epsilon + const double f = std::nextafter(1.0, 0.0); // 1 - epsilon + + // (1.0, 1.0 + epsilon) + EXPECT_EQ(e, uniform_lower_bound(IntervalOpenClosed, 1.0, e)); + EXPECT_EQ(std::nextafter(e, 2.0), + uniform_upper_bound(IntervalOpenClosed, 1.0, e)); + + // (1.0-epsilon, 1.0) + EXPECT_EQ(1.0, uniform_lower_bound(IntervalOpenClosed, f, 1.0)); + EXPECT_EQ(e, uniform_upper_bound(IntervalOpenClosed, f, 1.0)); + + // denorm cases. + const double g = std::numeric_limits::denorm_min(); + const double h = std::nextafter(g, 1.0); + + // (0, denorm_min) + EXPECT_EQ(g, uniform_lower_bound(IntervalOpenClosed, 0.0, g)); + EXPECT_EQ(h, uniform_upper_bound(IntervalOpenClosed, 0.0, g)); + + // (denorm_min, 1.0) + EXPECT_EQ(h, uniform_lower_bound(IntervalOpenClosed, g, 1.0)); + EXPECT_EQ(e, uniform_upper_bound(IntervalOpenClosed, g, 1.0)); + + // Edge cases: invalid bounds. + EXPECT_EQ(f, uniform_lower_bound(IntervalOpenClosed, 1.0, -1.0)); +} + +struct Invalid {}; + +template +auto InferredUniformReturnT(int) -> uniform_inferred_return_t; + +template +Invalid InferredUniformReturnT(...); + +// Given types , CheckArgsInferType() verifies that +// +// uniform_inferred_return_t and +// uniform_inferred_return_t +// +// returns the type "Expect". +// +// This interface can also be used to assert that a given inferred return types +// are invalid. Writing: +// +// CheckArgsInferType() +// +// will assert that this overload does not exist. +template +void CheckArgsInferType() { + static_assert( + absl::conjunction< + std::is_same(0))>, + std::is_same(0))>>::value, + ""); +} + +TEST_F(UniformHelperTest, UniformTypeInference) { + // Infers common types. + CheckArgsInferType(); + CheckArgsInferType(); + CheckArgsInferType(); + CheckArgsInferType(); + CheckArgsInferType(); + CheckArgsInferType(); + CheckArgsInferType(); + CheckArgsInferType(); + + // Properly promotes uint16_t. + CheckArgsInferType(); + CheckArgsInferType(); + CheckArgsInferType(); + CheckArgsInferType(); + CheckArgsInferType(); + CheckArgsInferType(); + + // Properly promotes int16_t. + CheckArgsInferType(); + CheckArgsInferType(); + CheckArgsInferType(); + CheckArgsInferType(); + + // Invalid (u)int16_t-pairings do not compile. + // See "CheckArgsInferType" comments above, for how this is achieved. + CheckArgsInferType(); + CheckArgsInferType(); + CheckArgsInferType(); + + // Properly promotes uint32_t. + CheckArgsInferType(); + CheckArgsInferType(); + CheckArgsInferType(); + + // Properly promotes int32_t. + CheckArgsInferType(); + CheckArgsInferType(); + + // Invalid (u)int32_t-pairings do not compile. + CheckArgsInferType(); + CheckArgsInferType(); + CheckArgsInferType(); + CheckArgsInferType(); + + // Invalid (u)int64_t-pairings do not compile. + CheckArgsInferType(); + CheckArgsInferType(); + CheckArgsInferType(); + + // Properly promotes float. + CheckArgsInferType(); +} + +} // namespace -- cgit v1.2.3