126

I always thought random numbers would lie between zero and one, without 1, i.e. they are numbers from the half-open interval [0,1). The documention on cppreference.com of std::generate_canonical confirms this.

However, when I run the following program:

#include <iostream>
#include <limits>
#include <random>

int main()
{
    std::mt19937 rng;

    std::seed_seq sequence{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    rng.seed(sequence);
    rng.discard(12 * 629143 + 6);

    float random = std::generate_canonical<float,
                   std::numeric_limits<float>::digits>(rng);

    if (random == 1.0f)
    {
        std::cout << "Bug!\n";
    }

    return 0;
}

It gives me the following output:

Bug!

i.e. it generates me a perfect 1, which causes problems in my MC integration. Is that valid behavior or is there an error on my side? This gives the same output with G++ 4.7.3

g++ -std=c++11 test.c && ./a.out

and clang 3.3

clang++ -stdlib=libc++ -std=c++11 test.c && ./a.out

If this is correct behavior, how can I avoid 1?

Edit 1: G++ from git seems to suffer from the same problem. I am on

commit baf369d7a57fb4d0d5897b02549c3517bb8800fd
Date:   Mon Sep 1 08:26:51 2014 +0000

and compiling with ~/temp/prefix/bin/c++ -std=c++11 -Wl,-rpath,/home/cschwan/temp/prefix/lib64 test.c && ./a.out gives the same output, ldd yields

linux-vdso.so.1 (0x00007fff39d0d000)
libstdc++.so.6 => /home/cschwan/temp/prefix/lib64/libstdc++.so.6 (0x00007f123d785000)
libm.so.6 => /lib64/libm.so.6 (0x000000317ea00000)
libgcc_s.so.1 => /home/cschwan/temp/prefix/lib64/libgcc_s.so.1 (0x00007f123d54e000)
libc.so.6 => /lib64/libc.so.6 (0x000000317e600000)
/lib64/ld-linux-x86-64.so.2 (0x000000317e200000)

Edit 2: I reported the behavior here: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63176

Edit 3: The clang team seems to be aware of the problem: http://llvm.org/bugs/show_bug.cgi?id=18767

Ganesh Sittampalam
  • 28,821
  • 4
  • 79
  • 98
cschwan
  • 3,283
  • 3
  • 22
  • 32
  • 1
    That seems wrong, indeed. – R. Martinho Fernandes Sep 04 '14 at 14:54
  • Test it on g++, clang and VS2013. Only VS2013 got it right Clang and g++ are generating 1.0. Seems like a double bug both gcc and Clang are wrong. – 101010 Sep 04 '14 at 14:58
  • The compiler has nothing to do with the generated number, the stdlib is important - I'm guessing you're using libstdc++ for GCC and Clang. If you can, try libc++. – Xeo Sep 04 '14 at 14:58
  • Oh thats interesting. Whats the output of VS2013? How is different from GCC/clang? – cschwan Sep 04 '14 at 14:58
  • Forget what I said, VS2013 generates values >> 1.0. – 101010 Sep 04 '14 at 15:01
  • @Xeo: I didn't, but even with libc++ it is the same. – cschwan Sep 04 '14 at 15:02
  • I cannot reproduce this issue with GCC. As Xeo said, it's not the compiler per se, but the standard library. Try to update it. My rather recent version doesn't have this bug. – stefan Sep 04 '14 at 15:04
  • 1
    @Niall It is perfectly accurate. A simple `if ( random == 1.0f ) { std::printf("Bad\n"); }` would have been enough. – stefan Sep 04 '14 at 15:06
  • 22
    @David Lively `1.f == 1.f` in all cases (what all cases are there? I don't even seen any variables in `1.f == 1.f`; there's only one case here: `1.f == 1.f`, and that is invariably `true`). Please don't spread this myth further. Floating point comparisons are always exact. – R. Martinho Fernandes Sep 04 '14 at 15:09
  • 1
    Clearly you don't understand how floating point numbers work. – R. Martinho Fernandes Sep 04 '14 at 15:10
  • 15
    @DavidLively: No, it's not. The comparison is always exact. It's your operands that may not be exact _if_ they are calculated and not literals. – Lightness Races in Orbit Sep 04 '14 at 15:10
  • Maybe I'm wrong (not an expert) but wouldn't there be be a range of values (between the last representable value before 1.0 and 1.0) which would be left out unless occasionally 1.0 is output as the nearest rounded value? I mean the range [1.0 - epsilon, 1.0). Would that not sometimes round down to (1.0 - epsilon) and other times round up to 1.0? – Galik Sep 04 '14 at 15:12
  • 2
    @Galik any positive number below 1.0 is a valid result. 1.0 is not. It's as simple as that. Rounding is irrelevant: the code gets a random number and doesn't perform any rounding on it. – R. Martinho Fernandes Sep 04 '14 at 15:13
  • Hm, the GCC from git (updated a few days ago) gives the same, but I am not sure I am using the newest libstdc++. Is it enough to prefix `./a.out` with `LD_LIBRARY_PATH` pointing to the corresponding path? – cschwan Sep 04 '14 at 15:13
  • 1
    @LightnessRacesinOrbit, some clarity, thanks. – Niall Sep 04 '14 at 15:14
  • 1
    @cschwan: Could be, but check with `ldd`. – Lightness Races in Orbit Sep 04 '14 at 15:15
  • 7
    @DavidLively he's saying that there is only one value that compares equal to 1.0. That value is 1.0. Values close to 1.0 don't compare equal to 1.0. It doesn't matter what the generation function does: if it returns 1.0 it will compare equal to 1.0. If it doesn't return 1.0 it will not compare equal to 1.0. Your example using `abs(random - 1.f) < numeric_limits::epsilon` checks if the result is *close to 1.0*, which is totally wrong in this context: there are numbers close to 1.0 that are valid results here, namely, all those that are less than 1.0. – R. Martinho Fernandes Sep 04 '14 at 15:23
  • 2
    @R. Martinho Fernandes Okay I accept that. But to clarify my point I was talking about the inevitable rounding that happens within the random number generating algorithm due to hardware limitations on representing the actual number. It means that numbers extremely close to 1.0 either can not be represented at all or else are rounded down to (1.0 - epsilon) giving an over-representation of those values. Or, at least, that's how it seems to me. – Galik Sep 04 '14 at 15:28
  • 4
    @Galik Yes, there will be trouble implementing that. But that trouble is for the implementer to deal with. The user must never see a 1.0, and the user must always see an equal distribution of all the results. – R. Martinho Fernandes Sep 04 '14 at 15:30
  • @R.MartinhoFernandes Okay, I am apparently having a Monday, on Thursday. I am often wrong. This may be one of those times. I'll deleting my earlier comments as they appear to be misleading. – 3Dave Sep 04 '14 at 15:42
  • Should the title of this question be "is 1.0 a valid output from std::generate_canonical"? As it stands it's rather ill-posed. – Ganesh Sittampalam Sep 05 '14 at 07:34
  • Well, it seems to attract a lot of viewers ;) - but yes, you're right. – cschwan Sep 05 '14 at 07:46
  • 1
    It seems that both GCC and Clang got it wrong and produce 1.0 and this is a bug that should be reported. For completeness sake, I tried the code posted by the OP with VS2013 I get numbers that are >> 1. Searched a little bit and found that this is a bug for VS2013 that's still active ([bug report](https://connect.microsoft.com/VisualStudio/feedback/details/811611/std-generate-canonical-does-not-generate-random-number-in-range-0-1)). – 101010 Sep 04 '14 at 15:10

