简体   繁体   English

当使用float精度时,C ++ random为相同的Mersenne Twister种子产生不同的数字

[英]C++ random yields different numbers for same Mersenne Twister seed when using float precision

I need to run reproducible Monte Carlo runs. 我需要运行可重现的蒙特卡罗运行。 That means I use a known seed that I store with my results, and use that seed if I need to run the same problem instance using the same random numbers. 这意味着我使用我存储的已知种子和我的结果,如果我需要使用相同的随机数运行相同的问题实例,则使用该种子。 This is common practice. 这是常见的做法。

While investigating the effects of numeric precision, I ran into the following issue: For the same Mersenne Twister seed, std::uniform_real_distribution<float>(-1, 1) returns different numbers than std::uniform_real_distribution<double>(-1, 1) and std::uniform_real_distribution<long double>(-1, 1) , as the following example shows: 在研究数值精度的影响时,我遇到了以下问题:对于相同的Mersenne Twister种子, std::uniform_real_distribution<float>(-1, 1)返回的数字不同于std::uniform_real_distribution<double>(-1, 1)std::uniform_real_distribution<long double>(-1, 1) ,如下例所示:

#include <iomanip>
#include <iostream>
#include <random>

template < typename T >
void numbers( int seed ) {
  std::mt19937                        gen( seed );
  std::uniform_real_distribution< T > dis( -1, 1 );
  auto p = std::numeric_limits< T >::max_digits10;
  std::cout << std::setprecision( p ) << std::scientific << std::setw( p + 7 )
            << dis( gen ) << "\n"
            << std::setw( p + 7 ) << dis( gen ) << "\n"
            << std::setw( p + 7 ) << dis( gen ) << "\n"
            << "**********\n";
}

int main() {
  int seed = 123;
  numbers< float >( seed );
  numbers< double >( seed );
  numbers< long double >( seed );
}

Result: 结果:

$ /usr/bin/clang++ -v
Apple LLVM version 10.0.0 (clang-1000.11.45.5)
Target: x86_64-apple-darwin18.2.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin

$ /usr/bin/clang++ bug.cpp -std=c++17
$ ./a.out 
 3.929383755e-01
 4.259105921e-01
-4.277213216e-01
**********
 4.25910643160561708e-01
-1.43058149942132062e-01
 3.81769702875451866e-01
**********
 4.259106431605616525145e-01
-1.430581499421320209545e-01
 3.817697028754518623166e-01
**********

As you can see, double and long double both start around at the same number (save precision differences) and continue yielding the same values. 正如您所看到的, doublelong double都以相同的数字开始(保存精度差异)并继续产生相同的值。 On the other hand, float starts off with a completely different number, and its second number is similar to the first number produced by double and long double . 另一方面, float以完全不同的数字开始,其第二个数字类似于doublelong double产生的第一个数字。

Do you see the same behavior in your compiler? 你在编译器中看到了相同的行为吗? Is there a reason for this unexpected (to me) discrepancy? 这种意外(对我而言)是否存在差异?

Approach 途径

The responses make it clear that there's no reason to expect that values generated with different underlying precision will be the same. 响应清楚地表明,没有理由期望使用不同底层精度生成的值将是相同的。

The approach that I'll take to generate reproducible runs will be to always generate values at the highest precision possible, and cast them to lower precision on demand (eg, float x = y , where y is double or long double , as the case may be). 我将采用可生成可重复运行的方法是始终以尽可能高的精度生成值,并根据需要将它们转换为较低的精度(例如, float x = y ,其中ydoublelong double ,视情况而定也许)。

Each distribution will generate floating point numbers by grabbing a sufficient number of (pseudo)random bits from the underlying Mersenne Twister and then producing uniformly distributed floating point numbers from it. 每个分布将通过从底层Mersenne Twister中获取足够数量的(伪)随机位,然后从中生成均匀分布的浮点数来生成浮点数。

