From 780702c2aac603a5ca09c22e34d3f913a375627d Mon Sep 17 00:00:00 2001 From: "joi@chromium.org" Date: Thu, 5 May 2011 02:22:11 +0000 Subject: Add one-time randomization support for FieldTrial, and the ability to disable field trials. I am going to have a need for both soon. Cleaning up some comments about empty trial names, adding static method TrialExists() and simplifying many call sites by using this method. While I'm in there and needing base/OWNERS approval, add an OWNERS file for base/metrics that adds jar@chromium.org as an owner for that directory. BUG=none TEST=base_unittests TBR=jam@chromium.org git-svn-id: svn://svn.chromium.org/chrome/trunk/src@84197 0039d316-1c4b-4281-b951-d872f2087c98 --- base/metrics/OWNERS | 1 + base/metrics/field_trial.cc | 98 +++++++++++++++++++++++---- base/metrics/field_trial.h | 115 +++++++++++++++++++++++--------- base/metrics/field_trial_unittest.cc | 125 +++++++++++++++++++++++++++++++++-- base/rand_util.cc | 6 +- base/rand_util.h | 4 ++ 6 files changed, 298 insertions(+), 51 deletions(-) create mode 100644 base/metrics/OWNERS (limited to 'base') diff --git a/base/metrics/OWNERS b/base/metrics/OWNERS new file mode 100644 index 0000000..844a032 --- /dev/null +++ b/base/metrics/OWNERS @@ -0,0 +1 @@ +jar@chromium.org diff --git a/base/metrics/field_trial.cc b/base/metrics/field_trial.cc index 03e5809..b86b9cc 100644 --- a/base/metrics/field_trial.cc +++ b/base/metrics/field_trial.cc @@ -6,6 +6,8 @@ #include "base/logging.h" #include "base/rand_util.h" +#include "base/sha1.h" +#include "base/string_util.h" #include "base/stringprintf.h" #include "base/utf_string_conversions.h" @@ -41,11 +43,13 @@ FieldTrial::FieldTrial(const std::string& name, : name_(name), divisor_(total_probability), default_group_name_(default_group_name), - random_(static_cast(divisor_ * base::RandDouble())), + random_(static_cast(divisor_ * RandDouble())), accumulated_group_probability_(0), - next_group_number_(kDefaultGroupNumber+1), - group_(kNotFinalized) { + next_group_number_(kDefaultGroupNumber + 1), + group_(kNotFinalized), + enable_field_trial_(true) { DCHECK_GT(total_probability, 0); + DCHECK(!name_.empty()); DCHECK(!default_group_name_.empty()); FieldTrialList::Register(this); @@ -55,7 +59,7 @@ FieldTrial::FieldTrial(const std::string& name, DCHECK_GT(day_of_month, 0); DCHECK_LT(day_of_month, 32); - base::Time::Exploded exploded; + Time::Exploded exploded; exploded.year = year; exploded.month = month; exploded.day_of_week = 0; // Should be unused. @@ -65,8 +69,33 @@ FieldTrial::FieldTrial(const std::string& name, exploded.second = 0; exploded.millisecond = 0; - base::Time expiration_time = Time::FromLocalExploded(exploded); - disable_field_trial_ = (GetBuildTime() > expiration_time) ? true : false; + Time expiration_time = Time::FromLocalExploded(exploded); + if (GetBuildTime() > expiration_time) + Disable(); +} + +void FieldTrial::UseOneTimeRandomization() { + DCHECK_EQ(group_, kNotFinalized); + DCHECK_EQ(kDefaultGroupNumber + 1, next_group_number_); + if (!FieldTrialList::IsOneTimeRandomizationEnabled()) { + NOTREACHED(); + Disable(); + return; + } + + random_ = static_cast( + divisor_ * HashClientId(FieldTrialList::client_id(), name_)); +} + +void FieldTrial::Disable() { + enable_field_trial_ = false; + + // In case we are disabled after initialization, we need to switch + // the trial to the default group. + if (group_ != kNotFinalized) { + group_ = kDefaultGroupNumber; + group_name_ = default_group_name_; + } } int FieldTrial::AppendGroup(const std::string& name, @@ -74,7 +103,7 @@ int FieldTrial::AppendGroup(const std::string& name, DCHECK_LE(group_probability, divisor_); DCHECK_GE(group_probability, 0); - if (enable_benchmarking_ || disable_field_trial_) + if (enable_benchmarking_ || !enable_field_trial_) group_probability = 0; accumulated_group_probability_ += group_probability; @@ -84,7 +113,7 @@ int FieldTrial::AppendGroup(const std::string& name, // This is the group that crossed the random line, so we do the assignment. group_ = next_group_number_; if (name.empty()) - base::StringAppendF(&group_name_, "%d", group_); + StringAppendF(&group_name_, "%d", group_); else group_name_ = name; FieldTrialList::NotifyFieldTrialGroupSelection(name_, group_name_); @@ -103,7 +132,8 @@ int FieldTrial::group() { } std::string FieldTrial::group_name() { - group(); // call group() to make group assignment was done. + group(); // call group() to make sure group assignment was done. + DCHECK(!group_name_.empty()); return group_name_; } @@ -133,6 +163,25 @@ Time FieldTrial::GetBuildTime() { return integral_build_time; } +// static +double FieldTrial::HashClientId(const std::string& client_id, + const std::string& trial_name) { + // SHA-1 is designed to produce a uniformly random spread in its output space, + // even for nearly-identical inputs, so it helps massage whatever client_id + // and trial_name we get into something with a uniform distribution, which + // is desirable so that we don't skew any part of the 0-100% spectrum. + std::string input(client_id + trial_name); + unsigned char sha1_hash[SHA1_LENGTH]; + SHA1HashBytes(reinterpret_cast(input.c_str()), + input.size(), + sha1_hash); + + COMPILE_ASSERT(sizeof(uint64) < sizeof(sha1_hash), need_more_data); + uint64* bits = reinterpret_cast(&sha1_hash[0]); + + return BitsToOpenEndedUnitInterval(*bits); +} + //------------------------------------------------------------------------------ // FieldTrialList methods and members. @@ -142,8 +191,9 @@ FieldTrialList* FieldTrialList::global_ = NULL; // static bool FieldTrialList::register_without_global_ = false; -FieldTrialList::FieldTrialList() +FieldTrialList::FieldTrialList(const std::string& client_id) : application_start_time_(TimeTicks::Now()), + client_id_(client_id), observer_list_(ObserverList::NOTIFY_EXISTING_ONLY) { DCHECK(!global_); DCHECK(!register_without_global_); @@ -204,19 +254,23 @@ std::string FieldTrialList::FindFullName(const std::string& name) { } // static +bool FieldTrialList::TrialExists(const std::string& name) { + return Find(name) != NULL; +} + +// static void FieldTrialList::StatesToString(std::string* output) { if (!global_) return; DCHECK(output->empty()); AutoLock auto_lock(global_->lock_); + for (RegistrationList::iterator it = global_->registered_.begin(); it != global_->registered_.end(); ++it) { const std::string name = it->first; std::string group_name = it->second->group_name_internal(); if (group_name.empty()) - // No definitive winner in this trial, use default_group_name as the - // group_name. - group_name = it->second->default_group_name(); + continue; // Should not include uninitialized trials. DCHECK_EQ(name.find(kPersistentStringSeparator), std::string::npos); DCHECK_EQ(group_name.find(kPersistentStringSeparator), std::string::npos); output->append(name); @@ -313,6 +367,24 @@ size_t FieldTrialList::GetFieldTrialCount() { return global_->registered_.size(); } +// static +bool FieldTrialList::IsOneTimeRandomizationEnabled() { + DCHECK(global_); + if (!global_) + return false; + + return !global_->client_id_.empty(); +} + +// static +const std::string& FieldTrialList::client_id() { + DCHECK(global_); + if (!global_) + return EmptyString(); + + return global_->client_id_; +} + FieldTrial* FieldTrialList::PreLockedFind(const std::string& name) { RegistrationList::iterator it = registered_.find(name); if (registered_.end() == it) diff --git a/base/metrics/field_trial.h b/base/metrics/field_trial.h index b8ab0c5..4e89ccf 100644 --- a/base/metrics/field_trial.h +++ b/base/metrics/field_trial.h @@ -13,12 +13,13 @@ // pseudo-randomly selected). // // States are typically generated randomly, either based on a one time -// randomization (generated randomly once, and then persistently reused in the -// client during each future run of the program), or by a startup randomization -// (generated each time the application starts up, but held constant during the -// duration of the process), or by continuous randomization across a run (where -// the state can be recalculated again and again, many times during a process). -// Only startup randomization is implemented thus far. +// randomization (which will yield the same results, in terms of selecting +// the client for a field trial or not, for every run of the program on a +// given machine), or by a startup randomization (generated each time the +// application starts up, but held constant during the duration of the +// process), or by continuous randomization across a run (where the state +// can be recalculated again and again, many times during a process). +// Continuous randomization is not yet implemented. //------------------------------------------------------------------------------ // Example: Suppose we have an experiment involving memory, such as determining @@ -52,10 +53,9 @@ // to randomly be assigned: // HISTOGRAM_COUNTS("Memory.RendererTotal", count); // The original histogram. -// static bool use_memoryexperiment_histogram( -// base::FieldTrialList::Find("MemoryExperiment") && -// !base::FieldTrialList::Find("MemoryExperiment")->group_name().empty()); -// if (use_memoryexperiment_histogram) { +// static const bool memory_renderer_total_trial_exists = +// FieldTrialList::TrialExists("Memory.RendererTotal"); +// if (memory_renderer_total_trial_exists) { // HISTOGRAM_COUNTS(FieldTrial::MakeName("Memory.RendererTotal", // "MemoryExperiment"), count); // } @@ -103,15 +103,35 @@ class BASE_API FieldTrial : public RefCounted { // The name is used to register the instance with the FieldTrialList class, // and can be used to find the trial (only one trial can be present for each - // name). + // name). |name| and |default_group_name| may not be empty. + // // Group probabilities that are later supplied must sum to less than or equal // to the total_probability. Arguments year, month and day_of_month specify // the expiration time. If the build time is after the expiration time then // the field trial reverts to the 'default' group. + // + // Using this constructor creates a startup-randomized FieldTrial. If you + // want a one-time randomized trial, call UseOneTimeRandomization() right + // after construction. FieldTrial(const std::string& name, Probability total_probability, const std::string& default_group_name, const int year, const int month, const int day_of_month); + // Changes the field trial to use one-time randomization, i.e. produce the + // same result for the current trial on every run of this client. Must be + // called right after construction. + // + // Before using this method, |FieldTrialList::EnableOneTimeRandomization()| + // must be called exactly once. + void UseOneTimeRandomization(); + + // Disables this trial, meaning it always determines the default group + // has been selected. May be called immediately after construction, or + // at any time after initialization (should not be interleaved with + // AppendGroup calls). Once disabled, there is no way to re-enable a + // trial. + void Disable(); + // Establish the name and probability of the next group in this trial. // Sometimes, based on construction randomization, this call may cause the // provided group to be *THE* group selected for use in this instance. @@ -124,18 +144,17 @@ class BASE_API FieldTrial : public RefCounted { // Return the randomly selected group number that was assigned. // Return kDefaultGroupNumber if the instance is in the 'default' group. // Note that this will force an instance to participate, and make it illegal - // to attempt to probabalistically add any other groups to the trial. + // to attempt to probabilistically add any other groups to the trial. int group(); - // If the field trial is not in an experiment, this returns the empty string. - // if the group's name is empty, a name of "_" concatenated with the group + // If the group's name is empty, a string version containing the group // number is used as the group name. std::string group_name(); // Return the default group name of the FieldTrial. std::string default_group_name() const { return default_group_name_; } - // Helper function for the most common use: as an argument to specifiy the + // Helper function for the most common use: as an argument to specify the // name of a HISTOGRAM. Use the original histogram name as the name_prefix. static std::string MakeName(const std::string& name_prefix, const std::string& trial_name); @@ -155,6 +174,9 @@ class BASE_API FieldTrial : public RefCounted { FRIEND_TEST(FieldTrialTest, Save); FRIEND_TEST(FieldTrialTest, DuplicateRestore); FRIEND_TEST(FieldTrialTest, MakeName); + FRIEND_TEST(FieldTrialTest, HashClientId); + FRIEND_TEST(FieldTrialTest, HashClientIdIsUniform); + FRIEND_TEST(FieldTrialTest, UseOneTimeRandomization); friend class base::FieldTrialList; @@ -168,8 +190,13 @@ class BASE_API FieldTrial : public RefCounted { // Get build time. static Time GetBuildTime(); + // Calculates a uniformly-distributed double between [0.0, 1.0) given + // a |client_id| and a |trial_name| (the latter is used as salt to avoid + // separate one-time randomized trials from all having the same results). + static double HashClientId(const std::string& client_id, + const std::string& trial_name); + // The name of the field trial, as can be found via the FieldTrialList. - // This is empty of the trial is not in the experiment. const std::string name_; // The maximum sum of all probabilities supplied, which corresponds to 100%. @@ -182,7 +209,7 @@ class BASE_API FieldTrial : public RefCounted { // The randomly selected probability that is used to select a group (or have // the instance not participate). It is the product of divisor_ and a random // number between [0, 1). - const Probability random_; + Probability random_; // Sum of the probabilities of all appended groups. Probability accumulated_group_probability_; @@ -193,13 +220,13 @@ class BASE_API FieldTrial : public RefCounted { // This is kNotFinalized if no group has been assigned. int group_; - // A textual name for the randomly selected group. If this Trial is not a - // member of an group, this string is empty. + // A textual name for the randomly selected group. Valid after |group()| + // has been called. std::string group_name_; - // When disable_field_trial_ is true, field trial reverts to the 'default' + // When enable_field_trial_ is false, field trial reverts to the 'default' // group. - bool disable_field_trial_; + bool enable_field_trial_; // When benchmarking is enabled, field trials all revert to the 'default' // group. @@ -234,7 +261,12 @@ class BASE_API FieldTrialList { }; // This singleton holds the global list of registered FieldTrials. - FieldTrialList(); + // + // |client_id| should be an opaque, diverse ID for this client that does not + // change between sessions, to enable one-time randomized trials. The empty + // string may be provided, in which case one-time randomized trials will + // not be available. + explicit FieldTrialList(const std::string& client_id); // Destructor Release()'s references to all registered FieldTrial instances. ~FieldTrialList(); @@ -246,15 +278,22 @@ class BASE_API FieldTrialList { // registered, or to retrieve a pointer to it from the global map. static FieldTrial* Find(const std::string& name); + // Returns the group number chosen for the named trial, or + // FieldTrial::kNotFinalized if the trial does not exist. static int FindValue(const std::string& name); + // Returns the group name chosen for the named trial, or the + // empty string if the trial does not exist. static std::string FindFullName(const std::string& name); - // Create a persistent representation of all FieldTrial instances for - // resurrection in another process. This allows randomization to be done in - // one process, and secondary processes can by synchronized on the result. - // The resulting string contains only the names, the trial name, and a "/" - // separator. + // Returns true if the named trial has been registered. + static bool TrialExists(const std::string& name); + + // Create a persistent representation of all FieldTrial instances and the + // |client_id()| state for resurrection in another process. This allows + // randomization to be done in one process, and secondary processes can by + // synchronized on the result. The resulting string contains the + // |client_id()|, the names, the trial name, and a "/" separator. static void StatesToString(std::string* output); // Use a previously generated state string (re: StatesToString()) augment the @@ -263,7 +302,9 @@ class BASE_API FieldTrialList { // is commonly used in a non-browser process, to carry randomly selected state // in a browser process into this non-browser process. This method calls // CreateFieldTrial to create the FieldTrial in the non-browser process. - // Currently only the group_name_ and name_ are restored. + // Currently only the group_name_ and name_ are restored, as well as the + // |client_id()| state, that could be used for one-time randomized trials + // set up only in child processes. static bool CreateTrialsInChildProcess(const std::string& prior_trials); // Create a FieldTrial with the given |name| and using 100% probability for @@ -303,6 +344,17 @@ class BASE_API FieldTrialList { // Return the number of active field trials. static size_t GetFieldTrialCount(); + // Returns true if you can call |FieldTrial::UseOneTimeRandomization()| + // without error, i.e. if a non-empty string was provided as the client_id + // when constructing the FieldTrialList singleton. + static bool IsOneTimeRandomizationEnabled(); + + // Returns an opaque, diverse ID for this client that does not change + // between sessions. + // + // Returns the empty string if one-time randomization is not enabled. + static const std::string& client_id(); + private: // A map from FieldTrial names to the actual instances. typedef std::map RegistrationList; @@ -317,7 +369,7 @@ class BASE_API FieldTrialList { // is created after that. static bool register_without_global_; - // A helper value made availabel to users, that shows when the FieldTrialList + // A helper value made available to users, that shows when the FieldTrialList // was initialized. Note that this is a singleton instance, and hence is a // good approximation to the start of the process. TimeTicks application_start_time_; @@ -326,6 +378,10 @@ class BASE_API FieldTrialList { base::Lock lock_; RegistrationList registered_; + // An opaque, diverse ID for this client that does not change + // between sessions, or the empty string if not initialized. + std::string client_id_; + // List of observers to be notified when a group is selected for a FieldTrial. ObserverList observer_list_; @@ -335,4 +391,3 @@ class BASE_API FieldTrialList { } // namespace base #endif // BASE_METRICS_FIELD_TRIAL_H_ - diff --git a/base/metrics/field_trial_unittest.cc b/base/metrics/field_trial_unittest.cc index 3357085..a790add 100644 --- a/base/metrics/field_trial_unittest.cc +++ b/base/metrics/field_trial_unittest.cc @@ -6,14 +6,18 @@ #include "base/metrics/field_trial.h" +#include "base/rand_util.h" #include "base/stringprintf.h" +#include "base/string_number_conversions.h" #include "testing/gtest/include/gtest/gtest.h" +#include + namespace base { class FieldTrialTest : public testing::Test { public: - FieldTrialTest() : trial_list_() { + FieldTrialTest() : trial_list_("client_id") { Time now = Time::NowFromSystemTime(); TimeDelta oneYear = TimeDelta::FromDays(365); Time::Exploded exploded; @@ -224,9 +228,10 @@ TEST_F(FieldTrialTest, Save) { new FieldTrial( "Some name", 10, "Default some name", next_year_, 12, 31); // There is no winner yet, so no textual group name is associated with trial. + // In this case, the trial should not be included. EXPECT_EQ("", trial->group_name_internal()); FieldTrialList::StatesToString(&save_string); - EXPECT_EQ("Some name/Default some name/", save_string); + EXPECT_EQ("", save_string); save_string.clear(); // Create a winning group. @@ -265,10 +270,10 @@ TEST_F(FieldTrialTest, Restore) { TEST_F(FieldTrialTest, BogusRestore) { EXPECT_FALSE(FieldTrialList::CreateTrialsInChildProcess("MissingSlash")); EXPECT_FALSE(FieldTrialList::CreateTrialsInChildProcess("MissingGroupName/")); - EXPECT_FALSE( - FieldTrialList::CreateTrialsInChildProcess("MissingFinalSlash/gname")); - EXPECT_FALSE( - FieldTrialList::CreateTrialsInChildProcess("/noname, only group/")); + EXPECT_FALSE(FieldTrialList::CreateTrialsInChildProcess( + "MissingFinalSlash/gname")); + EXPECT_FALSE(FieldTrialList::CreateTrialsInChildProcess( + "noname, only group/")); } TEST_F(FieldTrialTest, DuplicateRestore) { @@ -284,7 +289,8 @@ TEST_F(FieldTrialTest, DuplicateRestore) { EXPECT_TRUE(FieldTrialList::CreateTrialsInChildProcess(save_string)); // But it is an error to try to change to a different winner. - EXPECT_FALSE(FieldTrialList::CreateTrialsInChildProcess("Some name/Loser/")); + EXPECT_FALSE(FieldTrialList::CreateTrialsInChildProcess( + "Some name/Loser/")); } TEST_F(FieldTrialTest, CreateFieldTrial) { @@ -321,4 +327,109 @@ TEST_F(FieldTrialTest, MakeName) { FieldTrial::MakeName("Histogram", "Field Trial")); } +TEST_F(FieldTrialTest, HashClientId) { + double results[] = { + FieldTrial::HashClientId("hi", "1"), + FieldTrial::HashClientId("there", "1"), + }; + ASSERT_NE(results[0], results[1]); + for (size_t i = 0; i < arraysize(results); ++i) { + ASSERT_LE(0.0, results[i]); + ASSERT_GT(1.0, results[i]); + } + + ASSERT_EQ(FieldTrial::HashClientId("yo", "1"), + FieldTrial::HashClientId("yo", "1")); + ASSERT_NE(FieldTrial::HashClientId("yo", "something"), + FieldTrial::HashClientId("yo", "else")); +} + +TEST_F(FieldTrialTest, HashClientIdIsUniform) { + // Choose a random start number but go sequentially from there, so + // that each test tries a different range but we never provide uniformly + // distributed input data. + int current_number = RandInt(0, std::numeric_limits::max()); + + // The expected value of a random distribution is the average over all + // samples as the number of samples approaches infinity. For a uniform + // distribution from [0.0, 1.0) this would be 0.5. + // + // We do kSamplesBetweenChecks at a time and check if the value has converged + // to a narrow interval around 0.5. A non-uniform distribution would likely + // converge at something different, or not converge consistently within this + // range (i.e. the test would start timing out occasionally). + int kSamplesBetweenChecks = 300; + int num_samples = 0; + double total_value = 0.0; + while (true) { + for (int i = 0; i < kSamplesBetweenChecks; ++i) { + total_value += FieldTrial::HashClientId( + IntToString(current_number++), "salt"); + num_samples++; + } + + double average = total_value / num_samples; + double kExpectedMin = 0.48; + double kExpectedMax = 0.52; + + if (num_samples > 1000 && + (average < kExpectedMin || average > kExpectedMax)) { + // Only printed once we have enough samples that it's very unlikely + // things haven't converged. + printf("After %d samples, the average was %f, outside the expected\n" + "range (%f, %f). We will add more samples and check after every\n" + "%d samples. If the average does not converge, something\n" + "is broken. If it does converge, the test will pass.\n", + num_samples, average, + kExpectedMin, kExpectedMax, kSamplesBetweenChecks); + } else { + // Success. + break; + } + } +} + +TEST_F(FieldTrialTest, UseOneTimeRandomization) { + // Simply asserts that two trials using one-time randomization + // that have different names, normally generate different results. + // + // Note that depending on the one-time random initialization, they + // _might_ actually give the same result, but we know that given + // the particular client_id we use for unit tests they won't. + scoped_refptr trials[] = { + new FieldTrial("one", 100, "default", next_year_, 1, 1), + new FieldTrial("two", 100, "default", next_year_, 1, 1), + }; + + for (size_t i = 0; i < arraysize(trials); ++i) { + trials[i]->UseOneTimeRandomization(); + + for (int j = 0; j < 100; ++j) { + trials[i]->AppendGroup("", 1); + } + } + + // The trials are most likely to give different results since they have + // different names. + ASSERT_NE(trials[0]->group(), trials[1]->group()); + ASSERT_NE(trials[0]->group_name(), trials[1]->group_name()); +} + +TEST_F(FieldTrialTest, DisableImmediately) { + FieldTrial* trial = + new FieldTrial("trial", 100, "default", next_year_, 12, 31); + trial->Disable(); + ASSERT_EQ("default", trial->group_name()); + ASSERT_EQ(FieldTrial::kDefaultGroupNumber, trial->group()); +} + +TEST_F(FieldTrialTest, DisableAfterInitialization) { + FieldTrial* trial = + new FieldTrial("trial", 100, "default", next_year_, 12, 31); + trial->AppendGroup("non_default", 100); + ASSERT_EQ("non_default", trial->group_name()); + trial->Disable(); + ASSERT_EQ("default", trial->group_name()); +} + } // namespace base diff --git a/base/rand_util.cc b/base/rand_util.cc index a71bb0c..b823fa0 100644 --- a/base/rand_util.cc +++ b/base/rand_util.cc @@ -24,6 +24,10 @@ int RandInt(int min, int max) { } double RandDouble() { + return BitsToOpenEndedUnitInterval(base::RandUint64()); +} + +double BitsToOpenEndedUnitInterval(uint64 bits) { // We try to get maximum precision by masking out as many bits as will fit // in the target type's mantissa, and raising it to an appropriate power to // produce output in the range [0, 1). For IEEE 754 doubles, the mantissa @@ -31,7 +35,7 @@ double RandDouble() { COMPILE_ASSERT(std::numeric_limits::radix == 2, otherwise_use_scalbn); static const int kBits = std::numeric_limits::digits; - uint64 random_bits = base::RandUint64() & ((GG_UINT64_C(1) << kBits) - 1); + uint64 random_bits = bits & ((GG_UINT64_C(1) << kBits) - 1); double result = ldexp(static_cast(random_bits), -1 * kBits); DCHECK_GE(result, 0.0); DCHECK_LT(result, 1.0); diff --git a/base/rand_util.h b/base/rand_util.h index 4e902da..d10cc8b 100644 --- a/base/rand_util.h +++ b/base/rand_util.h @@ -29,6 +29,10 @@ BASE_API uint64 RandGenerator(uint64 max); // Returns a random double in range [0, 1). Thread-safe. BASE_API double RandDouble(); +// Given input |bits|, convert with maximum precision to a double in +// the range [0, 1). Thread-safe. +BASE_API double BitsToOpenEndedUnitInterval(uint64 bits); + // Returns a random string of the specified length. BASE_API std::string RandBytesAsString(size_t length); -- cgit v1.1