I need to migrate some C# code to Python. The original code makes use of the Random
class. The migrated code must be cycle-accurate (namely consecutive calls to Next()
must produce the same results in both codes). Some questions:
Random
in Python?I don't know of any library that is available for both Python and C# and which generates the same random numbers for both. However, you may be able to take advantage of IronPython. The default Random
implementation differs between IronPython and CPython, but the WichmannHill
class does not.
You can use C# to instantiate the WichmannHill
class in IronPython and get the same values as CPython for the same seed. Alternatively, you can implement the Wichmann-Hill algorithm in C# relatively easily by translating the Python code in random.py
.
Another option is to take the CPython implementation of Random
's Mersenne Twister algorithm and translate that to C# to get identical results.
I know this is an old question, but I ended up needing a solution to this. I ended up implementing C#'s Random class in python. It works as long as you don't need random numbers larger than 2147483647, I ended up not needing that functionality so I left it unimplemented.
https://gist.github.com/BadStreff/541cf2e6953b3c666f83127a1d4f6a47
from ctypes import *
# implemented from:
# http://referencesource.microsoft.com/#mscorlib/system/random.cs,dec894a7e816e665
class Random(object):
def __init__(self, seed):
self.seed = c_int(seed).value
self.MBIG = 2147483647
self.MMIN = -2147483648
self.MZ = 0
self.MSEED = 161803398
self.SeedArray = [0] * 56
if seed == self.MMIN:
subtraction = self.MBIG
else:
subtraction = abs(seed)
mj = c_int(self.MSEED - subtraction).value
self.SeedArray[55] = mj
mk = 1
for i in range(1, 55):
ii = (21 * i) % 55
self.SeedArray[ii] = mk
mk = mj - mk
if mk < 0:
mk += self.MBIG
mj = self.SeedArray[ii]
for k in range(1, 5):
for i in range(1, 56):
self.SeedArray[i] -= self.SeedArray[1 + (i + 30) % 55]
if self.SeedArray[i] < 0:
self.SeedArray[i] = c_int(self.SeedArray[i] + self.MBIG).value
self.inext = 0
self.inextp = 21
self.seed = 1
def InternalSample(self):
locINext = self.inext + 1
locINextp = self.inextp + 1
if locINext >= 56:
locINext = 1
if locINextp >= 56:
locINextp = 1
retVal = c_int(self.SeedArray[locINext] - self.SeedArray[locINextp]).value
if retVal == self.MBIG:
retVal -= 1
if retVal < 0:
retVal = c_int(retVal + self.MBIG).value
self.SeedArray[locINext] = retVal
self.inext = locINext
self.inextp = locINextp
return retVal
def Next(self, minValue=None, maxValue=None):
if minValue == None:
return self.InternalSample()
valRange = maxValue - minValue
if valRange <= self.MBIG:
return int(c_float(self.Sample() * valRange).value) + minValue
else:
return self.GetSampleForLargeRange() * valRange + minValue
def GetSampleRangeForLargeRange(self):
pass
def Sample(self):
s = self.InternalSample()
ret = c_double(s * c_double(1.0/self.MBIG).value).value
# print(f'sample: {s}\nret: {ret}')
return ret