S
sebastian
A while back, as I was playing around with some ideas for pseudo-
random number generators, I came up with a deceptively simple one that
seemed to perform remarkably well. I've subjected it to a battery of
tests, including NIST, DIEHARD, Fourmilab's ENT, as well as some of my
own, and in all cases it's performance has been confirmed it to be a
suitable PRNG algorithm. Thing is, tho, I'm not completely convinced -
it's just too damn simple! I keep thinking that there must be some
flaw in the algorithm that has escaped these trials. What I really
need is someone with a really good grasp of mathematics to look it
over, methinks. Any takers?
Here is the code for the PRNG:
*****************************************************************************
#ifndef GARTH_RANDOM_HPP
#define GARTH_RANDOM_HPP
template < typename UnsignedInteger >
class garth_random
{
public:
inline garth_random( UnsignedInteger initial = UnsignedInteger( ) )
{
seed( initial );
}
void seed( UnsignedInteger initial )
{
m_value_ = initial;
m_counter_ = m_scale_ = 0;
}
UnsignedInteger operator ( )( void )
{
static UnsignedInteger const
zero = 0,
one = 1,
mask = ~zero,
msb = ~( mask >> one );
if( m_counter_++ == zero )
++m_scale_;
UnsignedInteger
temp = ( m_value_ * m_scale_++ ) + one;
return m_value_ =
(
( ( ( temp << one ) | ( ( temp & msb ) ? one : zero ) ) ^
m_counter_ ) ^ mask
);
}
inline operator UnsignedInteger( void )
{
return m_value_;
}
protected:
UnsignedInteger
m_value_,
m_scale_,
m_counter_;
};
#endif
*****************************************************************************
The algorithm has some notable properties that I should probably
mention:
(1) It's *definately* not sufficient for crypographic applications -
predicting it's state from one transition to the next is most likely
trivial.
(2) It has a very precise period of 4^N, where N is the number of bits
in the underlying integral type - so for example, using a 32-bit
number, the PRNG will produce a unique sequence of
18,446,744,073,709,551,616 numbers, given some arbitrary seed value.
(3) Unlike most PRNG's, it doesn't have to be taylored for a specific
architecture (that is, it doesn't rely on the fact that a short is 16-
bits, or some such). It's completely generic, working equally well for
integers containing 8 bits, 64 bits, 128 bits, or what have you.
(4) It's extremely fast.
(5) It's memory footprint is essentially negligible.
(6) It produces a good distribution (much to my dismay, actually).
And here a sample of the output from one of the tests (Fourmilab's
ENT):
*****************************************************************************
--- std::rand ---
Value Char Occurrences Fraction
0 17120290 0.531920
1 15065534 0.468080
Total: 32185824 1.000000
Entropy = 0.997058 bits per bit.
Optimum compression would reduce the size
of this 32185824 bit file by 0 percent.
Chi square distribution for 32185824 samples is 131176.45, and
randomly
would exceed this value less than 0.01 percent of the times.
Arithmetic mean value of data bits is 0.4681 (0.5 = random).
Monte Carlo value for Pi is 3.830697142 (error 21.93 percent).
Serial correlation coefficient is -0.003975 (totally uncorrelated =
0.0).
--- marsenne ---
Value Char Occurrences Fraction
0 16078169 0.500499
1 16046079 0.499501
Total: 32124248 1.000000
Entropy = 0.999999 bits per bit.
Optimum compression would reduce the size
of this 32124248 bit file by 0 percent.
Chi square distribution for 32124248 samples is 32.06, and randomly
would exceed this value less than 0.01 percent of the times.
Arithmetic mean value of data bits is 0.4995 (0.5 = random).
Monte Carlo value for Pi is 3.148647377 (error 0.22 percent).
Serial correlation coefficient is -0.000144 (totally uncorrelated =
0.0).
--- garth_random ---
Value Char Occurrences Fraction
0 16079544 0.500566
1 16043184 0.499434
Total: 32122728 1.000000
Entropy = 0.999999 bits per bit.
Optimum compression would reduce the size
of this 32122728 bit file by 0 percent.
Chi square distribution for 32122728 samples is 41.16, and randomly
would exceed this value less than 0.01 percent of the times.
Arithmetic mean value of data bits is 0.4994 (0.5 = random).
Monte Carlo value for Pi is 3.147524816 (error 0.19 percent).
Serial correlation coefficient is -0.000060 (totally uncorrelated =
0.0).
*****************************************************************************
It outperforms my system's std::rand function by a wide margin (and
consistently did so in all other tests), and performs comparably to
the marsenne-twister (in some tests slightly better, others slightly
worse), in all cases.
Anyway, any and all input would be appreciated. Thanks!
random number generators, I came up with a deceptively simple one that
seemed to perform remarkably well. I've subjected it to a battery of
tests, including NIST, DIEHARD, Fourmilab's ENT, as well as some of my
own, and in all cases it's performance has been confirmed it to be a
suitable PRNG algorithm. Thing is, tho, I'm not completely convinced -
it's just too damn simple! I keep thinking that there must be some
flaw in the algorithm that has escaped these trials. What I really
need is someone with a really good grasp of mathematics to look it
over, methinks. Any takers?
Here is the code for the PRNG:
*****************************************************************************
#ifndef GARTH_RANDOM_HPP
#define GARTH_RANDOM_HPP
template < typename UnsignedInteger >
class garth_random
{
public:
inline garth_random( UnsignedInteger initial = UnsignedInteger( ) )
{
seed( initial );
}
void seed( UnsignedInteger initial )
{
m_value_ = initial;
m_counter_ = m_scale_ = 0;
}
UnsignedInteger operator ( )( void )
{
static UnsignedInteger const
zero = 0,
one = 1,
mask = ~zero,
msb = ~( mask >> one );
if( m_counter_++ == zero )
++m_scale_;
UnsignedInteger
temp = ( m_value_ * m_scale_++ ) + one;
return m_value_ =
(
( ( ( temp << one ) | ( ( temp & msb ) ? one : zero ) ) ^
m_counter_ ) ^ mask
);
}
inline operator UnsignedInteger( void )
{
return m_value_;
}
protected:
UnsignedInteger
m_value_,
m_scale_,
m_counter_;
};
#endif
*****************************************************************************
The algorithm has some notable properties that I should probably
mention:
(1) It's *definately* not sufficient for crypographic applications -
predicting it's state from one transition to the next is most likely
trivial.
(2) It has a very precise period of 4^N, where N is the number of bits
in the underlying integral type - so for example, using a 32-bit
number, the PRNG will produce a unique sequence of
18,446,744,073,709,551,616 numbers, given some arbitrary seed value.
(3) Unlike most PRNG's, it doesn't have to be taylored for a specific
architecture (that is, it doesn't rely on the fact that a short is 16-
bits, or some such). It's completely generic, working equally well for
integers containing 8 bits, 64 bits, 128 bits, or what have you.
(4) It's extremely fast.
(5) It's memory footprint is essentially negligible.
(6) It produces a good distribution (much to my dismay, actually).
And here a sample of the output from one of the tests (Fourmilab's
ENT):
*****************************************************************************
--- std::rand ---
Value Char Occurrences Fraction
0 17120290 0.531920
1 15065534 0.468080
Total: 32185824 1.000000
Entropy = 0.997058 bits per bit.
Optimum compression would reduce the size
of this 32185824 bit file by 0 percent.
Chi square distribution for 32185824 samples is 131176.45, and
randomly
would exceed this value less than 0.01 percent of the times.
Arithmetic mean value of data bits is 0.4681 (0.5 = random).
Monte Carlo value for Pi is 3.830697142 (error 21.93 percent).
Serial correlation coefficient is -0.003975 (totally uncorrelated =
0.0).
--- marsenne ---
Value Char Occurrences Fraction
0 16078169 0.500499
1 16046079 0.499501
Total: 32124248 1.000000
Entropy = 0.999999 bits per bit.
Optimum compression would reduce the size
of this 32124248 bit file by 0 percent.
Chi square distribution for 32124248 samples is 32.06, and randomly
would exceed this value less than 0.01 percent of the times.
Arithmetic mean value of data bits is 0.4995 (0.5 = random).
Monte Carlo value for Pi is 3.148647377 (error 0.22 percent).
Serial correlation coefficient is -0.000144 (totally uncorrelated =
0.0).
--- garth_random ---
Value Char Occurrences Fraction
0 16079544 0.500566
1 16043184 0.499434
Total: 32122728 1.000000
Entropy = 0.999999 bits per bit.
Optimum compression would reduce the size
of this 32122728 bit file by 0 percent.
Chi square distribution for 32122728 samples is 41.16, and randomly
would exceed this value less than 0.01 percent of the times.
Arithmetic mean value of data bits is 0.4994 (0.5 = random).
Monte Carlo value for Pi is 3.147524816 (error 0.19 percent).
Serial correlation coefficient is -0.000060 (totally uncorrelated =
0.0).
*****************************************************************************
It outperforms my system's std::rand function by a wide margin (and
consistently did so in all other tests), and performs comparably to
the marsenne-twister (in some tests slightly better, others slightly
worse), in all cases.
Anyway, any and all input would be appreciated. Thanks!