Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

265 lines
7.3 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Random number generator
  4. //
  5. // $Workfile: $
  6. // $NoKeywords: $
  7. //===========================================================================//
  8. #include "vstdlib/random.h"
  9. #include <math.h>
  10. #include "dbg.h"
  11. #include "tier0/memdbgon.h"
  12. #define IA 16807
  13. #define IM 2147483647
  14. #define IQ 127773
  15. #define IR 2836
  16. #define NDIV (1+(IM-1)/NTAB)
  17. #define MAX_RANDOM_RANGE 0x7FFFFFFFUL
  18. // fran1 -- return a random floating-point number on the interval [0,1)
  19. //
  20. #define AM (1.0/IM)
  21. #define EPS 1.2e-7
  22. #define RNMX (1.0-EPS)
  23. //-----------------------------------------------------------------------------
  24. // globals
  25. //-----------------------------------------------------------------------------
  26. static CUniformRandomStream s_UniformStream;
  27. static CGaussianRandomStream s_GaussianStream;
  28. static IUniformRandomStream *s_pUniformStream = &s_UniformStream;
  29. //-----------------------------------------------------------------------------
  30. // Installs a global random number generator, which will affect the Random functions above
  31. //-----------------------------------------------------------------------------
  32. void InstallUniformRandomStream( IUniformRandomStream *pStream )
  33. {
  34. s_pUniformStream = pStream ? pStream : &s_UniformStream;
  35. }
  36. //-----------------------------------------------------------------------------
  37. // A couple of convenience functions to access the library's global uniform stream
  38. //-----------------------------------------------------------------------------
  39. void RandomSeed( int iSeed )
  40. {
  41. s_pUniformStream->SetSeed( iSeed );
  42. }
  43. float RandomFloat( float flMinVal, float flMaxVal )
  44. {
  45. return s_pUniformStream->RandomFloat( flMinVal, flMaxVal );
  46. }
  47. float RandomFloatExp( float flMinVal, float flMaxVal, float flExponent )
  48. {
  49. return s_pUniformStream->RandomFloatExp( flMinVal, flMaxVal, flExponent );
  50. }
  51. int RandomInt( int iMinVal, int iMaxVal )
  52. {
  53. return s_pUniformStream->RandomInt( iMinVal, iMaxVal );
  54. }
  55. float RandomGaussianFloat( float flMean, float flStdDev )
  56. {
  57. return s_GaussianStream.RandomFloat( flMean, flStdDev );
  58. }
  59. //-----------------------------------------------------------------------------
  60. //
  61. // Implementation of the uniform random number stream
  62. //
  63. //-----------------------------------------------------------------------------
  64. CUniformRandomStream::CUniformRandomStream()
  65. {
  66. SetSeed(0);
  67. }
  68. void CUniformRandomStream::SetSeed( int iSeed )
  69. {
  70. AUTO_LOCK( m_mutex );
  71. m_idum = ( ( iSeed < 0 ) ? iSeed : -iSeed );
  72. m_iy = 0;
  73. }
  74. int CUniformRandomStream::GenerateRandomNumber()
  75. {
  76. AUTO_LOCK( m_mutex );
  77. int j;
  78. int k;
  79. if (m_idum <= 0 || !m_iy)
  80. {
  81. if (-(m_idum) < 1)
  82. m_idum=1;
  83. else
  84. m_idum = -(m_idum);
  85. for ( j=NTAB+7; j>=0; j--)
  86. {
  87. k = (m_idum)/IQ;
  88. m_idum = IA*(m_idum-k*IQ)-IR*k;
  89. if (m_idum < 0)
  90. m_idum += IM;
  91. if (j < NTAB)
  92. m_iv[j] = m_idum;
  93. }
  94. m_iy=m_iv[0];
  95. }
  96. k=(m_idum)/IQ;
  97. m_idum=IA*(m_idum-k*IQ)-IR*k;
  98. if (m_idum < 0)
  99. m_idum += IM;
  100. j=m_iy/NDIV;
  101. // We're seeing some strange memory corruption in the contents of s_pUniformStream.
  102. // Perhaps it's being caused by something writing past the end of this array?
  103. // Bounds-check in release to see if that's the case.
  104. if (j >= NTAB || j < 0)
  105. {
  106. DebuggerBreakIfDebugging();
  107. Warning("CUniformRandomStream had an array overrun: tried to write to element %d of 0..31. Contact Tom or Elan.\n", j);
  108. // Ensure that NTAB is a power of two.
  109. COMPILE_TIME_ASSERT( ( NTAB & ( NTAB - 1 ) ) == 0 );
  110. // Clamp j.
  111. j &= NTAB - 1;
  112. }
  113. m_iy=m_iv[j];
  114. m_iv[j] = m_idum;
  115. return m_iy;
  116. }
  117. float CUniformRandomStream::RandomFloat( float flLow, float flHigh )
  118. {
  119. // float in [0,1)
  120. float fl = AM * GenerateRandomNumber();
  121. if (fl > RNMX)
  122. {
  123. fl = RNMX;
  124. }
  125. return (fl * ( flHigh - flLow ) ) + flLow; // float in [low,high)
  126. }
  127. float CUniformRandomStream::RandomFloatExp( float flMinVal, float flMaxVal, float flExponent )
  128. {
  129. // float in [0,1)
  130. float fl = AM * GenerateRandomNumber();
  131. if (fl > RNMX)
  132. {
  133. fl = RNMX;
  134. }
  135. if ( flExponent != 1.0f )
  136. {
  137. fl = powf( fl, flExponent );
  138. }
  139. return (fl * ( flMaxVal - flMinVal ) ) + flMinVal; // float in [low,high)
  140. }
  141. int CUniformRandomStream::RandomInt( int iLow, int iHigh )
  142. {
  143. //ASSERT(lLow <= lHigh);
  144. unsigned int maxAcceptable;
  145. unsigned int x = iHigh-iLow+1;
  146. unsigned int n;
  147. // If you hit either of these assert, you're not getting back the random number that you thought you were.
  148. Assert( x == iHigh-(int64)iLow+1 ); // Check that we didn't overflow int
  149. Assert( x-1 <= MAX_RANDOM_RANGE ); // Check that the values provide an acceptable range
  150. if (x <= 1 || MAX_RANDOM_RANGE < x-1)
  151. {
  152. Assert( iLow == iHigh ); // This is the only time it is OK to have a range containing a single number
  153. return iLow;
  154. }
  155. // The following maps a uniform distribution on the interval [0,MAX_RANDOM_RANGE]
  156. // to a smaller, client-specified range of [0,x-1] in a way that doesn't bias
  157. // the uniform distribution unfavorably. Even for a worst case x, the loop is
  158. // guaranteed to be taken no more than half the time, so for that worst case x,
  159. // the average number of times through the loop is 2. For cases where x is
  160. // much smaller than MAX_RANDOM_RANGE, the average number of times through the
  161. // loop is very close to 1.
  162. //
  163. maxAcceptable = MAX_RANDOM_RANGE - ((MAX_RANDOM_RANGE+1) % x );
  164. do
  165. {
  166. n = GenerateRandomNumber();
  167. } while (n > maxAcceptable);
  168. return iLow + (n % x);
  169. }
  170. //-----------------------------------------------------------------------------
  171. //
  172. // Implementation of the gaussian random number stream
  173. // We're gonna use the Box-Muller method (which actually generates 2
  174. // gaussian-distributed numbers at once)
  175. //
  176. //-----------------------------------------------------------------------------
  177. CGaussianRandomStream::CGaussianRandomStream( IUniformRandomStream *pUniformStream )
  178. {
  179. AttachToStream( pUniformStream );
  180. }
  181. //-----------------------------------------------------------------------------
  182. // Attaches to a random uniform stream
  183. //-----------------------------------------------------------------------------
  184. void CGaussianRandomStream::AttachToStream( IUniformRandomStream *pUniformStream )
  185. {
  186. AUTO_LOCK( m_mutex );
  187. m_pUniformStream = pUniformStream;
  188. m_bHaveValue = false;
  189. }
  190. //-----------------------------------------------------------------------------
  191. // Generates random numbers
  192. //-----------------------------------------------------------------------------
  193. float CGaussianRandomStream::RandomFloat( float flMean, float flStdDev )
  194. {
  195. AUTO_LOCK( m_mutex );
  196. IUniformRandomStream *pUniformStream = m_pUniformStream ? m_pUniformStream : s_pUniformStream;
  197. float fac,rsq,v1,v2;
  198. if (!m_bHaveValue)
  199. {
  200. // Pick 2 random #s from -1 to 1
  201. // Make sure they lie inside the unit circle. If they don't, try again
  202. do
  203. {
  204. v1 = 2.0f * pUniformStream->RandomFloat() - 1.0f;
  205. v2 = 2.0f * pUniformStream->RandomFloat() - 1.0f;
  206. rsq = v1*v1 + v2*v2;
  207. } while ((rsq > 1.0f) || (rsq == 0.0f));
  208. // The box-muller transformation to get the two gaussian numbers
  209. fac = sqrtf( -2.0f * log(rsq) / rsq );
  210. // Store off one value for later use
  211. m_flRandomValue = v1 * fac;
  212. m_bHaveValue = true;
  213. return flStdDev * (v2 * fac) + flMean;
  214. }
  215. else
  216. {
  217. m_bHaveValue = false;
  218. return flStdDev * m_flRandomValue + flMean;
  219. }
  220. }
  221. //-----------------------------------------------------------------------------
  222. // Creates a histogram (for testing)
  223. //-----------------------------------------------------------------------------