https://gcc.gnu.org/bugzilla/show_bug.cgi?id=110045

            Bug ID: 110045
           Summary: std::normal_distribution<float> (and likely others)
                    give wrong min() and max() values
           Product: gcc
           Version: 12.2.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: libstdc++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: gccbugs at elkpod dot com
  Target Milestone: ---

>From my (non-expert) reading of the C++ spec (I'm using
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/n4950.pdf for the
current 2023-05-10 draft), the min() and max() methods for
std::normal_distribution<float> are returning incorrect values.

"28.5.3.6 Random number distribution requirements" says that "A class D meets
the requirements of a random number distribution if the expressions shown in
Table 97 are valid and have the indicated semantics", and that in Table 97 "x"
is a "(possibly const) value[s] of D", and that "x.min()" "Returns glb", and
that "x.max()" "Returns lub", and that "glb and lub are values of T
respectively corresponding to the greatest lower bound and the least upper
bound on the values potentially returned by d's operator(), as determined by
the current values of d's parameters".

By my reading, this means that if I have "std::normal_distribution<float> norm
{0, 1};", then "norm.max()" should return the least upper bound on the values
potentially returned by norm's operator(). It does not seem to do that. 

For example, the highest value I was able to get norm() to generate was
0x1.ff13ccp+2 (== 7.985583). norm.max() reports 0x1.fffffep+127 (==
340282346638528859811704183484516925440.000000). While the values returned are
technically valid bounds, they do not seem to be the least upper or greatest
lower. I could find no Generator outputs which produced a value of
0x1.fffffep+127 from std::normal_distribution<float>.

It is possible that my interpretation of the spec is wrong, and that somehow
the min/max values should encompass all possible instantiations of
std::normal_distribution, or some other loophole may exist. I could not find a
better forum to find the answer to that; sorry.

I have looked at the implementation of std::normal_distribution, and written
the following code to generate what I believe to be the actual extreme values
that are "potentially returned by norm's operator()".

Reproduction code:
#include <cstdio>
#include <cstdint>
#include <random>

int32_t offsets[8] = { -1, +0, +0, -1, +0, +1, +1, +0 };
class SimpleGen {
    using result_type = uint32_t;
 public:
    result_type val, ctr = 0;
    static constexpr result_type min() { return 0; }
    static constexpr result_type max() { return 0xffffff; }
    result_type operator()()           { val = 0x800000 + offsets[(ctr++)%8];
                                         printf("\tG 0x%06x\n", val);
                                         return val; }
};

int main(void) {
    SimpleGen gen;
    std::normal_distribution<float> norm {0, 1};

    for (int i = 0; i < 8; i++) {
        float r = norm(gen);
        printf("%d %f %a\n", i, r, r);
    }
    printf("\n%f %a\n%f %a\n", norm.min(), norm.min(), norm.max(), norm.max());
}

Build output:
$ g++-12.2 -Wall -Wextra -o normdist2 normdist2.c -fno-strict-aliasing -fwrapv
-fno-aggressive-loop-optimizations -fsanitize=undefined
$ echo $?
0

Output:
        G 0x7fffff
        G 0x800000
0 0.000000 0x0p+0
1 -7.985583 -0x1.ff13ccp+2
        G 0x800000
        G 0x7fffff
2 -7.985583 -0x1.ff13ccp+2
3 0.000000 0x0p+0
        G 0x800000
        G 0x800001
4 7.985583 0x1.ff13ccp+2
5 0.000000 0x0p+0
        G 0x800001
        G 0x800000
6 0.000000 0x0p+0
7 7.985583 0x1.ff13ccp+2

-340282346638528859811704183484516925440.000000 -0x1.fffffep+127
340282346638528859811704183484516925440.000000 0x1.fffffep+127

Expected output:
        G 0x7fffff
        G 0x800000
0 0.000000 0x0p+0
1 -7.985583 -0x1.ff13ccp+2
        G 0x800000
        G 0x7fffff
2 -7.985583 -0x1.ff13ccp+2
3 0.000000 0x0p+0
        G 0x800000
        G 0x800001
4 7.985583 0x1.ff13ccp+2
5 0.000000 0x0p+0
        G 0x800001
        G 0x800000
6 0.000000 0x0p+0
7 7.985583 0x1.ff13ccp+2

-7.985583 -0x1.ff13ccp+2
7.985583 0x1.ff13ccp+2

$ g++-12.2 -v
Using built-in specs.
COLLECT_GCC=g++-12.2
COLLECT_LTO_WRAPPER=/usr/local/libexec/gcc/x86_64-slackware-linux/12.2.0/lto-wrapper
Target: x86_64-slackware-linux
Configured with: ../gcc-12.2.0/configure --prefix=/usr/local
--program-suffix=-12.2 -enable-languages=c,c++,lto --enable-lto
--disable-multilib --with-gnu-ld --enable-threads --verbose
--target=x86_64-slackware-linux --build=x86_64-slackware-linux
--host=x86_64-slackware-linux --enable-tls --with-fpmath=avx
--enable-__cxa_atexit --enable-gnu-indirect-function --enable-bootstrap
--enable-libssp
Thread model: posix
Supported LTO compression algorithms: zlib
gcc version 12.2.0 (GCC)

Reply via email to