3 Answers3

123

The problem is in mapping from the codomain of std::mt19937 (std::uint_fast32_t) to float; the algorithm described by the standard gives incorrect results (inconsistent with its description of the output of the algorithm) when loss of precision occurs if the current IEEE754 rounding mode is anything other than round-to-negative-infinity (note that the default is round-to-nearest).

The 7549723rd output of mt19937 with your seed is 4294967257 (0xffffffd9u), which when rounded to 32-bit float gives 0x1p+32, which is equal to the max value of mt19937, 4294967295 (0xffffffffu) when that is also rounded to 32-bit float.

The standard could ensure correct behavior if it were to specify that when converting from the output of the URNG to the RealType of generate_canonical, rounding is to be performed towards negative infinity; this would give a correct result in this case. As QOI, it would be good for libstdc++ to make this change.

With this change, 1.0 will no longer be generated; instead the boundary values 0x1.fffffep-N for 0 < N <= 8 will be generated more often (approximately 2^(8 - N - 32) per N, depending on the actual distribution of MT19937).

I would recommend to not use float with std::generate_canonical directly; rather generate the number in double and then round towards negative infinity:

    double rd = std::generate_canonical<double,
        std::numeric_limits<float>::digits>(rng);
    float rf = rd;
    if (rf > rd) {
      rf = std::nextafter(rf, -std::numeric_limits<float>::infinity());
    }