There are only two ways that an implementation could fulfill your expectation of "same algorithm, therefore same results (minus precision)": 实现只有两种方式可以满足您对“相同算法,因此相同结果(减去精度)”的期望:

  1. std::uniform_real_distribution<long double>(-1, 1) is only as random as std::uniform_real_distribution<float>(-1, 1) . std::uniform_real_distribution<long double>(-1, 1)仅与std::uniform_real_distribution<float>(-1, 1)一样随机。 More to the point, the former has exactly as many possible outputs as the latter. 更重要的是,前者具有与后者一样多的可能输出。 If the latter can produce more different values than the former, then it needs to consume more bits of randomness from the underlying Mersenne Twister. 如果后者可以产生比前者更多的不同值,那么它需要从底层的Mersenne Twister中消耗更多的随机性。 If it cannot - well, what's the point of using it (and how would it still be "uniform")? 如果它不能 - 那么,使用它的重点是什么(它将如何仍然是“统一的”)?

  2. std::uniform_real_distribution<float>(-1, 1) consumes (and mostly discards) exactly as many bits of randomness from the underlying Mersenne Twister as std::uniform_real_distribution<long double>(-1, 1) . std::uniform_real_distribution<float>(-1, 1)消耗(并且主要是丢弃)与底层Mersenne Twister一样多的随机性位作为std::uniform_real_distribution<long double>(-1, 1) That would be very wasteful and inefficient. 那将是非常浪费和低效的。

Since no sane implementation will do either of the above, std::uniform_real_distribution<long double>(-1, 1) will advance the underlying Mersenne Twister by more steps than std::uniform_real_distribution<float>(-1, 1) for each generated number. 由于没有理智的实现可以执行上述任一操作,因此std::uniform_real_distribution<long double>(-1, 1)将使基础Mersenne Twister比std::uniform_real_distribution<float>(-1, 1)为每个步骤推进生成的号码。 That will of course change the progression of the random numbers. 这当然会改变随机数的进展。 This also explains why the long double and double variant are relatively close together: They share most of their random bits initially (whereas float likely requires a lot fewer bits and thus diverges quicker). 这也解释了为什么long doubledouble变量相对接近:它们最初共享大部分随机位(而float可能需要更少的位,从而更快地发散)。

Initializing a random number generator to a specific seed will specify the sequence of random bits it puts out. 将随机数生成器初始化为特定种子将指定它输出的随机位序列。 However, you aren't using those bits the same way in each case. 但是,在每种情况下,您都不会以相同的方式使用这些位。 A std::uniform_real_distribution<double> has a larger possibility space than std::uniform_real_distribution<float> (assuming sizeof(double) > sizeof(float) on your platform) so it will need to consume a larger quantity of random bits to generate a fully uniform distribution. std::uniform_real_distribution<double>std::uniform_real_distribution<float> (假设你的平台上有sizeof(double) > sizeof(float)有更大的可能性空间,因此它需要消耗更多的随机位来生成完全均匀的分布。

The first consequence is that the pseudo-random sequence of bits will have a different interpretation for different distribution types. 第一个结果是伪随机比特序列对于不同的分布类型将具有不同的解释。 The second consequence is each distribution moves a different number of bits down the pseudo-random sequence whenever is produces a value, meaning following numbers won't be at the same point in the pseudo-random bit sequence. 第二个结果是每当产生一个值时,每个分布沿着伪随机序列向下移动不同数量的比特,这意味着跟随数字将不在伪随机比特序列中的相同点。

The solution to your problem is to always use the same type of distribution. 您的问题的解决方案是始终使用相同类型的分发。 If you want to compare the result of using lower precision values with using higher precision values, only generate values with the highest precision and truncate them down when needed. 如果要将使用较低精度值的结果与使用较高精度值进行比较,则仅生成具有最高精度的值,并在需要时将其截断。

Just to add to excellent @MaxLanghof answer with more specifics: 只是为了增加优秀的@MaxLanghof答案,并提供更多细节:

For double code would do something like this - generate u64 integer, and use 53bits of it to make float, along the lines 对于双重代码,可以执行类似的操作 - 生成u64整数,并使用53位来沿浮点生成浮点数

double r = (u64 >> 11) * (1.0 / (uint64_t(1) << 53));

For long double, assuming Intel 80bits format, with 64bit mantissa, it will do about the same, get 64bits, return back long double. 对于长双,假设Intel 80bits格式,64位尾数,它将做大约相同,得到64位,返回长双。

long double r = u64 * (1.0 / (uint64_t(1) << 64)); // pseudocode

64bit of randomness are consumed in both cases, thus you see same values. 在这两种情况下都会消耗64位随机性,因此您可以看到相同的值。

In case of a float, 32bits are used to make single float 在浮点数的情况下,使用32位来制作单个浮点数

float r = (u32 >> 8) * (1.0f / (uint32_t(1) << 24));

32bits of randomness are consumed, and another 32bits are used for next number, which together with endianness makes second float about the same as first double/long double. 消耗32比特的随机性,并且另外32比特用于下一个数字,其与字节顺序一起使第二个浮点数与第一个双/长双数相同。

Link: http://xoshiro.di.unimi.it/ 链接: http//xoshiro.di.unimi.it/

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM