Meines Erachtens gibt es drei Möglichkeiten, dies zu tun,
1) Der einfache Weg.
double rand_easy(void)
{ return (double) rand() / (RAND_MAX + 1.0);
}
2) Der sichere Weg (standardkonform).
double rand_safe(void)
{
double limit = pow(2.0, DBL_MANT_DIG);
double denom = RAND_MAX + 1.0;
double denom_to_k = 1.0;
double numer = 0.0;
for ( ; denom_to_k < limit; denom_to_k *= denom )
numer += rand() * denom_to_k;
double result = numer / denom_to_k;
if (result == 1.0)
result -= DBL_EPSILON/2;
assert(result != 1.0);
return result;
}
3) Der individuelle Weg.
Durch die Eliminierung rand()
müssen wir uns nicht mehr um die Eigenheiten einer bestimmten Version kümmern, was uns mehr Spielraum für unsere eigene Implementierung gibt.
Anmerkung: Die Periode des hier verwendeten Generators beträgt ≅ 1,8e+19.
#define RANDMAX (-1ULL)
uint64_t custom_lcg(uint_fast64_t* next)
{ return *next = *next * 2862933555777941757ULL + 3037000493ULL;
}
uint_fast64_t internal_next;
void seed_fast(uint64_t seed)
{ internal_next = seed;
}
double rand_fast(void)
{
#define SHR_BIT (64 - (DBL_MANT_DIG-1))
union {
double f; uint64_t i;
} u;
u.f = 1.0;
u.i = u.i | (custom_lcg(&internal_next) >> SHR_BIT);
return u.f - 1.0;
}
Unabhängig von der Wahl kann die Funktionalität wie folgt erweitert werden,
double rand_dist(double min, double max)
{ return rand_fast() * (max - min) + min;
}
double rand_open(void)
{ return rand_dist(DBL_EPSILON, 1.0);
}
double rand_closed(void)
{ return rand_dist(0.0, 1.0 + DBL_EPSILON);
}
Abschließende Anmerkungen: Die schnelle Version - obwohl in C geschrieben - kann für die Verwendung in C++ angepasst werden, um als Ersatz für std::generate_canonical
und funktioniert für jeden Generator, der Werte mit ausreichend signifikanten Bits ausgibt.
Die meisten 64-Bit-Generatoren nutzen die Vorteile ihrer vollen Breite, so dass dies wahrscheinlich ohne Modifikation (Shift-Anpassung) verwendet werden kann. z.B. funktioniert dies so wie es ist mit dem std::mt19937_64
Motor.