This problem can also occur with std::uniform_real_distribution<float>; the solution is the same, to specialize the distribution on double and round the result towards negative infinity in float.

ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • If the output of the generator is truncated to 24 bits and divided by 16777216f, the result should be one of 16777216 equally-likely values. If the generator isn't truncated to 24 bits, individual values above 0.5 will occur about twice as often as those between 0.25 and 0.5. – supercat Sep 04 '14 at 17:20
  • @supercat no truncation occurs; the output of the URNG is cast to the target type and then divided by the URNG's max, also cast to the target type. The facility `generate_canonical` isn't aiming for individual values in [0, 1) to occur equally often; it's aiming for uniform distribution. – ecatmur Sep 04 '14 at 17:34
  • I would think the best uniform distribution for `float` would be computed by taking a 23-bit number, adding 0.5, and dividing by 8388608. For any `float` values 1.0 <= a <= b <= 2.0, the expected average of returned numbers in the range (a-1.0,b-1.0) would be precisely (a+b-2)/2. I'm not sure how well that property can be upheld if one doesn't truncate the random number generator before division, no matter which rounding mode one uses. – supercat Sep 04 '14 at 18:25
  • @supercat yes, I see - e.g. for a=1.25, b=1.75, all the values between 0.25 and 0.5 and between 0.5 and 0.75 would occur, with the former being twice as dense so occurring half as often, but with an upward bias of 2^-25 as a result in the lower half-range. Your suggestion would definitely have better statistics. – ecatmur Sep 04 '14 at 19:17
  • I've noticed a tendency sometimes for people to assume that the precision of a range-bounded number should be relative to its magnitude, rather than to the magnitude of the range. Some "sine" functions, for example, go out of their way to yield absurd levels of "precision" at angles where the sine is nearly zero. If there were a function to compute 1-cos(x), such a function could be meaningfully precise to within 1E-32, but for values of x near pi, any precision of sin(x) finer than about 1E-16 is meaningless. – supercat Sep 04 '14 at 19:55
  • If code is expecting a range to be uniformly distributed from 0 to 1 and can deal with a gap of 1/16777216 between values greater than 0.5, it should be able to deal with such a gap anywhere in the range. Further, the benefits of eliminating bias would in most applications outweigh any harm caused by doubling the gap to 1/8388608. In some rare cases there could be value to a random method which would return 23 or 24 meaningful bits of mantissa regardless of the magnitude of the value, but that should be handled by using an exponential distribution to select the magnitude, ... – supercat Sep 04 '14 at 20:01
  • ...and then setting the mantissa by multiplying a 1/2^n fraction by a integer from 8388608 to 16777215. – supercat Sep 04 '14 at 20:03
  • What does QOI stand for? – user Sep 04 '14 at 20:08
  • 2
    @user quality of implementation - all the things that make one conformant implementation better than another e.g. performance, behavior in edge cases, helpfulness of error messages. – ecatmur Sep 04 '14 at 20:09
  • 2
    @supercat: To digress a bit, there actually are good reasons to try to make sine functions as accurate as possible for small angles, e.g. because small errors in sin(x) can turn into large errors in sin(x)/x (which [occurs quite often](http://en.wikipedia.org/wiki/Sinc_function) in real-world calculations) when x is close to zero. The "extra precision" near multiples of π is generally just a side effect of that. – Ilmari Karonen Sep 04 '14 at 20:16
  • 1
    @IlmariKaronen: For sufficiently small angles, sin(x) is simply x. My squawk at Java's sine function relates to is with angles that are near multiples of pi. I would posit that 99% of the time, when code asks for `sin(x)`, what it *really* wants is the sine of (π/Math.PI) times x. The people maintaining Java insist that it's better to have a slow math routine report that the sine of Math.PI is difference between π and Math.PI than to have it report a value which is slightly less, notwithstanding that in 99% of applications it would be better... – supercat Sep 04 '14 at 20:47
  • ...to scale up the argument by a factor of π/Math.PI, such that an argument of Math.PI would yield a sine of zero. If code wishes to compute the sine of an angle expressed in degrees, multiplying by Math.PI/180.0 before performing argument reduction to a range of +/-45 degrees will lose some precision, but the resulting precision would still be acceptable. In such cases, however, the extra code the JVM added to make the results "precise" in fact makes the results less accurate than they would be in the absence of such code. – supercat Sep 04 '14 at 20:55
  • @supercat: (double)π/Math.PI == 1.0. (The bits that make the ratio useful would get lopped off.) Aside from that, though, an implementation of a well-known standard math function shouldn't try to guess intent. When i ask for `sin(x)`, i want `sin(x)`. I'd prefer not even losing precision to rounding errors. But at the very least, i should get sin(exactly x). Assumptions about `Math.PI` throw off the result if i didn't multiply by `Math.PI`. It's valid and useful for some other function to make such assumptions, but it shouldn't misrepresent itself as the true sine function. – cHao Sep 05 '14 at 18:43
  • @cHao: Can you offer up *any realistic, non-contrived, scenario* in which, a slower implementation of sin(x) which always returns a value which is within 0.75 lsb of the sine of the exact value of x would be more useful than a faster one which always returns a value that is within 0.5lsb of the exact sine of some value which is within 0.5lsb of x? For that matter, in what non-contrived scenarios would the value 884279719003555/281474976710656 be computed without rounding errors? – supercat Sep 05 '14 at 19:14
  • @supercat: I didn't say i don't tolerate rounding errors; they're pretty much inevitable unless you only ever divide by powers of two. But of course, i would prefer they not exist, and unless performance is actually an issue, i'd prefer a slow, accurate result over a fast, sloppy one. As for the usefulness of being sloppy, honestly, i haven't looked too deeply into it. I just know that whenever possible, when i ask for X, i would very much like to be able to trust that i got X and not something that kinda sorta resembles X. – cHao Sep 05 '14 at 23:30
  • 3
    @ecatmur Suggestion; update this post to mention that `std::uniform_real_distribution` suffers from the same problem as a consequence of this. (So that people searching for uniform_real_distribution will have this Q/A come up). – M.M Nov 25 '14 at 21:53
  • 2
    @ecatmur, I'm not sure why you want to round towards negative infinity. Since `generate_canonical` should generate a number in the range `[0,1)`, and we're talking about an error where it generates 1.0 occasionally, wouldn't rounding towards zero be just as effective? – Marshall Clow Aug 26 '15 at 18:12
  • 1
    @MarshallClow yes, round towards zero would also work fine here. – ecatmur Aug 26 '15 at 18:52
  • @T.C. Is this issue fixed now? – green diod Dec 04 '16 at 08:40
  • "Without this change, not only will 1.0 be generated occasionally, but 0.0 will be generated half as often as it should." I don't think this is true. The issue here is floating-point rounding error, and at the 0-end there's no rounding involved; everything is exact. – T.C. Mar 28 '17 at 17:38
  • @T.C. yes, you're right, and the final division by `0x1p+32` is also exact; the space that becomes 1.0 is stolen from the boundary values `0x1.fffffep+N` for `24 <= N < 32`. I'll correct my answer. – ecatmur Mar 28 '17 at 18:54
39

According to the standard, 1.0 is not valid.

C++11 §26.5.7.2 Function template generate_canonical

Each function instantiated from the template described in this section 26.5.7.2 maps the result of one or more invocations of a supplied uniform random number generator g to one member of the specified RealType such that, if the values gi produced by g are uniformly distributed, the instantiation’s results tj , 0 ≤ tj < 1, are distributed as uniformly as possible as specified below.

Community
  • 1
  • 1
Yu Hao
  • 119,891
  • 44
  • 235
  • 294
-2

I just ran into a similar question with uniform_real_distribution, and here's how I interpret the Standard's parsimonious wording on the subject:

The Standard always defines math functions in terms of math, never in terms of IEEE floating-point (because the Standard still pretends that floating-point might not mean IEEE floating point). So, any time you see mathematical wording in the Standard, it's talking about real math, not IEEE.

The Standard says that both uniform_real_distribution<T>(0,1)(g) and generate_canonical<T,1000>(g) should return values in the half-open range [0,1). But these are mathematical values. When you take a real number in the half-open range [0,1) and represent it as IEEE floating-point, well, a significant fraction of the time it will round up to T(1.0).

When T is float (24 mantissa bits), we expect to see uniform_real_distribution<float>(0,1)(g) == 1.0f about 1 in 2^25 times. My brute-force experimentation with libc++ confirms this expectation.

template<class F>
void test(long long N, const F& get_a_float) {
    int count = 0;
    for (long long i = 0; i < N; ++i) {
        float f = get_a_float();
        if (f == 1.0f) {
            ++count;
        }
    }
    printf("Expected %d '1.0' results; got %d in practice\n", (int)(N >> 25), count);
}

int main() {
    std::mt19937 g(std::random_device{}());
    auto N = (1uLL << 29);
    test(N, [&g]() { return std::uniform_real_distribution<float>(0,1)(g); });
    test(N, [&g]() { return std::generate_canonical<float, 32>(g); });
}

Example output:

Expected 16 '1.0' results; got 19 in practice
Expected 16 '1.0' results; got 11 in practice

When T is double (53 mantissa bits), we expect to see uniform_real_distribution<double>(0,1)(g) == 1.0 about 1 in 2^54 times. I don't have the patience to test this expectation. :)

My understanding is that this behavior is fine. It may offend our sense of "half-open-rangeness" that a distribution claiming to return numbers "less than 1.0" can in fact return numbers that are equal to 1.0; but those are two different meanings of "1.0", see? The first is the mathematical 1.0; the second is the IEEE single-precision floating-point number 1.0. And we've been taught for decades not to compare floating-point numbers for exact equality.

Whatever algorithm you feed the random numbers into isn't going to care if it sometimes gets exactly 1.0. There's nothing you can do with a floating-point number except mathematical operations, and as soon as you do some mathematical operation, your code will have to deal with rounding. Even if you could legitimately assume that generate_canonical<float,1000>(g) != 1.0f, you still wouldn't be able to assume that generate_canonical<float,1000>(g) + 1.0f != 2.0f — because of rounding. You just can't get away from it; so why would we pretend in this single instance that you can?

Quuxplusone
  • 23,928
  • 8
  • 94
  • 159
  • 2
    I strongly disagree with this view. If the standard dictates values from a half-open interval and an implementation breaks this rule, the implementation is wrong. Unfortunately, as ecatmur correctly pointed out in his answer, the standard also dictates the algorithm which has a bug. This is also officially recognized here: http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#2524 – cschwan Sep 09 '17 at 17:02
  • @cschwan: My interpretation is that the implementation *is not* breaking the rule. The standard dictates values from [0,1); the implementation returns values from [0,1); some of those values happen to round up to IEEE `1.0f` but that's just unavoidable when you cast them to IEEE floats. If you want pure mathematical results, use a symbolic computation system; if you are trying to use IEEE floating-point to represent numbers that are within `eps` of 1, you are in a state of sin. – Quuxplusone Sep 09 '17 at 19:15
  • Hypothetical example that would be broken by this bug: divide something by `canonical - 1.0f`. For every representable float in `[0, 1.0)`, `x-1.0f` is non-zero. With exactly 1.0f, you can get a divide-by-zero instead of just a very tiny divisor. – Peter Cordes Jul 23 '20 at 09:21